Skip to content

Keyboard Layouts

opencode-agent[bot] edited this page May 11, 2026 · 1 revision

Keyboard Layouts (l10n)

Locale-specific keyboard interpreters that translate hardware scancodes into virtual key codes and characters for a given language/keyboard.

Overview

JNode's Keyboard Layouts subsystem provides locale-specific KeyboardInterpreter implementations that map hardware scancodes (from PS/2 keyboards or translated USB HID reports) to Java KeyEvent virtual key codes and character values. Each layout extends AbstractKeyboardInterpreter and overrides initKeys() to populate a scancode-to-key mapping with appropriate character translations, dead keys, and modifier states for a specific locale.

The layouts live in core/src/driver/org/jnode/driver/input/l10n/ and are selected at runtime via KeyboardLayoutManager, which resolves the active locale and instantiates the matching interpreter.

Key Components

Class / File Role
core/src/driver/org/jnode/driver/input/AbstractKeyboardInterpreter.java Base class: modifier state, interpretScancode(), dead key handling, extended scancode processing
core/src/driver/org/jnode/driver/input/Key.java Per-scancode descriptor: lowerChar/upperChar/altGrChar, lowerVirtuelKey/upperVirtuelKey/altGrVirtuelKey, dead key maps
core/src/driver/org/jnode/driver/input/Keys.java Array of 128 Key objects indexed by scancode; getScanCode(int keycode) for reverse lookup
core/src/driver/org/jnode/driver/input/KeyboardLayoutManager.java Factory manager resolving locale to layout ID, plugin extension point, and fallback class loading
core/src/driver/org/jnode/driver/input/KeyboardInterpreter.java Interface defining Factory inner interface and interpretScancode(int) / interpretKeycode(int) methods
core/src/driver/org/jnode/driver/input/KeyboardEvent.java Event wrapper: key type (KEY_PRESSED/KEY_RELEASED), timestamp, modifiers, virtual key code, character
l10n/KeyboardInterpreter_US_en.java US English QWERTY layout; base reference layout
l10n/KeyboardInterpreter_DE.java German QWERTZ layout; dead key support for umlauts (ä/ö/ü), Euro sign (\u20ac), sharp S (\u00df)
l10n/KeyboardInterpreter_GB_en.java British English layout; slightly different punctuation mapping vs US
l10n/KeyboardInterpreter_FR_fr.java French AZERTY layout; dead keys for accents, currency symbols
l10n/KeyboardInterpreter_IT_it.java Italian layout
l10n/KeyboardInterpreter_ES.java Spanish layout
l10n/KeyboardInterpreter_HU.java Hungarian layout (standard)
l10n/KeyboardInterpreter_HU_101.java Hungarian 101-key layout variant
l10n/KeyboardInterpreter_HU_hu.java Hungarian with language tag
l10n/KeyboardInterpreter_HU_hu_101.java Hungarian 101-key with language tag
l10n/KeyboardInterpreter_RU.java Russian layout (Cyrillic)
l10n/KeyboardInterpreter_SE.java Swedish layout
l10n/KeyboardInterpreter_DK.java Danish layout
l10n/KeyboardInterpreter_NO.java Norwegian layout
l10n/KeyboardInterpreter_DV.java Dvorak-style layout variant
l10n/KeyboardInterpreter_BE_fr.java Belgian French layout
l10n/KeyboardInterpreter_CH_fr.java Swiss French layout

How It Works

Scancode-to-Character Translation

Each layout extends AbstractKeyboardInterpreter and overrides initKeys(Keys keys) to populate the per-scancode Key descriptors:

public class KeyboardInterpreter_DE extends AbstractKeyboardInterpreter {
    protected void initKeys(Keys keys) {
        keys.setKey(1, new Key('^', '\u00b0'));                    // scancode 1 → '^' lower, '\u00b0' upper
        keys.setKey(2, new Key('1', KeyEvent.VK_1, '!', KeyEvent.VK_EXCLAMATION_MARK));
        keys.setKey(3, new Key('2', KeyEvent.VK_2, '"', KeyEvent.VK_QUOTEDBL));
        keys.setKey(12, new Key('\u00df', KeyEvent.VK_PLUS, '?', KeyEvent.VK_UNDEFINED, '\\', KeyEvent.VK_BACK_SLASH));
        keys.setKey(16, new Key('q', KeyEvent.VK_Q, 'Q', KeyEvent.VK_Q, '@', KeyEvent.VK_AT));
        keys.setKey(42, new Key(KeyEvent.VK_SHIFT));
        keys.setKey(54, new Key(KeyEvent.VK_SHIFT));
        // ... 128-entry array populated by scancode
    }
}

The Key class supports:

  • Lower/upper characters: Plain text and shifted character
  • AltGr character: Character produced with AltGr modifier
  • Virtual key codes: KeyEvent.VK_* constants for the virtual key
  • Dead key associations: Accented character combinations via addDeadKeyChar(int vk, char[] chars)

Dead Key Handling

Dead keys (accent marks that combine with a following letter) are handled via exception-based control flow in interpretExtendedScanCode():

switch (vk) {
    case KeyEvent.VK_DEAD_ACUTE:
    case KeyEvent.VK_DEAD_GRAVE:
    case KeyEvent.VK_DEAD_CIRCUMFLEX:
    case KeyEvent.VK_DEAD_DIAERESIS:
    case KeyEvent.VK_DEAD_TILDE:
        lastDeadVK = vk;
        throw deadKeyException;  // buffer the dead key state
}
// On next keypress, combine dead state with key:
if (lastDeadVK != -1) {
    char[] deadChars = key.getDeadKeyChar(lastDeadVK);
    if (flags == SHIFT_DOWN_MASK) return deadChars[1]; // uppercase accented char
    else return deadChars[0];                          // lowercase accented char
}

The German layout (KeyboardInterpreter_DE) demonstrates dead key support:

key = keys.getKey(18);  // 'E' key
key.addDeadKeyChar(KeyEvent.VK_DEAD_ACUTE, new char[]{'\u00e9', '\u00c9'});  // é, É
key.addDeadKeyChar(KeyEvent.VK_DEAD_GRAVE, new char[]{'\u00e8', '\u00c8'});  // è, È
key.addDeadKeyChar(KeyEvent.VK_DEAD_DIAERESIS, new char[]{'\u00eb', '\u00cb'});  // ë, Ë
key.addDeadKeyChar(KeyEvent.VK_DEAD_CIRCUMFLEX, new char[]{'\u00ea', '\u00ca'});  // ê, Ê

Layout Resolution

KeyboardLayoutManager.createDefaultKeyboardInterpreter() resolves the locale using ResourceBundle named org.jnode.driver.input.KeyboardLayout with keys defaultCountry, defaultRegion, defaultVariant. It builds an ID like US_en, HU_hu_101, or DE and looks up the factory via the keyboard-layouts extension point. Fallback loads org.jnode.driver.input.l10n.KeyboardInterpreter_<id> by class name:

final String classI10N = "org.jnode.driver.input.l10n.KeyboardInterpreter_" + id;
return new KIClassWrapper(classI10N).create();

Modifier State and CapsLock

AbstractKeyboardInterpreter.adjustFlags() tracks modifier state via a bitmask field. CapsLock uses a 4-state counter where states 0 and 3 apply SHIFT_DOWN_MASK, states 1 and 2 clear it — requiring two key-down events per toggle. Extended scancodes (0xE0 prefix) set extendedMode = true; the next scancode is processed as an extended key (arrows, Home/End, etc.).

Layout Registration

Layouts are registered via the org.jnode.driver.input.keyboard-layouts extension point in plugin descriptors:

<extension-point name="keyboard-layouts"/>
<extension point="org.jnode.driver.input.keyboard-layouts">
    <layout name="US_en" class="org.jnode.driver.input.l10n.KeyboardInterpreter_US_en"/>
    <layout name="DE" class="org.jnode.driver.input.l10n.KeyboardInterpreter_DE"/>
    <!-- ... -->
</extension>

Gotchas

  • 19 layouts, inconsistent coverage: Layouts exist for US, GB, DE, FR, IT, ES, HU (4 variants), RU, SE, DK, NO, DV, BE, CH. Other locales (e.g., PT, PL, JP, CN) have no layout.

  • Dead key performance: Exception-based control flow (DeadKeyException, UnsupportedKeyException) is acknowledged as "an evil hack" in the code. Each dead key throw creates an exception object per invocation.

  • CapsLock 4-state cycle: The unusual 4-state counter means CapsLock requires two key-down events. Applications that monitor CapsLock via KeyEvent.VK_CAPS_LOCK may not see expected behavior.

  • Stateful interpreter instances: Each KeyboardInterpreter maintains per-keyboard modifier state and dead-key state. Sharing an instance across multiple keyboards causes input cross-talk.

  • No Unicode beyond BMP: All characters use 16-bit char. No surrogate pair handling for non-BMP Unicode.

  • Limited numpad support: Numpad keys (71-83, 86-110 in scancode map) are mapped but numlock state is not tracked.

  • AltGr is just a modifier flag: ALT_GRAPH_DOWN_MASK is set when AltGr is pressed; the layout's AltGr character is then returned if that flag is set. There is no separate AltGr key symbol.

Related Pages

Clone this wiki locally