mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
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:
parent
228b5753d9
commit
915e33434e
22 changed files with 1624 additions and 275 deletions
8
Makefile
8
Makefile
|
|
@ -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 $@
|
||||
|
|
|
|||
273
docs/architecture/input/keyboard.md
Normal file
273
docs/architecture/input/keyboard.md
Normal 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 (F1–F12)
|
||||
|
||||
------
|
||||
## 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
|
||||
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
102
src/dev/ps2.c
102
src/dev/ps2.c
|
|
@ -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
214
src/input/keyboard.c
Normal 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
26
src/input/keyboard.h
Normal 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
84
src/input/keycodes.h
Normal 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
447
src/input/keymap.c
Normal 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
42
src/input/keymap.h
Normal 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
|
||||
79
src/mem/vm.c
79
src/mem/vm.c
|
|
@ -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++];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
115
src/userland/libc/utf-8.c
Normal 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
25
src/userland/libc/utf-8.h
Normal 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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
37
src/wm/wm.c
37
src/wm/wm.c
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue