feature(input): implement keyboard layouts and utf-8 input subsystem

* Adding keyboard layout (backend)

* Update settings.c with new keyboard tab

* Fixing keyboard icon && Fixing long loading time in settings.c

* Refactor of key handling for a larger compatibility with the keyboard layout

* Adding keyboard handler

* Udating ps2.c with the new logic

* Updating WM/kernel/userland with the new input system

* Fixing keycode range && Updating dead keys handling

* Add comments for explanation

* Update notepad & vm.c to parse utf-8

* Adding utf-8 parsing utils in libc && Update notepad.c

* Adding icon for icon settings

* Fixing a warning with double definition

* Adding new kb kayout: QWERTZ and DVORAK && Update new layout instrauction

* Add documentation for keyboard input subsystem

This document outlines the architecture and design of the input subsystem, focusing on keyboard input processing, driver responsibilities, keycode representation, and keymap functionality.

---------

Co-authored-by: boreddevnl <chris@boreddev.nl>
This commit is contained in:
Lluciocc
2026-04-23 21:31:52 +02:00
committed by GitHub
parent 228b5753d9
commit 915e33434e
22 changed files with 1624 additions and 275 deletions

214
src/input/keyboard.c Normal file
View File

@@ -0,0 +1,214 @@
#include "keyboard.h"
#include "keymap.h"
typedef struct {
bool e0_prefix;
bool left_shift;
bool right_shift;
bool left_ctrl;
bool right_ctrl;
bool left_alt;
bool right_alt;
bool caps_lock;
bool num_lock;
bool scroll_lock;
uint32_t dead_key;
} keyboard_state_t;
static keyboard_state_t g_kb;
// table of scancode to keycode for set 1 (without E0 prefix)
static const uint16_t set1_base[128] = {
[0x01] = KEY_ESC,
[0x02] = KEY_1, [0x03] = KEY_2, [0x04] = KEY_3, [0x05] = KEY_4,
[0x06] = KEY_5, [0x07] = KEY_6, [0x08] = KEY_7, [0x09] = KEY_8,
[0x0A] = KEY_9, [0x0B] = KEY_0,
[0x0C] = KEY_MINUS, [0x0D] = KEY_EQUAL,
[0x0E] = KEY_BACKSPACE,
[0x0F] = KEY_TAB,
[0x10] = KEY_Q, [0x11] = KEY_W, [0x12] = KEY_E, [0x13] = KEY_R,
[0x14] = KEY_T, [0x15] = KEY_Y, [0x16] = KEY_U, [0x17] = KEY_I,
[0x18] = KEY_O, [0x19] = KEY_P,
[0x1A] = KEY_LBRACKET, [0x1B] = KEY_RBRACKET,
[0x1C] = KEY_ENTER,
[0x1D] = KEY_LEFT_CTRL,
[0x1E] = KEY_A, [0x1F] = KEY_S, [0x20] = KEY_D, [0x21] = KEY_F,
[0x22] = KEY_G, [0x23] = KEY_H, [0x24] = KEY_J, [0x25] = KEY_K,
[0x26] = KEY_L,
[0x27] = KEY_SEMICOLON, [0x28] = KEY_APOSTROPHE, [0x29] = KEY_GRAVE,
[0x2A] = KEY_LEFT_SHIFT,
[0x2B] = KEY_BACKSLASH,
[0x2C] = KEY_Z, [0x2D] = KEY_X, [0x2E] = KEY_C, [0x2F] = KEY_V,
[0x30] = KEY_B, [0x31] = KEY_N, [0x32] = KEY_M,
[0x33] = KEY_COMMA, [0x34] = KEY_DOT, [0x35] = KEY_SLASH,
[0x36] = KEY_RIGHT_SHIFT,
[0x37] = KEY_KP_STAR,
[0x38] = KEY_LEFT_ALT,
[0x39] = KEY_SPACE,
[0x3A] = KEY_CAPS_LOCK,
[0x3B] = KEY_F1, [0x3C] = KEY_F2, [0x3D] = KEY_F3, [0x3E] = KEY_F4,
[0x3F] = KEY_F5, [0x40] = KEY_F6, [0x41] = KEY_F7, [0x42] = KEY_F8,
[0x43] = KEY_F9, [0x44] = KEY_F10,
[0x45] = KEY_NUM_LOCK,
[0x46] = KEY_SCROLL_LOCK,
[0x47] = KEY_KP_7, [0x48] = KEY_KP_8, [0x49] = KEY_KP_9,
[0x4A] = KEY_KP_MINUS,
[0x4B] = KEY_KP_4, [0x4C] = KEY_KP_5, [0x4D] = KEY_KP_6,
[0x4E] = KEY_KP_PLUS,
[0x4F] = KEY_KP_1, [0x50] = KEY_KP_2, [0x51] = KEY_KP_3,
[0x52] = KEY_KP_0, [0x53] = KEY_KP_DOT,
[0x57] = KEY_F11,
[0x58] = KEY_F12,
};
// table of scancode to keycode for set 1 with E0 prefix
static const uint16_t set1_ext[128] = {
[0x1C] = KEY_KP_ENTER,
[0x1D] = KEY_RIGHT_CTRL,
[0x35] = KEY_KP_SLASH,
[0x38] = KEY_RIGHT_ALT,
[0x47] = KEY_HOME,
[0x48] = KEY_ARROW_UP,
[0x49] = KEY_PAGE_UP,
[0x4B] = KEY_ARROW_LEFT,
[0x4D] = KEY_ARROW_RIGHT,
[0x4F] = KEY_END,
[0x50] = KEY_ARROW_DOWN,
[0x51] = KEY_PAGE_DOWN,
[0x52] = KEY_INSERT,
[0x53] = KEY_DELETE,
};
void keyboard_init(void) {
g_kb.e0_prefix = false;
g_kb.left_shift = false;
g_kb.right_shift = false;
g_kb.left_ctrl = false;
g_kb.right_ctrl = false;
g_kb.left_alt = false;
g_kb.right_alt = false;
g_kb.caps_lock = false;
g_kb.num_lock = false;
g_kb.scroll_lock = false;
g_kb.dead_key = 0;
}
// Convert a set 1 scancode (with optional E0 prefix) to a keycode. Returns KEY_NONE if the scancode is invalid or unmapped.
uint16_t keyboard_keycode_from_set1(uint8_t scancode, bool extended) {
if (scancode >= 128) return KEY_NONE;
return extended ? set1_ext[scancode] : set1_base[scancode];
}
// Update the state of modifier keys based on the keycode and press/release event.
static void update_mod_state(uint16_t keycode, bool pressed) {
switch (keycode) {
case KEY_LEFT_SHIFT: g_kb.left_shift = pressed; break;
case KEY_RIGHT_SHIFT: g_kb.right_shift = pressed; break;
case KEY_LEFT_CTRL: g_kb.left_ctrl = pressed; break;
case KEY_RIGHT_CTRL: g_kb.right_ctrl = pressed; break;
case KEY_LEFT_ALT: g_kb.left_alt = pressed; break;
case KEY_RIGHT_ALT: g_kb.right_alt = pressed; break;
case KEY_CAPS_LOCK:
if (pressed) g_kb.caps_lock = !g_kb.caps_lock;
break;
case KEY_NUM_LOCK:
if (pressed) g_kb.num_lock = !g_kb.num_lock;
break;
case KEY_SCROLL_LOCK:
if (pressed) g_kb.scroll_lock = !g_kb.scroll_lock;
break;
default:
break;
}
}
// Get the current state of modifier keys as a bitmask.
uint32_t keyboard_get_modifiers(void) {
uint32_t mods = 0;
if (g_kb.left_shift || g_kb.right_shift) mods |= KB_MOD_SHIFT;
if (g_kb.left_ctrl || g_kb.right_ctrl) mods |= KB_MOD_CTRL;
if (g_kb.left_alt) mods |= KB_MOD_ALT;
if (g_kb.right_alt) mods |= KB_MOD_ALTGR;
if (g_kb.caps_lock) mods |= KB_MOD_CAPS;
if (g_kb.num_lock) mods |= KB_MOD_NUM;
if (g_kb.scroll_lock) mods |= KB_MOD_SCROLL;
return mods;
}
// Helper functions to check specific modifiers
bool keyboard_shift_pressed(void) {
return (keyboard_get_modifiers() & KB_MOD_SHIFT) != 0;
}
bool keyboard_ctrl_pressed(void) {
return (keyboard_get_modifiers() & KB_MOD_CTRL) != 0;
}
bool keyboard_handle_set1_scancode(uint8_t scancode, keyboard_event_t *ev) {
if (!ev) return false;
ev->keycode = KEY_NONE;
ev->codepoint = 0;
ev->mods = keyboard_get_modifiers();
ev->pressed = false;
ev->repeat = false;
ev->is_text = false;
if (scancode == 0xE0) {
g_kb.e0_prefix = true;
return false;
}
if (scancode == 0xE1) {
g_kb.e0_prefix = false;
return false; // ignore Pause/Break's multi-byte sequence for simplicity
}
bool release = (scancode & 0x80) != 0;
uint8_t base = (uint8_t)(scancode & 0x7F);
uint16_t keycode = keyboard_keycode_from_set1(base, g_kb.e0_prefix);
g_kb.e0_prefix = false;
if (keycode == KEY_NONE) return false;
bool pressed = !release;
update_mod_state(keycode, pressed);
ev->keycode = keycode;
ev->pressed = pressed;
ev->mods = keyboard_get_modifiers();
if (!pressed) {
return true;
}
keymap_result_t r = keymap_translate_keycode(keycode, ev->mods);
if (r.is_dead) {
g_kb.dead_key = r.codepoint;
return true;
}
if (r.is_text) {
uint32_t cp = r.codepoint;
if (g_kb.dead_key != 0) {
uint32_t composed = keymap_compose(g_kb.dead_key, cp);
if (composed != 0) cp = composed;
g_kb.dead_key = 0;
}
ev->codepoint = cp;
ev->is_text = true;
}
return true;
}

26
src/input/keyboard.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef KEYBOARD_H
#define KEYBOARD_H
#include <stdint.h>
#include <stdbool.h>
#include "keycodes.h"
typedef struct {
uint16_t keycode;
uint32_t codepoint;
uint32_t mods;
bool pressed;
bool repeat;
bool is_text;
} keyboard_event_t;
void keyboard_init(void);
bool keyboard_handle_set1_scancode(uint8_t scancode, keyboard_event_t *ev);
uint16_t keyboard_keycode_from_set1(uint8_t scancode, bool extended);
bool keyboard_shift_pressed(void);
bool keyboard_ctrl_pressed(void);
uint32_t keyboard_get_modifiers(void);
#endif

84
src/input/keycodes.h Normal file
View File

@@ -0,0 +1,84 @@
#ifndef KEYCODES_H
#define KEYCODES_H
#include <stdint.h>
typedef enum {
KEY_NONE = 0,
KEY_ESC,
KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0,
KEY_MINUS,
KEY_EQUAL,
KEY_BACKSPACE,
KEY_TAB,
KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P,
KEY_LBRACKET,
KEY_RBRACKET,
KEY_ENTER,
KEY_LEFT_CTRL,
KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L,
KEY_SEMICOLON,
KEY_APOSTROPHE,
KEY_GRAVE,
KEY_LEFT_SHIFT,
KEY_BACKSLASH,
KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M,
KEY_COMMA,
KEY_DOT,
KEY_SLASH,
KEY_RIGHT_SHIFT,
KEY_KP_STAR,
KEY_LEFT_ALT,
KEY_SPACE,
KEY_CAPS_LOCK,
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6,
KEY_F7, KEY_F8, KEY_F9, KEY_F10,
KEY_NUM_LOCK,
KEY_SCROLL_LOCK,
KEY_KP_7, KEY_KP_8, KEY_KP_9,
KEY_KP_MINUS,
KEY_KP_4, KEY_KP_5, KEY_KP_6,
KEY_KP_PLUS,
KEY_KP_1, KEY_KP_2, KEY_KP_3,
KEY_KP_0,
KEY_KP_DOT,
KEY_F11,
KEY_F12,
KEY_KP_ENTER,
KEY_RIGHT_CTRL,
KEY_KP_SLASH,
KEY_RIGHT_ALT,
KEY_HOME,
KEY_ARROW_UP,
KEY_PAGE_UP,
KEY_ARROW_LEFT,
KEY_ARROW_RIGHT,
KEY_END,
KEY_ARROW_DOWN,
KEY_PAGE_DOWN,
KEY_INSERT,
KEY_DELETE,
KEY_MAX
} keycode_t;
#define KB_MOD_SHIFT 0x0001u
#define KB_MOD_CTRL 0x0002u
#define KB_MOD_ALT 0x0004u
#define KB_MOD_ALTGR 0x0008u
#define KB_MOD_CAPS 0x0010u
#define KB_MOD_NUM 0x0020u
#define KB_MOD_SCROLL 0x0040u
#endif

447
src/input/keymap.c Normal file
View File

@@ -0,0 +1,447 @@
#include "keymap.h"
#define DEAD_NORMAL 0x01
#define DEAD_SHIFT 0x02
#define DEAD_ALTGR 0x04
#define DEAD_SHIFT_ALTGR 0x08
typedef struct {
const char *name;
keymap_entry_t entries[KEY_MAX];
} keyboard_layout_t;
typedef struct {
uint32_t dead;
uint32_t base;
uint32_t result;
} compose_entry_t;
/*
HOW TO ADD A NEW LAYOUT:
1. Add a new entry to the keymap_id_t enum in keymap.h
2. Create a new keyboard_layout_t instance in keymap.c, filling the entries array with the appropriate codepoints for each keycode and modifier combination. Use 0 for unused combinations.
3. Add the new layout to the g_layouts array in keymap.c
4. (Optional) If your layout has dead keys, add the appropriate entries to the g_compose_table array in keymap.c, defining how dead keys combine with base characters to produce composed characters.
5. Add the layout to /src/userland/gui/settings.c in init_settings_widgets() in the *keyboard_opts[] array and increment the count in widget_dropdown_init for drop_keyboard.
*/
// QWERTY LAYOUT US (DEFAULT)
static const keyboard_layout_t layout_qwerty = {
"QWERTY (US)",
.entries = {
// [KEYCODE] = {normal, shift, altgr, shift_altgr, dead_mask, alpha}
[KEY_1] = {'1', '!', 0, 0, 0, false},
[KEY_2] = {'2', '@', 0, 0, 0, false},
[KEY_3] = {'3', '#', 0, 0, 0, false},
[KEY_4] = {'4', '$', 0, 0, 0, false},
[KEY_5] = {'5', '%', 0, 0, 0, false},
[KEY_6] = {'6', '^', 0, 0, 0, false},
[KEY_7] = {'7', '&', 0, 0, 0, false},
[KEY_8] = {'8', '*', 0, 0, 0, false},
[KEY_9] = {'9', '(', 0, 0, 0, false},
[KEY_0] = {'0', ')', 0, 0, 0, false},
[KEY_MINUS] = {'-', '_', 0, 0, 0, false},
[KEY_EQUAL] = {'=', '+', 0, 0, 0, false},
[KEY_Q] = {'q', 'Q', 0, 0, 0, true},
[KEY_W] = {'w', 'W', 0, 0, 0, true},
[KEY_E] = {'e', 'E', 0, 0, 0, true},
[KEY_R] = {'r', 'R', 0, 0, 0, true},
[KEY_T] = {'t', 'T', 0, 0, 0, true},
[KEY_Y] = {'y', 'Y', 0, 0, 0, true},
[KEY_U] = {'u', 'U', 0, 0, 0, true},
[KEY_I] = {'i', 'I', 0, 0, 0, true},
[KEY_O] = {'o', 'O', 0, 0, 0, true},
[KEY_P] = {'p', 'P', 0, 0, 0, true},
[KEY_LBRACKET] = {'[', '{', 0, 0, 0, false},
[KEY_RBRACKET] = {']', '}', 0, 0, 0, false},
[KEY_A] = {'a', 'A', 0, 0, 0, true},
[KEY_S] = {'s', 'S', 0, 0, 0, true},
[KEY_D] = {'d', 'D', 0, 0, 0, true},
[KEY_F] = {'f', 'F', 0, 0, 0, true},
[KEY_G] = {'g', 'G', 0, 0, 0, true},
[KEY_H] = {'h', 'H', 0, 0, 0, true},
[KEY_J] = {'j', 'J', 0, 0, 0, true},
[KEY_K] = {'k', 'K', 0, 0, 0, true},
[KEY_L] = {'l', 'L', 0, 0, 0, true},
[KEY_SEMICOLON] = {';', ':', 0, 0, 0, false},
[KEY_APOSTROPHE] = {'\'', '"', 0, 0, 0, false},
[KEY_GRAVE] = {'`', '~', 0, 0, 0, false},
[KEY_BACKSLASH] = {'\\', '|', 0, 0, 0, false},
[KEY_Z] = {'z', 'Z', 0, 0, 0, true},
[KEY_X] = {'x', 'X', 0, 0, 0, true},
[KEY_C] = {'c', 'C', 0, 0, 0, true},
[KEY_V] = {'v', 'V', 0, 0, 0, true},
[KEY_B] = {'b', 'B', 0, 0, 0, true},
[KEY_N] = {'n', 'N', 0, 0, 0, true},
[KEY_M] = {'m', 'M', 0, 0, 0, true},
[KEY_COMMA] = {',', '<', 0, 0, 0, false},
[KEY_DOT] = {'.', '>', 0, 0, 0, false},
[KEY_SLASH] = {'/', '?', 0, 0, 0, false},
[KEY_SPACE] = {' ', ' ', 0, 0, 0, false},
[KEY_KP_SLASH] = {'/', '/', 0, 0, 0, false},
[KEY_KP_STAR] = {'*', '*', 0, 0, 0, false},
[KEY_KP_MINUS] = {'-', '-', 0, 0, 0, false},
[KEY_KP_PLUS] = {'+', '+', 0, 0, 0, false},
[KEY_KP_DOT] = {'.', '.', 0, 0, 0, false},
[KEY_KP_0] = {'0', '0', 0, 0, 0, false},
[KEY_KP_1] = {'1', '1', 0, 0, 0, false},
[KEY_KP_2] = {'2', '2', 0, 0, 0, false},
[KEY_KP_3] = {'3', '3', 0, 0, 0, false},
[KEY_KP_4] = {'4', '4', 0, 0, 0, false},
[KEY_KP_5] = {'5', '5', 0, 0, 0, false},
[KEY_KP_6] = {'6', '6', 0, 0, 0, false},
[KEY_KP_7] = {'7', '7', 0, 0, 0, false},
[KEY_KP_8] = {'8', '8', 0, 0, 0, false},
[KEY_KP_9] = {'9', '9', 0, 0, 0, false},
}
};
// AZERTY LAYOUT FR
static const keyboard_layout_t layout_azerty = {
"AZERTY (FR)",
.entries = {
[KEY_1] = { '&', '1', 0, 0, 0, false },
[KEY_2] = { 0x00E9, '2', '~', 0, 0, false }, // é / 2 / ~
[KEY_3] = { '"', '3', '#', 0, 0, false },
[KEY_4] = { '\'', '4', '{', 0, 0, false },
[KEY_5] = { '(', '5', '[', 0, 0, false },
[KEY_6] = { '-', '6', '|', 0, 0, false },
[KEY_7] = { 0x00E8, '7', '`', 0, 0, false }, // è / 7 / `
[KEY_8] = { '_', '8', '\\', 0, 0, false },
[KEY_9] = { 0x00E7, '9', '^', 0, 0, false }, // ç / 9 / ^
[KEY_0] = { 0x00E0, '0', '@', 0, 0, false }, // à / 0 / @
[KEY_MINUS] = { ')', 0x00B0, ']', 0, 0, false }, // ) / °
[KEY_EQUAL] = { '=', '+', '}', 0, 0, false },
[KEY_Q] = { 'a', 'A', 0, 0, 0, true },
[KEY_W] = { 'z', 'Z', 0, 0, 0, true },
[KEY_E] = { 'e', 'E', 0x20AC, 0, 0, true }, // €
[KEY_R] = { 'r', 'R', 0, 0, 0, true },
[KEY_T] = { 't', 'T', 0, 0, 0, true },
[KEY_Y] = { 'y', 'Y', 0, 0, 0, true },
[KEY_U] = { 'u', 'U', 0, 0, 0, true },
[KEY_I] = { 'i', 'I', 0, 0, 0, true },
[KEY_O] = { 'o', 'O', 0, 0, 0, true },
[KEY_P] = { 'p', 'P', 0, 0, 0, true },
[KEY_LBRACKET] = { '^', 0x00A8, 0, 0, DEAD_NORMAL | DEAD_SHIFT, false }, // ^ / ¨
[KEY_RBRACKET] = { '$', 0x00A3, 0, 0, 0, false }, // £
[KEY_A] = { 'q', 'Q', 0, 0, 0, true },
[KEY_S] = { 's', 'S', 0, 0, 0, true },
[KEY_D] = { 'd', 'D', 0, 0, 0, true },
[KEY_F] = { 'f', 'F', 0, 0, 0, true },
[KEY_G] = { 'g', 'G', 0, 0, 0, true },
[KEY_H] = { 'h', 'H', 0, 0, 0, true },
[KEY_J] = { 'j', 'J', 0, 0, 0, true },
[KEY_K] = { 'k', 'K', 0, 0, 0, true },
[KEY_L] = { 'l', 'L', 0, 0, 0, true },
[KEY_SEMICOLON] = { 'm', 'M', 0, 0, 0, true },
[KEY_APOSTROPHE] = { 0x00F9, '%', 0, 0, 0, false }, // ù / %
[KEY_GRAVE] = { 0x00B2, 0, 0, 0, 0, false }, // ²
[KEY_BACKSLASH] = { '*', 0x00B5, 0, 0, 0, false }, // * / µ
[KEY_Z] = { 'w', 'W', 0, 0, 0, true },
[KEY_X] = { 'x', 'X', 0, 0, 0, true },
[KEY_C] = { 'c', 'C', 0, 0, 0, true },
[KEY_V] = { 'v', 'V', 0, 0, 0, true },
[KEY_B] = { 'b', 'B', 0, 0, 0, true },
[KEY_N] = { 'n', 'N', 0, 0, 0, true },
[KEY_M] = { ',', '?', 0, 0, 0, false },
[KEY_COMMA] = { ';', '.', 0, 0, 0, false },
[KEY_DOT] = { ':', '/', 0, 0, 0, false },
[KEY_SLASH] = { '!', 0x00A7, 0, 0, 0, false },
[KEY_SPACE] = { ' ', ' ', 0, 0, 0, false },
[KEY_KP_SLASH] = {'/', '/', 0, 0, 0, false},
[KEY_KP_STAR] = {'*', '*', 0, 0, 0, false},
[KEY_KP_MINUS] = {'-', '-', 0, 0, 0, false},
[KEY_KP_PLUS] = {'+', '+', 0, 0, 0, false},
[KEY_KP_DOT] = {'.', '.', 0, 0, 0, false},
[KEY_KP_0] = {'0', '0', 0, 0, 0, false},
[KEY_KP_1] = {'1', '1', 0, 0, 0, false},
[KEY_KP_2] = {'2', '2', 0, 0, 0, false},
[KEY_KP_3] = {'3', '3', 0, 0, 0, false},
[KEY_KP_4] = {'4', '4', 0, 0, 0, false},
[KEY_KP_5] = {'5', '5', 0, 0, 0, false},
[KEY_KP_6] = {'6', '6', 0, 0, 0, false},
[KEY_KP_7] = {'7', '7', 0, 0, 0, false},
[KEY_KP_8] = {'8', '8', 0, 0, 0, false},
[KEY_KP_9] = {'9', '9', 0, 0, 0, false},
}
};
static const keyboard_layout_t layout_qwertz = {
"QWERTZ (DE)",
.entries = {
[KEY_1] = { '1', '!', 0, 0, 0, false },
[KEY_2] = { '2', '"', 0x00B2, 0, 0, false }, // ²
[KEY_3] = { '3', 0x00A7, 0x00B3, 0, 0, false }, // § ³
[KEY_4] = { '4', '$', 0, 0, 0, false },
[KEY_5] = { '5', '%', 0, 0, 0, false },
[KEY_6] = { '6', '&', 0, 0, 0, false },
[KEY_7] = { '7', '/', '{', 0, 0, false },
[KEY_8] = { '8', '(', '[', 0, 0, false },
[KEY_9] = { '9', ')', ']', 0, 0, false },
[KEY_0] = { '0', '=', '}', 0, 0, false },
[KEY_MINUS] = { 0x00DF, '?', '\\', 0, 0, false }, // ß
[KEY_EQUAL] = { 0x00B4, '`', 0, 0, DEAD_NORMAL, false }, // ´ dead
[KEY_Q] = { 'q', 'Q', '@', 0, 0, true },
[KEY_W] = { 'w', 'W', 0, 0, 0, true },
[KEY_E] = { 'e', 'E', 0x20AC, 0, 0, true }, // €
[KEY_R] = { 'r', 'R', 0, 0, 0, true },
[KEY_T] = { 't', 'T', 0, 0, 0, true },
[KEY_Y] = { 'z', 'Z', 0, 0, 0, true },
[KEY_U] = { 'u', 'U', 0, 0, 0, true },
[KEY_I] = { 'i', 'I', 0, 0, 0, true },
[KEY_O] = { 'o', 'O', 0, 0, 0, true },
[KEY_P] = { 'p', 'P', 0, 0, 0, true },
[KEY_LBRACKET] = { 0x00FC, 0x00DC, 0, 0, 0, true }, // ü
[KEY_RBRACKET] = { '+', '*', '~', 0, 0, false },
[KEY_A] = { 'a', 'A', 0, 0, 0, true },
[KEY_S] = { 's', 'S', 0, 0, 0, true },
[KEY_D] = { 'd', 'D', 0, 0, 0, true },
[KEY_F] = { 'f', 'F', 0, 0, 0, true },
[KEY_G] = { 'g', 'G', 0, 0, 0, true },
[KEY_H] = { 'h', 'H', 0, 0, 0, true },
[KEY_J] = { 'j', 'J', 0, 0, 0, true },
[KEY_K] = { 'k', 'K', 0, 0, 0, true },
[KEY_L] = { 'l', 'L', 0, 0, 0, true },
[KEY_SEMICOLON] = { 0x00F6, 0x00D6, 0, 0, 0, true }, // ö
[KEY_APOSTROPHE] = { 0x00E4, 0x00C4, 0, 0, 0, true }, // ä
[KEY_GRAVE] = { '^', 0x00B0, 0, 0, DEAD_NORMAL, false }, // ^ dead
[KEY_BACKSLASH] = { '#', '\'', 0, 0, 0, false },
[KEY_Z] = { 'y', 'Y', 0, 0, 0, true },
[KEY_X] = { 'x', 'X', 0, 0, 0, true },
[KEY_C] = { 'c', 'C', 0, 0, 0, true },
[KEY_V] = { 'v', 'V', 0, 0, 0, true },
[KEY_B] = { 'b', 'B', 0, 0, 0, true },
[KEY_N] = { 'n', 'N', 0, 0, 0, true },
[KEY_M] = { 'm', 'M', 0, 0, 0, true },
[KEY_COMMA] = { ',', ';', 0, 0, 0, false },
[KEY_DOT] = { '.', ':', 0, 0, 0, false },
[KEY_SLASH] = { '-', '_', 0, 0, 0, false },
[KEY_SPACE] = { ' ', ' ', 0, 0, 0, false },
[KEY_KP_SLASH] = {'/', '/', 0, 0, 0, false},
[KEY_KP_STAR] = {'*', '*', 0, 0, 0, false},
[KEY_KP_MINUS] = {'-', '-', 0, 0, 0, false},
[KEY_KP_PLUS] = {'+', '+', 0, 0, 0, false},
[KEY_KP_DOT] = {'.', '.', 0, 0, 0, false},
[KEY_KP_0] = {'0', '0', 0, 0, 0, false},
[KEY_KP_1] = {'1', '1', 0, 0, 0, false},
[KEY_KP_2] = {'2', '2', 0, 0, 0, false},
[KEY_KP_3] = {'3', '3', 0, 0, 0, false},
[KEY_KP_4] = {'4', '4', 0, 0, 0, false},
[KEY_KP_5] = {'5', '5', 0, 0, 0, false},
[KEY_KP_6] = {'6', '6', 0, 0, 0, false},
[KEY_KP_7] = {'7', '7', 0, 0, 0, false},
[KEY_KP_8] = {'8', '8', 0, 0, 0, false},
[KEY_KP_9] = {'9', '9', 0, 0, 0, false},
}
};
static const keyboard_layout_t layout_dvorak = {
"DVORAK",
.entries = {
[KEY_1] = { '1', '!', 0, 0, 0, false },
[KEY_2] = { '2', '@', 0, 0, 0, false },
[KEY_3] = { '3', '#', 0, 0, 0, false },
[KEY_4] = { '4', '$', 0, 0, 0, false },
[KEY_5] = { '5', '%', 0, 0, 0, false },
[KEY_6] = { '6', '^', 0, 0, 0, false },
[KEY_7] = { '7', '&', 0, 0, 0, false },
[KEY_8] = { '8', '*', 0, 0, 0, false },
[KEY_9] = { '9', '(', 0, 0, 0, false },
[KEY_0] = { '0', ')', 0, 0, 0, false },
[KEY_MINUS] = { '[', '{', 0, 0, 0, false },
[KEY_EQUAL] = { ']', '}', 0, 0, 0, false },
[KEY_Q] = { '\'', '"', 0, 0, 0, false },
[KEY_W] = { ',', '<', 0, 0, 0, false },
[KEY_E] = { '.', '>', 0, 0, 0, false },
[KEY_R] = { 'p', 'P', 0, 0, 0, true },
[KEY_T] = { 'y', 'Y', 0, 0, 0, true },
[KEY_Y] = { 'f', 'F', 0, 0, 0, true },
[KEY_U] = { 'g', 'G', 0, 0, 0, true },
[KEY_I] = { 'c', 'C', 0, 0, 0, true },
[KEY_O] = { 'r', 'R', 0, 0, 0, true },
[KEY_P] = { 'l', 'L', 0, 0, 0, true },
[KEY_LBRACKET] = { '/', '?', 0, 0, 0, false },
[KEY_RBRACKET] = { '=', '+', 0, 0, 0, false },
[KEY_A] = { 'a', 'A', 0, 0, 0, true },
[KEY_S] = { 'o', 'O', 0, 0, 0, true },
[KEY_D] = { 'e', 'E', 0, 0, 0, true },
[KEY_F] = { 'u', 'U', 0, 0, 0, true },
[KEY_G] = { 'i', 'I', 0, 0, 0, true },
[KEY_H] = { 'd', 'D', 0, 0, 0, true },
[KEY_J] = { 'h', 'H', 0, 0, 0, true },
[KEY_K] = { 't', 'T', 0, 0, 0, true },
[KEY_L] = { 'n', 'N', 0, 0, 0, true },
[KEY_SEMICOLON] = { 's', 'S', 0, 0, 0, true },
[KEY_APOSTROPHE] = { '-', '_', 0, 0, 0, false },
[KEY_GRAVE] = { '`', '~', 0, 0, 0, false },
[KEY_BACKSLASH] = { '\\', '|', 0, 0, 0, false },
[KEY_Z] = { ';', ':', 0, 0, 0, false },
[KEY_X] = { 'q', 'Q', 0, 0, 0, true },
[KEY_C] = { 'j', 'J', 0, 0, 0, true },
[KEY_V] = { 'k', 'K', 0, 0, 0, true },
[KEY_B] = { 'x', 'X', 0, 0, 0, true },
[KEY_N] = { 'b', 'B', 0, 0, 0, true },
[KEY_M] = { 'm', 'M', 0, 0, 0, true },
[KEY_COMMA] = { 'w', 'W', 0, 0, 0, true },
[KEY_DOT] = { 'v', 'V', 0, 0, 0, true },
[KEY_SLASH] = { 'z', 'Z', 0, 0, 0, true },
[KEY_SPACE] = { ' ', ' ', 0, 0, 0, false },
}
};
static const keyboard_layout_t *g_layouts[] = {
&layout_qwerty,
&layout_azerty,
&layout_qwertz,
&layout_dvorak
};
static keymap_id_t g_current = KEYMAP_QWERTY;
static uint32_t g_active_dead = 0;
static const compose_entry_t g_compose_table[] = {
{ '^', 'a', 0x00E2 }, { '^', 'e', 0x00EA }, { '^', 'i', 0x00EE }, { '^', 'o', 0x00F4 }, { '^', 'u', 0x00FB },
{ '^', 'A', 0x00C2 }, { '^', 'E', 0x00CA }, { '^', 'I', 0x00CE }, { '^', 'O', 0x00D4 }, { '^', 'U', 0x00DB },
{ 0x00A8, 'a', 0x00E4 }, { 0x00A8, 'e', 0x00EB }, { 0x00A8, 'i', 0x00EF }, { 0x00A8, 'o', 0x00F6 }, { 0x00A8, 'u', 0x00FC }, { 0x00A8, 'y', 0x00FF },
{ 0x00A8, 'A', 0x00C4 }, { 0x00A8, 'E', 0x00CB }, { 0x00A8, 'I', 0x00CF }, { 0x00A8, 'O', 0x00D6 }, { 0x00A8, 'U', 0x00DC },
{ 0x00B4, 'a', 0x00E1 }, { 0x00B4, 'e', 0x00E9 }, { 0x00B4, 'i', 0x00ED }, { 0x00B4, 'o', 0x00F3 }, { 0x00B4, 'u', 0x00FA },
{ 0x00B4, 'A', 0x00C1 }, { 0x00B4, 'E', 0x00C9 }, { 0x00B4, 'I', 0x00CD }, { 0x00B4, 'O', 0x00D3 }, { 0x00B4, 'U', 0x00DA },
{ '`', 'a', 0x00E0 }, { '`', 'e', 0x00E8 }, { '`', 'i', 0x00EC }, { '`', 'o', 0x00F2 }, { '`', 'u', 0x00F9 },
{ 0, 0, 0 }
};
void keymap_init(void) {
g_current = KEYMAP_QWERTY;
}
void keymap_set_current(keymap_id_t id) {
if ((int)id < 0 || (int)id >= keymap_get_count()) return;
g_current = id;
}
keymap_id_t keymap_get_current(void) {
return g_current;
}
const char *keymap_get_name(keymap_id_t id) {
if ((int)id < 0 || (int)id >= keymap_get_count()) return "Unknown";
return g_layouts[id]->name;
}
int keymap_get_count(void) {
return (int)(sizeof(g_layouts) / sizeof(g_layouts[0]));
}
static keymap_result_t make_result(uint32_t cp, bool dead) {
keymap_result_t r;
r.codepoint = cp;
r.is_text = (cp != 0);
r.is_dead = dead;
return r;
}
keymap_result_t keymap_translate_keycode(uint16_t keycode, uint32_t mods) {
if (keycode <= 0 || keycode >= KEY_MAX)
return make_result(0, false);
const keymap_entry_t *e = &g_layouts[g_current]->entries[keycode];
if (!e->normal && !e->shift && !e->altgr && !e->shift_altgr)
return make_result(0, false);
bool shift = (mods & KB_MOD_SHIFT) != 0;
bool caps = (mods & KB_MOD_CAPS) != 0;
bool altgr = (mods & KB_MOD_ALTGR) != 0;
uint32_t cp = 0;
uint8_t dead_mask_bit = 0;
if (altgr) {
if (shift) {
cp = e->shift_altgr;
dead_mask_bit = DEAD_SHIFT_ALTGR;
} else {
cp = e->altgr;
dead_mask_bit = DEAD_ALTGR;
}
} else if (e->alpha) {
bool uppercase = shift ^ caps;
cp = uppercase ? e->shift : e->normal;
dead_mask_bit = uppercase ? DEAD_SHIFT : DEAD_NORMAL;
} else {
cp = shift ? e->shift : e->normal;
dead_mask_bit = shift ? DEAD_SHIFT : DEAD_NORMAL;
}
bool is_dead = (e->dead_mask & dead_mask_bit) != 0;
if (is_dead && cp != 0) {
g_active_dead = cp;
return make_result(0, true);
}
if (g_active_dead && cp != 0) {
uint32_t combined = keymap_compose(g_active_dead, cp);
g_active_dead = 0;
if (combined != 0) {
return make_result(combined, false);
}
return make_result(cp, false);
}
return make_result(cp, false);
}
uint32_t keymap_compose(uint32_t dead_codepoint, uint32_t base_codepoint) {
for (int i = 0; g_compose_table[i].dead != 0; i++) {
if (g_compose_table[i].dead == dead_codepoint &&
g_compose_table[i].base == base_codepoint) {
return g_compose_table[i].result;
}
}
return 0;
}
int keymap_legacy_key(uint16_t keycode, uint32_t codepoint) {
if (codepoint != 0 && codepoint < 128) return (int)codepoint;
switch (keycode) {
case KEY_ESC: return 27;
case KEY_BACKSPACE: return '\b';
case KEY_TAB: return '\t';
case KEY_ENTER:
case KEY_KP_ENTER: return '\n';
case KEY_ARROW_UP: return 17;
case KEY_ARROW_DOWN: return 18;
case KEY_ARROW_LEFT: return 19;
case KEY_ARROW_RIGHT: return 20;
case KEY_RIGHT_ALT: return 22; // for compat w/ doom
case KEY_CAPS_LOCK: return 23; // same here
case KEY_DELETE: return 127;
default: return 0;
}
}

42
src/input/keymap.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef KEYMAP_H
#define KEYMAP_H
#include <stdint.h>
#include <stdbool.h>
#include "keycodes.h"
typedef enum {
KEYMAP_QWERTY = 0,
KEYMAP_AZERTY = 1,
KEYMAP_QWERTZ = 2,
KEYMAP_DVORAK = 3,
} keymap_id_t;
typedef struct {
uint32_t normal;
uint32_t shift;
uint32_t altgr;
uint32_t shift_altgr;
uint8_t dead_mask; // bit0 normal, bit1 shift, bit2 altgr, bit3 shift_altgr
bool alpha;
} keymap_entry_t;
typedef struct {
uint32_t codepoint;
bool is_text;
bool is_dead;
} keymap_result_t;
void keymap_init(void);
void keymap_set_current(keymap_id_t id);
keymap_id_t keymap_get_current(void);
const char *keymap_get_name(keymap_id_t id);
int keymap_get_count(void);
keymap_result_t keymap_translate_keycode(uint16_t keycode, uint32_t mods);
uint32_t keymap_compose(uint32_t dead_codepoint, uint32_t base_codepoint);
// compat legacy for existing apps
int keymap_legacy_key(uint16_t keycode, uint32_t codepoint);
#endif