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

* Adding keyboard layout (backend)

* Update settings.c with new keyboard tab

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

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

* Adding keyboard handler

* Udating ps2.c with the new logic

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

* Fixing keycode range && Updating dead keys handling

* Add comments for explanation

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

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

* Adding icon for icon settings

* Fixing a warning with double definition

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

* Add documentation for keyboard input subsystem

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

---------

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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