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

View File

@@ -32,6 +32,7 @@
#include "sys/kernel_subsystem.h"
#include "sys/module_manager.h"
#include "sys/bootfs_state.h"
#include "input/keymap.h"
extern void sysfs_init_subsystems(void);
@@ -314,6 +315,7 @@ void kmain(void) {
fat32_mkdir("/Library/images/gif");
fat32_mkdir("/Library/Fonts");
fat32_mkdir("/Library/DOOM");
fat32_mkdir("/Library/conf");
fat32_mkdir("/Library/bsh");
fat32_mkdir("/docs");
fat32_mkdir("/root");
@@ -430,6 +432,9 @@ void kmain(void) {
ps2_init();
asm("sti");
keymap_init();
serial_write("[INIT] Keymap initialized");
lapic_init();
if (smp_request.response != NULL) {

View File

@@ -8,6 +8,8 @@
#include "lapic.h"
#include "smp.h"
#include <stdbool.h>
#include "input/keyboard.h"
#include "input/keymap.h"
extern void serial_print(const char *s);
extern void serial_print_hex(uint64_t n);
@@ -20,28 +22,23 @@ uint64_t timer_handler(registers_t *regs) {
kernel_ticks++;
wm_timer_tick();
network_process_frames();
extern void k_beep_process(void);
k_beep_process();
}
outb(0x20, 0x20);
outb(0x20, 0x20);
extern uint64_t process_schedule(uint64_t current_rsp);
uint64_t new_rsp = process_schedule((uint64_t)regs);
if (smp_cpu_count() > 1) {
lapic_send_ipi_all();
}
return new_rsp;
}
// --- Keyboard ---
static bool shift_pressed = false;
static bool caps_lock_on = false;
bool ps2_ctrl_pressed = false;
static bool extended_scancode = false;
static void ps2_kbd_wait_write(void) {
uint32_t timeout = 100000;
while (timeout--) {
@@ -51,95 +48,29 @@ static void ps2_kbd_wait_write(void) {
static void ps2_update_leds(void) {
uint8_t led_status = 0;
if (caps_lock_on) led_status |= 4;
uint32_t mods = keyboard_get_modifiers();
if (mods & KB_MOD_CAPS) led_status |= 4;
if (mods & KB_MOD_NUM) led_status |= 2;
if (mods & KB_MOD_SCROLL) led_status |= 1;
ps2_kbd_wait_write();
outb(0x60, 0xED);
ps2_kbd_wait_write();
outb(0x60, led_status);
}
static char scancode_map[128] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
21, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*',
22, ' ', 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static char scancode_map_shift[128] = {
0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
21, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0,
'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*',
22, ' ', 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
uint64_t keyboard_handler(registers_t *regs) {
uint8_t scancode = inb(0x60);
if (scancode == 0xE0) {
extended_scancode = true;
outb(0x20, 0x20);
return (uint64_t)regs;
}
if (scancode == 0x1D) {
ps2_ctrl_pressed = true;
extended_scancode = false;
} else if (scancode == 0x9D) {
ps2_ctrl_pressed = false;
extended_scancode = false;
}
if (scancode == 0x2A || scancode == 0x36) { // Shift Down
shift_pressed = true;
} else if (scancode == 0xAA || scancode == 0xB6) { // Shift Up
shift_pressed = false;
} else if (scancode == 0x3A) { // Caps Lock Down
caps_lock_on = !caps_lock_on;
ps2_update_leds();
} else if (!(scancode & 0x80)) { // Key Press
if (extended_scancode) {
extended_scancode = false;
switch (scancode) {
case 0x48: wm_handle_key(17, true); break; // Up arrow
case 0x50: wm_handle_key(18, true); break; // Down arrow
case 0x4B: wm_handle_key(19, true); break; // Left arrow
case 0x4D: wm_handle_key(20, true); break; // Right arrow
}
} else {
char c = shift_pressed ? scancode_map_shift[scancode] : scancode_map[scancode];
if (c) {
if (caps_lock_on) {
if (c >= 'a' && c <= 'z') c -= 32;
else if (c >= 'A' && c <= 'Z') c += 32;
}
wm_handle_key(c, true);
}
}
} else if (scancode & 0x80) { // Key release
if (extended_scancode) {
extended_scancode = false;
switch (scancode & 0x7F) { // Strip the release bit
case 0x48: wm_handle_key(17, false); break; // Up arrow
case 0x50: wm_handle_key(18, false); break; // Down arrow
case 0x4B: wm_handle_key(19, false); break; // Left arrow
case 0x4D: wm_handle_key(20, false); break; // Right arrow
}
} else {
uint8_t base_scancode = scancode & 0x7F;
char c = shift_pressed ? scancode_map_shift[base_scancode] : scancode_map[base_scancode];
if (c) {
if (caps_lock_on) {
if (c >= 'a' && c <= 'z') c -= 32;
else if (c >= 'A' && c <= 'Z') c += 32;
}
wm_handle_key(c, false);
}
keyboard_event_t ev;
if (keyboard_handle_set1_scancode(scancode, &ev)) {
// Update LEDs if a lock key state changed
if (ev.keycode == KEY_CAPS_LOCK || ev.keycode == KEY_NUM_LOCK || ev.keycode == KEY_SCROLL_LOCK) {
if (ev.pressed) ps2_update_leds();
}
wm_handle_key_event(ev.keycode, ev.codepoint, ev.mods, ev.pressed);
}
outb(0x20, 0x20); // EOI
@@ -178,11 +109,11 @@ uint8_t mouse_read(void) {
void mouse_init(void) {
uint8_t status;
// Enable Aux Device
mouse_wait(0);
outb(0x64, 0xA8);
// Enable Interrupts
mouse_wait(0);
outb(0x64, 0x20);
@@ -192,7 +123,7 @@ void mouse_init(void) {
outb(0x64, 0x60);
mouse_wait(0);
outb(0x60, status);
// Set Defaults
mouse_write(0xF6);
mouse_read();
@@ -201,12 +132,12 @@ void mouse_init(void) {
mouse_write(0xF3); mouse_read(); mouse_write(200); mouse_read();
mouse_write(0xF3); mouse_read(); mouse_write(100); mouse_read();
mouse_write(0xF3); mouse_read(); mouse_write(80); mouse_read();
mouse_write(0xF2);
mouse_read();
uint8_t id = mouse_read();
if (id == 3) mouse_has_wheel = true;
// Enable Streaming
mouse_write(0xF4);
mouse_read();
@@ -217,11 +148,11 @@ uint64_t mouse_handler(registers_t *regs) {
if (!(status & 0x20)) {
outb(0x20, 0x20);
outb(0xA0, 0x20);
return (uint64_t)regs;
return (uint64_t)regs;
}
uint8_t b = inb(0x60);
if (mouse_cycle == 0) {
if ((b & 0x08) == 0) {
// Out of sync
@@ -257,9 +188,12 @@ uint64_t mouse_handler(registers_t *regs) {
}
void ps2_init(void) {
keymap_init();
keyboard_init();
mouse_init();
ps2_update_leds();
}
bool ps2_shift_pressed(void) {
return shift_pressed;
}
return keyboard_shift_pressed();
}

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

View File

@@ -11,15 +11,8 @@
#include "ps2.h"
#include "kutils.h"
#include "io.h"
// --- Scancode Map (Set 1) ---
static char vm_scancode_map[128] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*',
0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
#include "input/keymap.h"
#include "input/keyboard.h"
// VM State
static int stack[VM_STACK_SIZE];
@@ -97,8 +90,22 @@ static void vm_syscall(int id) {
push(0);
break;
case VM_SYS_PRINT_CHAR: {
char c = (char)pop();
char s[2] = {c, 0};
uint32_t cp = (uint32_t)pop();
char s[5] = {0};
if (cp < 0x80) {
s[0] = cp;
} else if (cp < 0x800) {
s[0] = 0xC0 | (cp >> 6);
s[1] = 0x80 | (cp & 0x3F);
} else if (cp < 0x10000) {
s[0] = 0xE0 | (cp >> 12);
s[1] = 0x80 | ((cp >> 6) & 0x3F);
s[2] = 0x80 | (cp & 0x3F);
} else {
s[0] = '?';
}
cmd_write(s);
push(0);
break;
@@ -121,15 +128,32 @@ static void vm_syscall(int id) {
break;
case VM_SYS_GETCHAR: {
int c = 0;
// Blocking read for a valid key press
bool ext = false;
// Wait for a key press and return the ASCII code
while (1) {
if ((inb(0x64) & 1)) { // Data available
if ((inb(0x64) & 1)) { // Data available in keyboard controller
uint8_t sc = inb(0x60);
if (!(sc & 0x80)) { // Key press
if (sc < 128) {
c = vm_scancode_map[sc];
if (c) break;
if (sc == 0xE0) { // Extended scancode prefix
ext = true;
continue;
}
if (!(sc & 0x80)) { // Key press (not release)
uint16_t keycode = keyboard_keycode_from_set1((uint8_t)(sc & 0x7F), ext); // Get keycode and reset
ext = false;
if (keycode != KEY_NONE) {
uint32_t mods = keyboard_get_modifiers();
keymap_result_t r = keymap_translate_keycode(keycode, mods);
// Only return valid text characters, ignore modifiers and dead keys
if (r.is_text && !r.is_dead && r.codepoint >= 32 && r.codepoint != 127) {
c = (int)r.codepoint;
break;
}
}
} else {
ext = false;
}
}
}
@@ -545,7 +569,26 @@ int vm_exec(const uint8_t *code, int code_size) {
case OP_MUL: push(pop() * pop()); break;
case OP_DIV: { int b=pop(); int a=pop(); push(b==0?0:a/b); } break;
case OP_PRINT: cmd_write_int(pop()); cmd_write("\n"); break;
case OP_PRITC: { char c=(char)pop(); char s[2]={c,0}; cmd_write(s); } break;
case OP_PRITC: {
uint32_t cp = (uint32_t)pop();
char s[5] = {0};
if (cp < 0x80) {
s[0] = (char)cp;
} else if (cp < 0x800) {
s[0] = (char)(0xC0 | (cp >> 6));
s[1] = (char)(0x80 | (cp & 0x3F));
} else if (cp < 0x10000) {
s[0] = (char)(0xE0 | (cp >> 12));
s[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
s[2] = (char)(0x80 | (cp & 0x3F));
} else {
s[0] = '?';
}
cmd_write(s);
break;
}
case OP_JMP: {
int addr = 0;
addr |= memory[pc++];
@@ -601,4 +644,4 @@ int vm_exec(const uint8_t *code, int code_size) {
}
wm_custom_paint_hook = NULL;
return 0;
}
}

View File

@@ -22,14 +22,17 @@
#include "tty.h"
#include "font_manager.h"
#include "graphics.h"
#include "input/keycodes.h"
#include "input/keymap.h"
#include "app_metadata.h"
extern bool ps2_ctrl_pressed;
#define SPAWN_FLAG_TERMINAL 0x1
#define SPAWN_FLAG_INHERIT_TTY 0x2
#define SPAWN_FLAG_TTY_ID 0x4
#define SYSTEM_CMD_SET_KEYBOARD_LAYOUT 49
#define SYSTEM_CMD_GET_KEYBOARD_LAYOUT 51
// Read MSR
static inline uint64_t rdmsr(uint32_t msr) {
uint32_t low, high;
@@ -158,10 +161,17 @@ void syscall_send_mouse_up_event(Window *win, int x, int y) {
user_window_mouse_up(win, x, y);
}
static void user_window_key(Window *win, char c, bool pressed) {
static void user_window_key(Window *win, int legacy, uint16_t keycode, uint32_t codepoint, uint32_t mods, bool pressed) {
process_t *proc = process_get_by_ui_window(win);
if (!proc) return;
gui_event_t ev = { .type = pressed ? GUI_EVENT_KEY : GUI_EVENT_KEYUP, .arg1 = (int)c, .arg3 = (int)ps2_ctrl_pressed };
gui_event_t ev = {
.type = pressed ? GUI_EVENT_KEY : GUI_EVENT_KEYUP,
.arg1 = legacy,
.arg2 = (int)keycode,
.arg3 = (int)mods,
.arg4 = (int)codepoint
};
process_push_gui_event(proc, &ev);
}
@@ -2105,6 +2115,16 @@ static uint64_t sys_cmd_get_elf_primary_image(const syscall_args_t *args) {
return app_metadata_get_primary_image(path, out_path, out_size) ? 1 : 0;
}
static uint64_t sys_cmd_set_keyboard_layout(const syscall_args_t *args) {
keymap_set_current((keymap_id_t)args->arg2);
return 0;
}
static uint64_t sys_cmd_get_keyboard_layout(const syscall_args_t *args) {
(void)args;
return (uint64_t)keymap_get_current();
}
#define SYS_CMD_TABLE_SIZE 78
static const syscall_handler_fn sys_cmd_table[SYS_CMD_TABLE_SIZE] = {
[SYSTEM_CMD_SET_BG_COLOR] = sys_cmd_set_bg_color,
@@ -2152,6 +2172,8 @@ static const syscall_handler_fn sys_cmd_table[SYS_CMD_TABLE_SIZE] = {
[SYSTEM_CMD_SET_RESOLUTION] = sys_cmd_set_resolution,
[SYSTEM_CMD_NETWORK_GET_NIC_NAME] = sys_cmd_network_get_nic_name,
[SYSTEM_CMD_PARALLEL_RUN] = sys_cmd_parallel_run,
[SYSTEM_CMD_SET_KEYBOARD_LAYOUT] = sys_cmd_set_keyboard_layout,
[SYSTEM_CMD_GET_KEYBOARD_LAYOUT] = sys_cmd_get_keyboard_layout,
[SYSTEM_CMD_TTY_CREATE] = sys_cmd_tty_create,
[SYSTEM_CMD_TTY_READ_OUT] = sys_cmd_tty_read_out,
[SYSTEM_CMD_TTY_WRITE_IN] = sys_cmd_tty_write_in,

View File

@@ -100,7 +100,9 @@ typedef struct {
#define SYSTEM_CMD_SLEEP 46
#define SYSTEM_CMD_SET_RESOLUTION 47
#define SYSTEM_CMD_NETWORK_GET_NIC_NAME 48
#define SYSTEM_CMD_SET_KEYBOARD_LAYOUT 49
#define SYSTEM_CMD_PARALLEL_RUN 50
#define SYSTEM_CMD_GET_KEYBOARD_LAYOUT 51
#define SYSTEM_CMD_TTY_CREATE 60
#define SYSTEM_CMD_TTY_READ_OUT 61
#define SYSTEM_CMD_TTY_WRITE_IN 62

View File

@@ -7,6 +7,8 @@
#include "libc/libui.h"
#include "libc/stdlib.h"
#include "libc/syscall_user.h"
#include "libc/utf-8.h"
#include "libc/input.h"
#include <stddef.h>
#define COLOR_NOTEPAD_BG 0xFFFFFFFF
@@ -23,16 +25,16 @@ static void notepad_ensure_cursor_visible(int h) {
if (fh < 8) fh = 8;
int visible_lines = (h - 10) / fh;
if (visible_lines < 1) visible_lines = 1;
int cursor_line = 0;
for (int i = 0; i < cursor_pos && i < buf_len; i++) {
if (buffer[i] == '\n') cursor_line++;
}
if (cursor_line < notepad_scroll_line) {
notepad_scroll_line = cursor_line;
}
if (cursor_line >= notepad_scroll_line + visible_lines) {
notepad_scroll_line = cursor_line - visible_lines + 1;
}
@@ -51,7 +53,7 @@ static void notepad_load_state() {
}
static void notepad_save_state() {
// Ensure dir exists
// Ensure dir exist
sys_mkdir("/tmp");
int fd = sys_open("/tmp/notepad_state.txt", "w");
if (fd >= 0) {
@@ -66,21 +68,28 @@ static void notepad_paint(ui_window_t win, int w, int h) {
int fh = (int)ui_get_font_height();
if (fh < 8) fh = 8;
int visual_line = 0;
int visual_line = 0;
int current_x = 4;
int current_y = 4;
int window_right = w - 8;
for (int i = 0; i < buf_len; i++) {
int window_right = w - 8;
for (int i = 0; i < buf_len; ) {
int adv;
uint32_t cp = text_decode_utf8(&buffer[i], &adv);
if (visual_line < notepad_scroll_line) {
if (buffer[i] == '\n') {
if (cp == '\n') {
visual_line++;
current_x = 4;
current_y = 4;
} else {
char ch[2] = {buffer[i], 0};
int cw = (int)ui_get_string_width(ch);
char out[5];
int len = text_encode_utf8(cp, out);
out[len] = 0;
int cw = (int)ui_get_string_width(out);
if (cw < 1) cw = 8;
if (current_x + cw >= window_right) {
visual_line++;
current_x = 4;
@@ -88,50 +97,61 @@ static void notepad_paint(ui_window_t win, int w, int h) {
}
current_x += cw;
}
i += adv;
continue;
}
if (visual_line >= notepad_scroll_line + (h - 8) / fh) {
break;
}
if (buffer[i] == '\n') {
if (visual_line >= notepad_scroll_line + (h - 8) / fh) break;
if (cp == '\n') {
current_x = 4;
current_y += fh;
visual_line++;
} else {
char ch[2] = {buffer[i], 0};
int cw = (int)ui_get_string_width(ch);
char out[5];
int len = text_encode_utf8(cp, out);
out[len] = 0;
int cw = (int)ui_get_string_width(out);
if (cw < 1) cw = 8;
if (current_x + cw >= window_right) {
current_x = 4;
current_y += fh;
visual_line++;
if (visual_line >= notepad_scroll_line + (h - 8) / fh) {
break;
}
if (visual_line >= notepad_scroll_line + (h - 8) / fh) break;
}
ui_draw_string(win, current_x, current_y, ch, COLOR_BLACK);
ui_draw_string(win, current_x, current_y, out, COLOR_BLACK);
current_x += cw;
}
i += adv;
}
// Cursor
// --- CURSOR ---
int cx = 4;
int cy = 4;
int c_visual_line = 0;
for (int i = 0; i < cursor_pos; i++) {
if (buffer[i] == '\n') {
for (int i = 0; i < cursor_pos; ) {
int adv;
uint32_t cp = text_decode_utf8(&buffer[i], &adv);
if (cp == '\n') {
cx = 4;
cy += fh;
c_visual_line++;
} else {
char ch[2] = {buffer[i], 0};
int cw = (int)ui_get_string_width(ch);
char out[5];
int len = text_encode_utf8(cp, out);
out[len] = 0;
int cw = (int)ui_get_string_width(out);
if (cw < 1) cw = 8;
if (cx + cw >= window_right) {
cx = 4;
cy += fh;
@@ -139,120 +159,96 @@ static void notepad_paint(ui_window_t win, int w, int h) {
}
cx += cw;
}
i += adv;
}
if (c_visual_line >= notepad_scroll_line &&
if (c_visual_line >= notepad_scroll_line &&
c_visual_line < notepad_scroll_line + (h - 8) / fh) {
ui_draw_rect(win, cx, cy, 2, fh - 2, COLOR_BLACK);
}
ui_mark_dirty(win, 0, 0, w, h);
}
static void notepad_key(ui_window_t win, int h, char c) {
if (c == 17) { // UP
if (cursor_pos > 0) {
int curr = cursor_pos;
int line_start = curr;
while (line_start > 0 && buffer[line_start - 1] != '\n') {
line_start--;
}
int col = curr - line_start;
static void notepad_key(ui_window_t win, int h, int legacy, uint32_t codepoint) {
(void)win;
if (line_start > 0) {
int prev_line_end = line_start - 1;
int prev_line_start = prev_line_end;
while (prev_line_start > 0 && buffer[prev_line_start - 1] != '\n') {
prev_line_start--;
}
int prev_line_len = prev_line_end - prev_line_start;
if (col > prev_line_len) col = prev_line_len;
cursor_pos = prev_line_start + col;
}
}
} else if (c == 18) { // DOWN
if (cursor_pos < buf_len) {
int curr = cursor_pos;
int line_start = curr;
while (line_start > 0 && buffer[line_start - 1] != '\n') {
line_start--;
}
int col = curr - line_start;
int next_line_start = curr;
while (next_line_start < buf_len && buffer[next_line_start] != '\n') {
next_line_start++;
}
if (next_line_start < buf_len) {
next_line_start++; // Skip newline
int next_line_end = next_line_start;
while (next_line_end < buf_len && buffer[next_line_end] != '\n') {
next_line_end++;
}
int next_line_len = next_line_end - next_line_start;
if (col > next_line_len) col = next_line_len;
cursor_pos = next_line_start + col;
} else {
cursor_pos = buf_len;
}
}
} else if (c == 19) { // LEFT
if (legacy == KEY_UP) { // UP
if (cursor_pos > 0) cursor_pos--;
} else if (c == 20) { // RIGHT
} else if (legacy == KEY_DOWN) { // DOWN
if (cursor_pos < buf_len) cursor_pos++;
} else if (c == '\b') { // Backspace
} else if (legacy == KEY_LEFT) { // LEFT
if (cursor_pos > 0)
cursor_pos = (int)(text_prev_utf8(buffer, &buffer[cursor_pos]) - buffer);
} else if (legacy == KEY_RIGHT) { // RIGHT
if (cursor_pos < buf_len)
cursor_pos = (int)(text_next_utf8(&buffer[cursor_pos]) - buffer);
} else if (legacy == '\b') {
if (cursor_pos > 0) {
for (int i = cursor_pos; i < buf_len; i++) {
buffer[i - 1] = buffer[i];
}
buf_len--;
cursor_pos--;
int prev = (int)(text_prev_utf8(buffer, &buffer[cursor_pos]) - buffer);
int len = cursor_pos - prev;
for (int i = cursor_pos; i < buf_len; i++)
buffer[i - len] = buffer[i];
buf_len -= len;
cursor_pos = prev;
buffer[buf_len] = 0;
}
} else if (c == '\n') { // Enter
if (buf_len < 1023) {
for (int i = buf_len; i > cursor_pos; i--) {
buffer[i] = buffer[i - 1];
}
buffer[cursor_pos] = c;
buf_len++;
cursor_pos++;
buffer[buf_len] = 0;
}
} else {
} else if (legacy == '\n') {
if (buf_len < NOTEPAD_BUF_SIZE - 1) {
for (int i = buf_len; i > cursor_pos; i--) {
for (int i = buf_len; i > cursor_pos; i--)
buffer[i] = buffer[i - 1];
}
buffer[cursor_pos] = c;
buffer[cursor_pos++] = '\n';
buf_len++;
cursor_pos++;
buffer[buf_len] = 0;
}
}
// Only insert printable characters (excluding DEL)
else if (codepoint >= 32 && codepoint != 127) {
char utf8[4];
int len = text_encode_utf8(codepoint, utf8);
if (len > 0 && buf_len + len < NOTEPAD_BUF_SIZE) {
for (int i = buf_len - 1; i >= cursor_pos; i--)
buffer[i + len] = buffer[i];
for (int i = 0; i < len; i++)
buffer[cursor_pos + i] = utf8[i];
buf_len += len;
cursor_pos += len;
buffer[buf_len] = 0;
}
}
notepad_ensure_cursor_visible(h);
}
int main(int argc, char **argv) {
sys_serial_write("Notepad: Starting userspace main...\n");
ui_window_t win = ui_window_create("Notepad", 100, 100, 400, 300);
if (win == 0) {
sys_serial_write("Notepad: Failed to create window!\n");
return 1;
}
sys_serial_write("Notepad: Window created successfully.\n");
notepad_load_state();
gui_event_t ev;
sys_serial_write("Notepad: Entering event loop...\n");
while (1) {
if (ui_get_event(win, &ev)) {
if (ev.type == GUI_EVENT_PAINT) {
notepad_paint(win, 400, 300);
} else if (ev.type == GUI_EVENT_KEY) {
notepad_key(win, 300, (char)ev.arg1);
notepad_key(win, 300, ev.arg1, (uint32_t)ev.arg4);
notepad_paint(win, 400, 300);
} else if (ev.type == GUI_EVENT_CLOSE) {
sys_serial_write("Notepad: CLOSE\n");
@@ -265,4 +261,4 @@ int main(int argc, char **argv) {
}
return 0;
}
}

View File

@@ -55,13 +55,14 @@ static widget_checkbox_t chk_snap;
static widget_checkbox_t chk_align;
static widget_dropdown_t drop_res;
static widget_dropdown_t drop_color;
static widget_dropdown_t drop_keyboard;
static widget_textbox_t tb_r, tb_g, tb_b;
static widget_textbox_t tb_ip, tb_dns;
static widget_button_t btn_apply, btn_back;
#define MAX_WALLPAPERS 10
static widget_button_t btn_main_wallpaper, btn_main_network, btn_main_desktop, btn_main_mouse, btn_main_fonts, btn_main_display;
static widget_button_t btn_main_wallpaper, btn_main_network, btn_main_desktop, btn_main_mouse, btn_main_fonts, btn_main_display, btn_main_keyboard;
static widget_button_t btn_wp_colors[6];
static widget_button_t btn_wp_patterns[2];
static widget_button_t btn_wp_apply;
@@ -99,7 +100,8 @@ enum settings_icon_id {
SETTINGS_ICON_MOUSE,
SETTINGS_ICON_FONTS,
SETTINGS_ICON_DISPLAY,
SETTINGS_ICON_COUNT
SETTINGS_ICON_KEYBOARD,
SETTINGS_ICON_COUNT,// must be last
};
static const char *settings_icon_names[SETTINGS_ICON_COUNT] = {
@@ -109,6 +111,7 @@ static const char *settings_icon_names[SETTINGS_ICON_COUNT] = {
"input-mouse.png",
"fonts.png",
"preferences-desktop-display.png",
"input-keyboard.png"
};
static int settings_icon_main_state[SETTINGS_ICON_COUNT];
@@ -134,6 +137,7 @@ static uint32_t settings_icon_list_pixels[SETTINGS_ICON_COUNT][SETTINGS_ICON_LIS
#define VIEW_MOUSE 4
#define VIEW_FONTS 5
#define VIEW_DISPLAY 6
#define VIEW_KEYBOARD 7
static int disp_sel_res = 2;
static int disp_sel_color = 0;
@@ -148,6 +152,7 @@ static char net_ip[16] = "";
static char net_dns[16] = "";
static int focused_field = -1;
static int input_cursor = 0;
static int keyboard_layout = 0;
static int dyn_res_w[3];
static int dyn_res_h[3];
@@ -503,6 +508,13 @@ static void control_panel_paint_main(ui_window_t win) {
settings_draw_icon(win, SETTINGS_ICON_DISPLAY, offset_x + 16, offset_y + item_y + 14, false);
ui_draw_string(win, offset_x + 60, offset_y + item_y + 15, "Display", COLOR_DARK_TEXT);
ui_draw_string(win, offset_x + 60, offset_y + item_y + 35, "Screen resolution & color", COLOR_DKGRAY);
// Keyboard
item_y += item_h + item_spacing;
widget_button_draw(&settings_ctx, &btn_main_keyboard);
settings_draw_icon(win, SETTINGS_ICON_KEYBOARD, offset_x + 16, offset_y + item_y + 14, false);
ui_draw_string(win, offset_x + 60, offset_y + item_y + 15, "Keyboard", COLOR_DARK_TEXT);
ui_draw_string(win, offset_x + 60, offset_y + item_y + 35, "Keyboard layout", COLOR_DKGRAY);
}
static void control_panel_paint_wallpaper(ui_window_t win) {
@@ -891,6 +903,19 @@ static void control_panel_paint_display(ui_window_t win) {
widget_dropdown_draw(&settings_ctx, &drop_color);
}
static void control_panel_paint_keyboard(ui_window_t win) {
int offset_x = 8;
int offset_y = 6;
widget_button_draw(&settings_ctx, &btn_back);
ui_draw_string(win, offset_x, offset_y + 40, "Keyboard Layout:", COLOR_DARK_TEXT);
widget_dropdown_draw(&settings_ctx, &drop_keyboard);
widget_button_draw(&settings_ctx, &btn_apply);
}
static void control_panel_paint(ui_window_t win) {
// Fill background
ui_draw_rect(win, 0, 0, 350, 500, COLOR_DARK_BG);
@@ -911,6 +936,8 @@ static void control_panel_paint(ui_window_t win) {
control_panel_paint_fonts(win);
} else if (current_view == VIEW_DISPLAY) {
control_panel_paint_display(win);
} else if (current_view == VIEW_KEYBOARD) {
control_panel_paint_keyboard(win);
}
}
@@ -1185,6 +1212,14 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click
if (is_click) { current_view = VIEW_DISPLAY; focused_field = -1; btn_main_display.pressed = false; }
return;
}
if (widget_button_handle_mouse(&btn_main_keyboard, x, y, is_down, is_click, NULL)) {
if (is_click) {
current_view = VIEW_KEYBOARD;
focused_field = -1;
btn_main_keyboard.pressed = false;
}
return;
}
}
if (current_view == VIEW_MOUSE) {
@@ -1211,6 +1246,22 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click
focused_field = 4; disp_sel_res = 5; input_cursor = tb_custom_h.cursor_pos; return;
}
}
if (current_view == VIEW_KEYBOARD) {
if (widget_dropdown_handle_mouse(&drop_keyboard, x, y, is_click, NULL)) {
keyboard_layout = drop_keyboard.selected_idx;
return;
}
if (widget_button_handle_mouse(&btn_apply, x, y, is_down, is_click, NULL)) {
if (is_click) {
sys_system(SYSTEM_CMD_SET_KEYBOARD_LAYOUT, keyboard_layout, 0,0,0);
btn_apply.pressed = false;
}
return;
}
}
}
static void control_panel_handle_key(char c, bool pressed) {
@@ -1253,6 +1304,9 @@ static void init_settings_widgets(void) {
static const char *color_opts[] = {"32-bit", "16-bit", "256 Colors", "Grayscale", "Monochrome"};
widget_dropdown_init(&drop_color, 168, 66, 140, 30, color_opts, 5);
static const char *keyboard_opts[] = {"QWERTY", "AZERTY", "QWERTZ", "DVORAK"}; // add more layouts here
widget_dropdown_init(&drop_keyboard, 8, 80, 200, 30, keyboard_opts, 4); // increment the last number when adding more layouts
widget_textbox_init(&tb_r, 33, 226, 50, 18, rgb_r, 4);
widget_textbox_init(&tb_g, 123, 226, 50, 18, rgb_g, 4);
widget_textbox_init(&tb_b, 213, 226, 50, 18, rgb_b, 4);
@@ -1270,7 +1324,8 @@ static void init_settings_widgets(void) {
widget_button_init(&btn_main_desktop, 8, 6 + item_y, 334, 60, ""); item_y += 70;
widget_button_init(&btn_main_mouse, 8, 6 + item_y, 334, 60, ""); item_y += 70;
widget_button_init(&btn_main_fonts, 8, 6 + item_y, 334, 60, ""); item_y += 70;
widget_button_init(&btn_main_display, 8, 6 + item_y, 334, 60, "");
widget_button_init(&btn_main_display, 8, 6 + item_y, 334, 60, ""); item_y += 70;
widget_button_init(&btn_main_keyboard, 8, 6 + item_y, 334, 60, "");
// Wallpaper View Buttons
widget_button_init(&btn_wp_colors[0], 8, 71, 91, 25, "");
@@ -1302,54 +1357,93 @@ static void init_settings_widgets(void) {
int main(int argc, char **argv) {
(void)argc;
(void)argv;
ui_window_t win = ui_window_create("Settings", 200, 150, 350, 500);
if (!win) return 1;
generate_lumberjack_pattern();
fetch_kernel_state();
init_dynamic_resolutions();
init_settings_widgets();
desktop_snap_to_grid = sys_system(SYSTEM_CMD_GET_DESKTOP_PROP, 1, 0, 0, 0);
desktop_auto_align = sys_system(SYSTEM_CMD_GET_DESKTOP_PROP, 2, 0, 0, 0);
desktop_max_rows_per_col = sys_system(SYSTEM_CMD_GET_DESKTOP_PROP, 3, 0, 0, 0);
desktop_max_cols = sys_system(SYSTEM_CMD_GET_DESKTOP_PROP, 4, 0, 0, 0);
mouse_speed = sys_system(SYSTEM_CMD_GET_MOUSE_SPEED, 0, 0, 0, 0);
load_settings_icons();
// Set initial toggle states
chk_snap.checked = desktop_snap_to_grid;
chk_align.checked = desktop_auto_align;
drop_res.selected_idx = disp_sel_res;
// Set initial widget states
chk_snap.checked = desktop_snap_to_grid;
chk_align.checked = desktop_auto_align;
drop_res.selected_idx = disp_sel_res;
drop_color.selected_idx = disp_sel_color;
keyboard_layout = sys_system(SYSTEM_CMD_GET_KEYBOARD_LAYOUT, 0, 0, 0, 0);
drop_keyboard.selected_idx = keyboard_layout;
control_panel_paint(win);
ui_mark_dirty(win, 0, 0, 350, 500);
load_wallpapers(); // load after first paint to avoid startup delay
gui_event_t ev;
while (1) {
bool dirty = false;
if (ui_get_event(win, &ev)) {
if (ev.type == GUI_EVENT_PAINT) {
dirty = true;
} else if (ev.type == GUI_EVENT_CLICK || ev.type == GUI_EVENT_MOUSE_DOWN ||
ev.type == GUI_EVENT_MOUSE_MOVE || ev.type == GUI_EVENT_MOUSE_UP) {
bool down = (ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_CLICK);
if (ev.type == GUI_EVENT_MOUSE_MOVE) down = (ev.arg3 & 1);
if (ev.type == GUI_EVENT_MOUSE_UP) down = false;
control_panel_handle_mouse(ev.arg1, ev.arg2, down, ev.type == GUI_EVENT_CLICK);
} else if (ev.type == GUI_EVENT_CLICK ||
ev.type == GUI_EVENT_MOUSE_DOWN ||
ev.type == GUI_EVENT_MOUSE_MOVE ||
ev.type == GUI_EVENT_MOUSE_UP) {
bool down = false;
if (ev.type == GUI_EVENT_MOUSE_DOWN || ev.type == GUI_EVENT_CLICK) {
down = true;
} else if (ev.type == GUI_EVENT_MOUSE_MOVE) {
down = (ev.arg3 & 1);
} else if (ev.type == GUI_EVENT_MOUSE_UP) {
down = false;
}
control_panel_handle_mouse(
ev.arg1,
ev.arg2,
down,
ev.type == GUI_EVENT_CLICK
);
dirty = true;
} else if (ev.type == GUI_EVENT_MOUSE_WHEEL) {
if (current_view == VIEW_FONTS) {
font_scroll_y -= ev.arg1 * 20;
int max_scroll = font_scrollbar.content_height - font_scrollbar.h;
if (max_scroll < 0) max_scroll = 0;
if (font_scroll_y < 0) font_scroll_y = 0;
if (font_scroll_y > max_scroll) font_scroll_y = max_scroll;
widget_scrollbar_update(&font_scrollbar, font_scrollbar.content_height, font_scroll_y);
widget_scrollbar_update(
&font_scrollbar,
font_scrollbar.content_height,
font_scroll_y
);
dirty = true;
}
} else if (ev.type == GUI_EVENT_KEY) {
control_panel_handle_key((char)ev.arg1, true);
dirty = true;
} else if (ev.type == GUI_EVENT_KEYUP) {
control_panel_handle_key((char)ev.arg1, false);
} else if (ev.type == GUI_EVENT_CLOSE) {
sys_exit(0);
}
if (dirty) {
control_panel_paint(win);
ui_mark_dirty(win, 0, 0, 350, 500);
@@ -1358,6 +1452,7 @@ int main(int argc, char **argv) {
sleep(10);
}
}
return 0;
}

View File

@@ -44,9 +44,10 @@
typedef struct {
int type;
int arg1; // For click: x
int arg2; // For click: y
int arg3; // For click: button state
int arg1; // CLICK: x / KEY: legacy char-or-compat key
int arg2; // CLICK: y / KEY: keycode
int arg3; // CLICK: button state / KEY: modifier mask
int arg4; // KEY: Unicode codepoint (0 if none)
} gui_event_t;
// Window Handle

View File

@@ -64,6 +64,8 @@
#define SYSTEM_CMD_NETWORK_HAS_IP 30
#define SYSTEM_CMD_GET_SHELL_CONFIG 28
#define SYSTEM_CMD_NETWORK_GET_NIC_NAME 48
#define SYSTEM_CMD_SET_KEYBOARD_LAYOUT 49
#define SYSTEM_CMD_GET_KEYBOARD_LAYOUT 51
#define SYSTEM_CMD_SET_TEXT_COLOR 29
#define SYSTEM_CMD_SET_WALLPAPER_PATH 31
#define SYSTEM_CMD_RTC_SET 32

115
src/userland/libc/utf-8.c Normal file
View File

@@ -0,0 +1,115 @@
#include "utf-8.h"
static int utf8_write_replacement(char *out) {
out[0] = (char)0xEF;
out[1] = (char)0xBF;
out[2] = (char)0xBD;
return 3;
}
uint32_t text_decode_utf8(const char *s, int *advance) {
const unsigned char *u = (const unsigned char *)s;
if (!u || u[0] == 0) {
if (advance) *advance = 0;
return 0;
}
if ((u[0] & 0x80) == 0) {
if (advance) *advance = 1;
return u[0];
}
if ((u[0] & 0xE0) == 0xC0 &&
(u[1] & 0xC0) == 0x80) {
if (advance) *advance = 2;
return ((u[0] & 0x1F) << 6) |
(u[1] & 0x3F);
}
if ((u[0] & 0xF0) == 0xE0 &&
(u[1] & 0xC0) == 0x80 &&
(u[2] & 0xC0) == 0x80) {
if (advance) *advance = 3;
return ((u[0] & 0x0F) << 12) |
((u[1] & 0x3F) << 6) |
(u[2] & 0x3F);
}
if ((u[0] & 0xF8) == 0xF0 &&
(u[1] & 0xC0) == 0x80 &&
(u[2] & 0xC0) == 0x80 &&
(u[3] & 0xC0) == 0x80) {
if (advance) *advance = 4;
return ((u[0] & 0x07) << 18) |
((u[1] & 0x3F) << 12) |
((u[2] & 0x3F) << 6) |
(u[3] & 0x3F);
}
if (advance) *advance = 1;
return 0xFFFD;
}
int text_encode_utf8(uint32_t cp, char *out) {
if (cp <= 0x7F) {
out[0] = (char)cp;
return 1;
}
if (cp <= 0x7FF) {
out[0] = 0xC0 | (cp >> 6);
out[1] = 0x80 | (cp & 0x3F);
return 2;
}
if (cp <= 0xFFFF) {
out[0] = 0xE0 | (cp >> 12);
out[1] = 0x80 | ((cp >> 6) & 0x3F);
out[2] = 0x80 | (cp & 0x3F);
return 3;
}
if (cp <= 0x10FFFF) {
out[0] = 0xF0 | (cp >> 18);
out[1] = 0x80 | ((cp >> 12) & 0x3F);
out[2] = 0x80 | ((cp >> 6) & 0x3F);
out[3] = 0x80 | (cp & 0x3F);
return 4;
}
return utf8_write_replacement(out);
}
const char* text_next_utf8(const char *s) {
if (!s || *s == 0) return s;
int adv;
text_decode_utf8(s, &adv);
return s + adv;
}
const char* text_prev_utf8(const char *start, const char *s) {
if (!s || s <= start) return start;
s--;
while (s > start && ((*s & 0xC0) == 0x80)) {
s--;
}
return s;
}
int text_strlen_utf8(const char *s) {
if (!s) return 0;
int count = 0;
int adv;
while (*s) {
text_decode_utf8(s, &adv);
s += adv;
count++;
}
return count;
}

25
src/userland/libc/utf-8.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef UTF_8_H
#define UTF_8_H
#include <stdint.h>
// Decode one UTF-8 codepoint
// s: input string
// advance: number of bytes consumed
uint32_t text_decode_utf8(const char *s, int *advance);
// Encode one codepoint into UTF-8
// out must be at least 4 bytes
// return: number of bytes written
int text_encode_utf8(uint32_t cp, char *out);
// Move to next UTF-8 character
const char* text_next_utf8(const char *s);
// Move to previous UTF-8 character
const char* text_prev_utf8(const char *start, const char *s);
// Count characters (not bytes)
int text_strlen_utf8(const char *s);
#endif

View File

@@ -1414,7 +1414,12 @@ static void explorer_handle_click(Window *win, int x, int y) {
}
}
static void explorer_handle_key(Window *win, char c, bool pressed) {
static void explorer_handle_key(Window *win, int legacy, uint16_t keycode, uint32_t codepoint, uint32_t mods, bool pressed) {
(void)keycode;
(void)codepoint;
(void)mods;
char c = (char)legacy;
if (!pressed) return;
ExplorerState *state = (ExplorerState*)win->data;

View File

@@ -34,9 +34,10 @@
typedef struct {
int type;
int arg1; // For click: x
int arg2; // For click: y
int arg3; // For click: button state
int arg1; // CLICK: x / KEY: legacy char-or-compat key
int arg2; // CLICK: y / KEY: keycode
int arg3; // CLICK: button state / KEY: modifier mask
int arg4; // KEY: Unicode codepoint (0 if none)
} gui_event_t;
#endif

View File

@@ -22,6 +22,8 @@
#include "../sys/work_queue.h"
#include "../sys/smp.h"
#include "../core/kconsole.h"
#include "../input/keycodes.h"
#include "../input/keymap.h"
// Hello developer,
@@ -30,7 +32,7 @@
// TRUST ME.
// If you do decide to hate yourself for some dumb reason,
// add a few hours to the counter of despair:
// hours wasted: 57
// hours wasted: 61
// send help
#include "../sys/spinlock.h"
@@ -98,7 +100,7 @@ static char notif_text[256] = {0};
static int notif_timer = 0;
static int notif_x_offset = 420; // Starts offscreen
static bool notif_active = false;
extern bool ps2_ctrl_pressed;
static lumos_state_t lumos_state = {0};
static bool lumos_index_built = false;
@@ -3652,14 +3654,18 @@ void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz) {
// Input Queue
#define INPUT_QUEUE_SIZE 128
typedef struct {
char c;
int legacy;
uint16_t keycode;
uint32_t codepoint;
uint32_t mods;
bool pressed;
} key_event_t;
static key_event_t key_queue[INPUT_QUEUE_SIZE];
static volatile int key_head = 0;
static volatile int key_tail = 0;
static void wm_dispatch_key(char c, bool pressed) {
static void wm_dispatch_key(int legacy, uint16_t keycode, uint32_t codepoint, uint32_t mods, bool pressed) {
char c = (char)legacy;
if (desktop_dialog_state != 0) {
int len = 0; while(desktop_dialog_input[len]) len++;
if (c == '\n') {
@@ -3721,7 +3727,7 @@ static void wm_dispatch_key(char c, bool pressed) {
if (target->handle_key) {
target->handle_key(target, c, pressed);
target->handle_key(target, legacy, keycode, codepoint, mods, pressed);
}
// Mark window as needing redraw on next timer tick
@@ -3748,18 +3754,20 @@ static void build_file_index_async(void *arg) {
file_index_build();
}
void wm_handle_key(char c, bool pressed) {
if (pressed && c == 'p' && ps2_ctrl_pressed) {
// Called by keyboard interrupt handler
void wm_handle_key_event(uint16_t keycode, uint32_t codepoint, uint32_t mods, bool pressed) {
int legacy = keymap_legacy_key(keycode, codepoint);
char c = (char)legacy;
if (pressed && keycode == KEY_P && (mods & KB_MOD_CTRL)) {
process_create_elf("/bin/screenshot.elf", NULL, false, -1);
return;
}
if (pressed && c == ' ' && ps2_ctrl_pressed && ps2_shift_pressed()) {
if (pressed && keycode == KEY_SPACE && (mods & KB_MOD_CTRL) && (mods & KB_MOD_SHIFT)) {
lumos_state.visible = !lumos_state.visible;
if (lumos_state.visible) {
// Check current index status - it may still be building in background
lumos_index_built = file_index_is_valid();
// Clear search state when opening
lumos_state.search_len = 0;
lumos_state.search_query[0] = 0;
lumos_state.cursor_pos = 0;
@@ -3772,15 +3780,18 @@ void wm_handle_key(char c, bool pressed) {
force_redraw = true;
return;
}
if (lumos_state.visible && pressed) {
if (lumos_state.visible && pressed && legacy != 0) {
wm_lumos_handle_key(c);
return;
}
int next = (key_head + 1) % INPUT_QUEUE_SIZE;
if (next != key_tail) {
key_queue[key_head].c = c;
key_queue[key_head].legacy = legacy;
key_queue[key_head].keycode = keycode;
key_queue[key_head].codepoint = codepoint;
key_queue[key_head].mods = mods;
key_queue[key_head].pressed = pressed;
key_head = next;
}
@@ -3801,7 +3812,7 @@ void wm_process_input(void) {
while (key_head != key_tail) {
key_event_t ev = key_queue[key_tail];
key_tail = (key_tail + 1) % INPUT_QUEUE_SIZE;
wm_dispatch_key(ev.c, ev.pressed);
wm_dispatch_key(ev.legacy, ev.keycode, ev.codepoint, ev.mods, ev.pressed);
}
wm_lock_release(rflags);

View File

@@ -60,7 +60,7 @@ struct Window {
// Callbacks
void (*paint)(Window *win);
void (*handle_key)(Window *win, char c, bool pressed);
void (*handle_key)(Window *win, int legacy, uint16_t keycode, uint32_t codepoint, uint32_t mods, bool pressed);
void (*handle_click)(Window *win, int x, int y);
void (*handle_right_click)(Window *win, int x, int y);
void (*handle_mouse_down)(Window *win, int x, int y);
@@ -91,7 +91,7 @@ typedef struct {
void wm_init(void);
void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz);
void wm_handle_key(char c, bool pressed);
void wm_handle_key_event(uint16_t keycode, uint32_t codepoint, uint32_t mods, bool pressed);
void wm_handle_click(int x, int y);
void wm_handle_right_click(int x, int y);
void wm_process_input(void);