# JIS かな Key Detection in Karukan: Returning to Hiragana Mode on macOS

> Learn how Karukan on macOS uses JIS かな key detection to seamlessly return to hiragana mode. Understand the event interception and keysym translation that keeps your input smooth and efficient.

- Repository: [Hitoshi Togasaki/karukan](https://github.com/togatoga/karukan)
- Tags: internals
- Published: 2026-07-03

---

**Karukan's macOS frontend intercepts the JIS かな key (keyCode 104) and translates it into a Super_R keysym (0xffec) that commands the input engine to toggle back to hiragana mode, consuming the event to prevent duplicate system processing.**

Karukan is an open-source Japanese input method editor that provides consistent behavior across Linux and macOS platforms. Understanding how JIS かな key detection functions on macOS requires examining the platform-specific adapter code in `karukan-macos`. This implementation ensures that pressing the physical かな key instantly returns the input mode to hiragana without interfering with the system's normal key-processing pipeline.

## How JIS かな Key Detection Works

The macOS implementation follows a strict interception-and-translation pattern to handle the hardware-specific JIS かな key. This process involves identifying the physical key event, blocking default system behavior, and issuing a cross-platform command to the core engine.

### Key Code Identification

In [`KeyCodeMap.swift`](https://github.com/togatoga/karukan/blob/main/KeyCodeMap.swift), Karukan defines the macOS virtual key code for the JIS かな key as a constant value. This represents the hardware key code (kVK_JIS_Kana) that macOS reports when users press the かな key on JIS-layout keyboards.

```swift
/// JIS keyboard かな key (kVK_JIS_Kana).
static let kanaKeyCode: UInt16 = 104
/// XKB Super_R keysym — the engine's katakana→hiragana toggle.
static let superRKeysym: UInt32 = 0xffec

```

The `superRKeysym` constant (0xffec) serves as the universal signal across platforms. While macOS uses the JIS-specific key code 104, the engine expects the standard XKB Super_R keysym to trigger the hiragana toggle.

### Event Interception in KarukanInputController

Inside [`KarukanInputController.swift`](https://github.com/togatoga/karukan/blob/main/KarukanInputController.swift), the `handle(_:)` method acts as the primary entry point for all key events. When the controller detects the JIS かな key, it immediately consumes the event by returning `true`. This prevents macOS from inserting the raw key code after the engine processes it, avoiding duplicate input.

```swift
// JIS かな key (and Karabiner right-Command tap → かな): always
// consume so the system doesn't process keyCode 104 after the engine
// returns not_consumed (already in hiragana mode).
if event.keyCode == KeyCodeMap.kanaKeyCode {
    let key = EngineKeyEvent(keysym: KeyCodeMap.superRKeysym,
                             modifiers: KeyModifiers())
    if let result = engineClient.processKeySync(key) {
        apply(actions: result.actions, client: client)
    }
    return true
}

```

The `EngineKeyEvent` struct encapsulates the keysym and modifier state, creating a platform-agnostic message that the Rust-based core engine can interpret regardless of the operating system.

### Engine Processing and Mode Toggle

Once the fabricated event reaches the engine via `engineClient.processKeySync(key)`, the core logic in [`engine/src/core/keycode.rs`](https://github.com/togatoga/karukan/blob/main/engine/src/core/keycode.rs) interprets the 0xffec keysym as a direct command to switch input modes. The engine toggles its internal state from katakana back to hiragana and returns a set of actions to update the user interface.

The synchronous processing ensures that the mode change completes before the method returns, allowing the controller to immediately apply the results to the text client through `apply(actions:client:)`.

## Implementation Details and Code Examples

The following simplified example demonstrates how to intercept the JIS かな key in a custom InputMethodKit controller:

```swift
func handleKanaKey(event: NSEvent, client: IMKTextInput) {
    // Detect JIS kana key using the defined constant
    guard event.keyCode == KeyCodeMap.kanaKeyCode else { return }
    
    // Translate to engine toggle keysym without modifiers
    let toggle = EngineKeyEvent(keysym: KeyCodeMap.superRKeysym,
                                modifiers: KeyModifiers())
    
    // Send to the engine and apply UI updates synchronously
    if let result = engineClient.processKeySync(toggle) {
        apply(actions: result.actions, client: client)
    }
}

```

From the engine perspective, the Rust implementation handles the toggle keysym as follows:

```rust
// In the engine (karukan-im), the Super_R keysym toggles Katakana→Hiragana
match keysym {
    0xffec => self.toggle_input_mode(),
    _ => … // regular processing
}

```

## Summary

- **KeyCodeMap.swift** defines `kanaKeyCode` (104) and `superRKeysym` (0xffec) to bridge macOS hardware codes with the engine's expected input.
- **KarukanInputController.swift** intercepts keyCode 104 and consumes the event to prevent system-level duplicate processing.
- The controller fabricates an `EngineKeyEvent` with the Super_R keysym and dispatches it via `engineClient.processKeySync(key)`.
- The engine core interprets 0xffec as a hiragana toggle command, switching modes and returning UI update actions.
- This architecture ensures consistent JIS かな key behavior across macOS and Linux platforms while maintaining separation between platform-specific event handling and input logic.

## Frequently Asked Questions

### What is the virtual key code for the JIS かな key on macOS?

The JIS かな key maps to virtual key code **104** (kVK_JIS_Kana) in macOS. Karukan defines this as `KeyCodeMap.kanaKeyCode` in [`KeyCodeMap.swift`](https://github.com/togatoga/karukan/blob/main/KeyCodeMap.swift) to identify the physical key regardless of keyboard layout variations.

### Why does Karukan use the Super_R keysym for the かな key?

Karukan uses the **Super_R keysym (0xffec)** as a cross-platform standard to represent the hiragana toggle command. While macOS reports the JIS かな key as code 104, the Rust-based engine expects standard XKB keysyms. This abstraction allows the same core logic to handle the toggle on both macOS and Linux (Fcitx5) without platform-specific code in the engine.

### How does Karukan prevent duplicate character insertion when handling the かな key?

The `KarukanInputController.handle(_:)` method returns `true` immediately after processing the JIS かな key, which marks the event as **consumed** in the InputMethodKit pipeline. This prevents macOS from passing the raw key code 104 to the active application after the engine has already handled the mode switch, eliminating duplicate input.

### Can the JIS かな key detection be customized for different keyboard layouts?

Currently, the key code detection is hardcoded to value 104 in [`KeyCodeMap.swift`](https://github.com/togatoga/karukan/blob/main/KeyCodeMap.swift). Users employing remapping tools (such as Karabiner-Elements) can configure their systems to emit keyCode 104 for other physical keys (like right-Command tap), and Karukan will treat these remapped events identically to native JIS かな key presses.