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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1624 additions and 275 deletions

View file

@ -31,6 +31,7 @@ C_SOURCES = $(wildcard $(SRC_DIR)/core/*.c) \
$(wildcard $(SRC_DIR)/sys/*.c) \
$(wildcard $(SRC_DIR)/mem/*.c) \
$(wildcard $(SRC_DIR)/dev/*.c) \
$(wildcard $(SRC_DIR)/input/*.c) \
$(wildcard $(SRC_DIR)/net/*.c) \
$(wildcard $(SRC_DIR)/net/nic/*.c) \
$(wildcard $(SRC_DIR)/fs/*.c) \
@ -45,6 +46,7 @@ OBJ_FILES = $(patsubst $(SRC_DIR)/core/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_D
$(patsubst $(SRC_DIR)/sys/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/sys/*.c)) \
$(patsubst $(SRC_DIR)/mem/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/mem/*.c)) \
$(patsubst $(SRC_DIR)/dev/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/dev/*.c)) \
$(patsubst $(SRC_DIR)/input/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/input/*.c)) \
$(patsubst $(SRC_DIR)/net/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/net/*.c)) \
$(patsubst $(SRC_DIR)/net/nic/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/net/nic/*.c)) \
$(patsubst $(SRC_DIR)/fs/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/fs/*.c)) \
@ -55,7 +57,7 @@ OBJ_FILES = $(patsubst $(SRC_DIR)/core/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_D
CFLAGS = -g -O2 -pipe -Wall -Wextra -std=gnu11 -ffreestanding \
-fno-stack-protector -fno-stack-check -fno-lto -fPIE \
-m64 -march=x86-64 -msse -msse2 -mstackrealign -mno-red-zone \
-I$(SRC_DIR) -I$(SRC_DIR)/net/third_party/lwip -I$(SRC_DIR)/core -I$(SRC_DIR)/sys -I$(SRC_DIR)/mem -I$(SRC_DIR)/dev -I$(SRC_DIR)/net -I$(SRC_DIR)/net/nic -I$(SRC_DIR)/fs -I$(SRC_DIR)/wm
-I$(SRC_DIR) -I$(SRC_DIR)/net/third_party/lwip -I$(SRC_DIR)/core -I$(SRC_DIR)/sys -I$(SRC_DIR)/mem -I$(SRC_DIR)/dev -I$(SRC_DIR)/net -I$(SRC_DIR)/net/nic -I$(SRC_DIR)/fs -I$(SRC_DIR)/wm -I$(SRC_DIR)/input
LDFLAGS = -m elf_x86_64 -nostdlib -static -pie --no-dynamic-linker \
-z text -z max-page-size=0x1000 -T linker.ld
@ -106,6 +108,10 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/dev/%.c | $(BUILD_DIR) limine-setup
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/input/%.c | $(BUILD_DIR) limine-setup
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/net/%.c | $(BUILD_DIR) limine-setup
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@

View file

@ -0,0 +1,273 @@
# Input Subsystem
## Overview
The input subsystem is responsible for handling user input, primarily from the keyboard.
It provides a structured pipeline that transforms low-level hardware signals into usable data for the kernel and higher-level components. This subsystem abstracts hardware-specific behavior and exposes a consistent interface to the rest of the operating system.
---
## Scope
The `/input` directory focuses on keyboard input. It includes:
- A keyboard driver responsible for handling hardware events
- A keycode layer used as an intermediate representation
- A keymap system that translates keycodes into characters
---
## Design Principles
- **Hardware abstraction**
Hardware-specific logic is isolated from higher-level components.
- **Simplicity**
The input path is kept minimal and efficient, especially in interrupt context.
- **Modularity**
Each stage of input processing is handled by a dedicated component.
- **Extensibility**
The system is designed to support additional input devices and layouts in the future.
---
## Directory Structure
```
input/
├── keyboard.c
├── keyboard.h
├── keycodes.h
├── keymap.c
├── keymap.h
```
---
## Input Processing Model
Keyboard input is processed in three distinct stages:
1. Raw scancodes are received from the hardware
2. Scancodes are converted into keycodes
3. Keycodes are translated into characters or control signals
Each stage is handled independently to ensure clarity and maintainability.
---
## Components
### Keyboard Driver
#### Overview
The keyboard driver interfaces directly with the keyboard hardware. It handles interrupts and processes raw input data from the controller.
#### Responsibilities
- Handle keyboard interrupts
- Read scancodes from the PS/2 controller
- Convert scancodes into keycodes
- Forward processed data to higher layers
#### Behavior
The driver operates in an interrupt-driven context. When a key event occurs, the hardware triggers an interrupt. The driver reads the corresponding scancode and processes it immediately.
Because this code runs at a low level, it must be fast, predictable, and minimal.
#### Integration
The keyboard driver depends on:
- The PS/2 controller driver for hardware communication
- The interrupt subsystem for event handling
It provides output to:
- The keycode system
- The keymap system
#### Constraints
- Must not block execution
- Must minimize processing time per interrupt
- Must correctly handle key press and key release events
---
### Keycodes
#### Overview
Keycodes define a hardware-independent representation of keyboard keys.
They serve as an abstraction layer between raw scancodes and higher-level logic.
#### Purpose
The keycode system standardizes keyboard input by mapping all physical key events to a consistent set of identifiers.
This allows the system to:
- Remain independent from specific hardware implementations
- Simplify input handling logic
- Support multiple layouts and configurations
#### Design
Each key is represented by a unique constant, such as:
- KEY_A
- KEY_ENTER
- KEY_SHIFT
#### Role in the System
Keycodes act as the intermediate layer between:
- Hardware-level scancodes
- Character-level or command-level input
#### Usage
- Generated by the keyboard driver
- Consumed by the keymap system
#### Extensibility
The keycode system can be extended to support:
- Additional keys (function keys, multimedia keys)
- Non-standard input devices
- Custom mappings
---
### Keymap
#### Overview
The keymap system translates keycodes into characters or control signals.
It defines how physical key presses are interpreted based on layout and modifier state.
#### Responsibilities
- Convert keycodes into ASCII or equivalent representations
- Apply modifier logic such as Shift and Control
- Provide consistent character output
#### Behavior
The keymap takes a keycode as input and produces an output depending on:
- The current keyboard layout
- Active modifier keys
The same keycode may produce different results depending on modifier state.
#### Integration
- Receives keycodes from the keyboard driver
- Outputs characters to the kernel or userland
---
## Control Signals
In addition to character generation, the input subsystem produces **control signals** representing non-printable keys and command-oriented input.
These signals are derived from keycodes that do not map directly to ASCII characters.
---
### Definition
A control signal is an abstract representation of a key event used to trigger system-level behavior rather than text output.
Typical control signals include:
- Enter
- Backspace
- Escape
- Tab
- Arrow keys
- Function keys
---
### Encoding
Control signals may be represented in different ways depending on the layer:
#### ASCII Control Characters (when applicable)
Some keys map to standard ASCII control codes:
- `ENTER``0x0A` (Line Feed) or `0x0D` (Carriage Return)
- `BACKSPACE``0x08`
- `TAB``0x09`
- `ESC``0x1B`
These values are part of the ASCII control range (`0x00``0x1F`).
---
#### Non-ASCII Keys
Keys that do not belong to the ASCII set are typically handled as **extended keycodes** or **internal constants**:
Examples:
- Arrow keys
- Insert / Delete
- Home / End
- Function keys (F1F12)
------
## Non-ASCII Characters
Non-ASCII characters include any character outside the standard 7-bit ASCII range (`0x00``0x7F`).
Examples:
- Accented characters: `é`, `à`, `ç`
- Symbols: `€`, `£`
- Unicode characters from non-Latin scripts
---
### Encoding Considerations
The current system typically assumes ASCII output. However, supporting non-ASCII characters requires:
- A wider character encoding (e.g. UTF-8)
- Extended keymaps capable of mapping key combinations to multi-byte sequences
Example:
- `'é'` in UTF-8 → `0xC3 0xA9`
---
### Modifier and Layout Impact
Non-ASCII characters are often produced through:
- Keyboard layout differences (AZERTY vs QWERTY)
- Modifier combinations (Shift, AltGr)
Example:
- `AltGr + E``'€'` (depending on layout)
- `KEY_E``'e'`
- `KEY_E + SHIFT``'E'`
---
### Usage
- Control signals are used for command handling and system interaction
- Non-ASCII characters are used for text input and require proper encoding support

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);
@ -37,11 +39,6 @@ uint64_t timer_handler(registers_t *regs) {
}
// --- 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,7 +48,11 @@ 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);
@ -59,87 +60,17 @@ static void ps2_update_leds(void) {
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
@ -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++];

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
@ -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) {
@ -71,16 +73,23 @@ static void notepad_paint(ui_window_t win, int w, int h) {
int current_y = 4;
int window_right = w - 8;
for (int i = 0; i < buf_len; i++) {
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 (visual_line >= notepad_scroll_line + (h - 8) / fh) break;
if (buffer[i] == '\n') {
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,6 +159,8 @@ static void notepad_paint(ui_window_t win, int w, int h) {
}
cx += cw;
}
i += adv;
}
if (c_visual_line >= notepad_scroll_line &&
@ -149,110 +171,84 @@ static void notepad_paint(ui_window_t win, int w, int h) {
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");

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,50 +1357,89 @@ 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);
}
@ -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;
@ -3773,14 +3781,17 @@ void wm_handle_key(char c, bool pressed) {
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);