feat: switch text to UTF-8; add Unicode key events, dead keys & Czech layout; fix numpad navigation; add NumLock support

This commit is contained in:
daniel committed 2026-10-03 10:11:49 +02:00
1 parent 32660ddd1a
commit 2aa6360b3d
101 files changed
+3733 -1639

No files matched your search

@@ -351,36 +351,21 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
} else if (key.scancode == 0x01) {
filemanager_cancel_pathbar(fm);
} else if (key.ascii == '\b' || key.scancode == 0x0E) {
if (fm->pathbar_cursor > 0) {
for (int i = fm->pathbar_cursor - 1; i < fm->pathbar_len - 1; i++)
fm->pathbar_buf[i] = fm->pathbar_buf[i + 1];
fm->pathbar_len--;
fm->pathbar_cursor--;
fm->pathbar_buf[fm->pathbar_len] = '\0';
}
} else if (key.scancode == 0x53) {
if (fm->pathbar_cursor < fm->pathbar_len) {
for (int i = fm->pathbar_cursor; i < fm->pathbar_len - 1; i++)
fm->pathbar_buf[i] = fm->pathbar_buf[i + 1];
fm->pathbar_len--;
fm->pathbar_buf[fm->pathbar_len] = '\0';
}
} else if (key.scancode == 0x4B) {
if (fm->pathbar_cursor > 0) fm->pathbar_cursor--;
} else if (key.scancode == 0x4D) {
if (fm->pathbar_cursor < fm->pathbar_len) fm->pathbar_cursor++;
} else if (key.scancode == 0x47) {
montauk::utf8::erase_before(fm->pathbar_buf, &fm->pathbar_len, &fm->pathbar_cursor);
} else if (montauk::nav_scancode(key) == 0x53) {
montauk::utf8::erase_at(fm->pathbar_buf, &fm->pathbar_len, fm->pathbar_cursor);
} else if (montauk::nav_scancode(key) == 0x4B) {
fm->pathbar_cursor = montauk::utf8::prev_boundary(fm->pathbar_buf, fm->pathbar_cursor);
} else if (montauk::nav_scancode(key) == 0x4D) {
fm->pathbar_cursor = montauk::utf8::next_boundary(fm->pathbar_buf, fm->pathbar_len,
fm->pathbar_cursor);
} else if (montauk::nav_scancode(key) == 0x47) {
fm->pathbar_cursor = 0;
} else if (key.scancode == 0x4F) {
} else if (montauk::nav_scancode(key) == 0x4F) {
fm->pathbar_cursor = fm->pathbar_len;
} else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127 && fm->pathbar_len < 254) {
for (int i = fm->pathbar_len; i > fm->pathbar_cursor; i--)
fm->pathbar_buf[i] = fm->pathbar_buf[i - 1];
fm->pathbar_buf[fm->pathbar_cursor] = key.ascii;
fm->pathbar_cursor++;
fm->pathbar_len++;
fm->pathbar_buf[fm->pathbar_len] = '\0';
} else {
montauk::utf8::insert_key(fm->pathbar_buf, &fm->pathbar_len, 255,
&fm->pathbar_cursor, key);
}
return;
}
@@ -394,44 +379,27 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
filemanager_cancel_rename(fm);
} else if (key.ascii == '\b' || key.scancode == 0x0E) {
// Backspace
if (fm->rename_cursor > 0) {
for (int i = fm->rename_cursor - 1; i < fm->rename_len - 1; i++)
fm->rename_buf[i] = fm->rename_buf[i + 1];
fm->rename_len--;
fm->rename_cursor--;
fm->rename_buf[fm->rename_len] = '\0';
}
} else if (key.scancode == 0x53) {
montauk::utf8::erase_before(fm->rename_buf, &fm->rename_len, &fm->rename_cursor);
} else if (montauk::nav_scancode(key) == 0x53) {
// Delete key
if (fm->rename_cursor < fm->rename_len) {
for (int i = fm->rename_cursor; i < fm->rename_len - 1; i++)
fm->rename_buf[i] = fm->rename_buf[i + 1];
fm->rename_len--;
fm->rename_buf[fm->rename_len] = '\0';
}
} else if (key.scancode == 0x4B) {
montauk::utf8::erase_at(fm->rename_buf, &fm->rename_len, fm->rename_cursor);
} else if (montauk::nav_scancode(key) == 0x4B) {
// Left arrow
if (fm->rename_cursor > 0) fm->rename_cursor--;
} else if (key.scancode == 0x4D) {
fm->rename_cursor = montauk::utf8::prev_boundary(fm->rename_buf, fm->rename_cursor);
} else if (montauk::nav_scancode(key) == 0x4D) {
// Right arrow
if (fm->rename_cursor < fm->rename_len) fm->rename_cursor++;
} else if (key.scancode == 0x47) {
fm->rename_cursor = montauk::utf8::next_boundary(fm->rename_buf, fm->rename_len,
fm->rename_cursor);
} else if (montauk::nav_scancode(key) == 0x47) {
// Home
fm->rename_cursor = 0;
} else if (key.scancode == 0x4F) {
} else if (montauk::nav_scancode(key) == 0x4F) {
// End
fm->rename_cursor = fm->rename_len;
} else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127) {
} else if (key.ascii != '/' && key.ascii != '\\') {
// Printable character (reject / and other FS-unsafe chars)
if (key.ascii != '/' && key.ascii != '\\' && fm->rename_len < 62) {
for (int i = fm->rename_len; i > fm->rename_cursor; i--)
fm->rename_buf[i] = fm->rename_buf[i - 1];
fm->rename_buf[fm->rename_cursor] = key.ascii;
fm->rename_cursor++;
fm->rename_len++;
fm->rename_buf[fm->rename_len] = '\0';
}
montauk::utf8::insert_key(fm->rename_buf, &fm->rename_len, 63,
&fm->rename_cursor, key);
}
return;
}
@@ -466,7 +434,7 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
if (key.ascii == '\b' || key.scancode == 0x0E) {
filemanager_go_up(fm);
} else if (key.scancode == 0x48) {
} else if (montauk::nav_scancode(key) == 0x48) {
// Up arrow
if (fm->grid_view) {
Rect cr_r = win->content_rect();
@@ -477,7 +445,7 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
if (fm->selected > 0) fm->selected--;
}
filemanager_scroll_to_selected(win, fm);
} else if (key.scancode == 0x50) {
} else if (montauk::nav_scancode(key) == 0x50) {
// Down arrow
if (fm->grid_view) {
Rect cr_r = win->content_rect();
@@ -488,22 +456,22 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
if (fm->selected < fm->entry_count - 1) fm->selected++;
}
filemanager_scroll_to_selected(win, fm);
} else if (key.scancode == 0x4B && !key.alt && fm->grid_view) {
} else if (montauk::nav_scancode(key) == 0x4B && !key.alt && fm->grid_view) {
// Left arrow (grid view only)
if (fm->selected > 0) fm->selected--;
} else if (key.scancode == 0x4D && !key.alt && fm->grid_view) {
} else if (montauk::nav_scancode(key) == 0x4D && !key.alt && fm->grid_view) {
// Right arrow (grid view only)
if (fm->selected < fm->entry_count - 1) fm->selected++;
} else if (key.ascii == '\n' || key.ascii == '\r') {
if (fm->selected >= 0 && fm->selected < fm->entry_count)
filemanager_open_entry(fm, fm->selected);
} else if (key.scancode == 0x53) {
} else if (montauk::nav_scancode(key) == 0x53) {
// Delete key
filemanager_delete_selected(fm);
} else if (key.alt && key.scancode == 0x4B) {
} else if (key.alt && montauk::nav_scancode(key) == 0x4B) {
// Alt+Left: go back
filemanager_go_back(fm);
} else if (key.alt && key.scancode == 0x4D) {
} else if (key.alt && montauk::nav_scancode(key) == 0x4D) {
// Alt+Right: go forward
filemanager_go_forward(fm);
}
@@ -65,10 +65,10 @@ static void props_fit_text(const char* src, char* out, int out_len, int max_w) {
while (len > 0) {
out[len] = '\0';
if (text_width(out) + ellipsis_w <= max_w) break;
len--;
len = montauk::utf8::prev_boundary(out, len);
}
if (len > out_len - 4) len = out_len - 4;
if (len > out_len - 4) len = montauk::utf8::floor_boundary(out, len, out_len - 4);
if (len < 0) len = 0;
out[len] = '.';
out[len + 1] = '.';
@@ -32,12 +32,14 @@ static int fitted_prefix_len(const char* src, int max_w, int max_len) {
char tmp[128];
int best = 0;
int limit = max_len < 127 ? max_len : 127;
for (int len = 1; len <= limit; len++) {
for (int len = montauk::utf8::next_boundary(src, max_len, 0); len <= limit;
len = montauk::utf8::next_boundary(src, max_len, len)) {
copy_label_range(tmp, sizeof(tmp), src, 0, len);
if (text_width(tmp) > max_w) break;
best = len;
if (len >= max_len) break;
}
return best > 0 ? best : 1;
return best > 0 ? best : montauk::utf8::next_boundary(src, max_len, 0);
}
static void fit_label_with_ellipsis(char* dst, int dst_max, const char* src, int max_w) {
@@ -49,10 +51,10 @@ static void fit_label_with_ellipsis(char* dst, int dst_max, const char* src, int
while (len > 0) {
dst[len] = '\0';
if (text_width(dst) + ellipsis_w <= max_w) break;
len--;
len = montauk::utf8::prev_boundary(dst, len);
}
if (len > dst_max - 4) len = dst_max - 4;
if (len > dst_max - 4) len = montauk::utf8::floor_boundary(dst, len, dst_max - 4);
if (len < 0) len = 0;
dst[len] = '.';
dst[len + 1] = '.';
@@ -403,7 +405,7 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) {
// Draw rename text (truncated to cell width)
char display[16];
int rlen = fm->rename_len;
if (rlen > 10) rlen = 10;
if (rlen > 10) rlen = montauk::utf8::floor_boundary(fm->rename_buf, fm->rename_len, 10);
for (int k = 0; k < rlen; k++) display[k] = fm->rename_buf[k];
display[rlen] = '\0';
c.text(cell_x + 4, ty, display, colors::TEXT_COLOR);
@@ -557,18 +559,19 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) {
int name_max_w = size_col_x - tx - 8;
if (name_max_w < 20) name_max_w = c.w - FM_SCROLLBAR_W - tx - 8;
char label[64];
montauk::strncpy(label, name, 63);
montauk::utf8::copy(label, 64, name);
if (text_width(label) > name_max_w && montauk::slen(name) > 3) {
int ellipsis_w = text_width("...");
int final_len = montauk::slen(name);
int final_len = montauk::slen(label);
while (final_len > 1) {
label[final_len] = '\0';
int w = text_width(label);
if (w + ellipsis_w <= name_max_w) break;
final_len--;
final_len = montauk::utf8::prev_boundary(label, final_len);
}
if (final_len > 60) final_len = 60;
if (final_len > 60)
final_len = montauk::utf8::floor_boundary(label, final_len, 60);
label[final_len] = '.';
label[final_len + 1] = '.';
label[final_len + 2] = '.';
+1 -1
View File
@@ -44,7 +44,7 @@ void settings_draw_about(Canvas& c, SettingsState* st) {
mtk::draw_separator(c, x, y, c.w - 2 * x, theme);
y += 12;
c.text(x, y, "Copyright \xa9 2026 Montauk Operating System Project", theme.text_muted);
c.text(x, y, "Copyright \u00A9 2026 Montauk Operating System Project", theme.text_muted);
}
} // namespace settings_app
+1 -1
View File
@@ -103,7 +103,7 @@ void desktop_draw_lock_screen(DesktopState* ds) {
// Masked password text (clipped to field width)
char masked[65];
int len = ds->lock_password_len;
int len = montauk::utf8::count(ds->lock_password); // one '*' per character
if (len > 64) len = 64;
int max_text_w = content_w - 10 - 10 - 2; // left pad, right pad, cursor
// Show only the trailing portion that fits
+5 -1
View File
@@ -105,7 +105,11 @@ int wrap_text(const char* text, int max_w, char (*lines)[LINE_CHARS], int max_li
char* last = lines[max_lines - 1];
int len = montauk::slen(last);
if (len > LINE_CHARS - 4) len = LINE_CHARS - 4;
while (len > 0 && text_width(last) + text_width("...") > max_w) last[--len] = '\0';
last[len] = '\0';
while (len > 0 && text_width(last) + text_width("...") > max_w) {
len = montauk::utf8::prev_boundary(last, len);
last[len] = '\0';
}
last[len] = '.'; last[len + 1] = '.'; last[len + 2] = '.'; last[len + 3] = '\0';
}
return count;
+3 -13
View File
@@ -98,22 +98,12 @@ static void handle_lock_keyboard(DesktopState* ds, const montauk::abi::KeyEvent&
// Backspace
if (key.ascii == '\b' || key.scancode == 0x0E) {
if (ds->lock_password_len > 0) {
ds->lock_password_len--;
ds->lock_password[ds->lock_password_len] = '\0';
}
montauk::utf8::erase_last(ds->lock_password, &ds->lock_password_len);
return;
}
// Printable characters
if ((unsigned char)key.ascii >= 0x20
&& (unsigned char)key.ascii != 0x7F) {
if (ds->lock_password_len < 63) {
ds->lock_password[ds->lock_password_len] = key.ascii;
ds->lock_password_len++;
ds->lock_password[ds->lock_password_len] = '\0';
}
}
// Printable characters (stored as UTF-8)
montauk::utf8::append_key(ds->lock_password, &ds->lock_password_len, 64, key);
}
static void forward_external_mouse(gui::Window* win,
+5 -12
View File
@@ -361,7 +361,7 @@ static void launcher_activate_selected(DesktopState* ds) {
}
static void launcher_update_super_state(DesktopState* ds, const montauk::abi::KeyEvent& key) {
uint8_t sc = key.scancode & 0x7F;
uint8_t sc = montauk::nav_scancode(key) & 0x7F;
if (sc == 0x5B) {
ds->super_left_down = key.pressed;
} else if (sc == 0x5C) {
@@ -371,7 +371,7 @@ static void launcher_update_super_state(DesktopState* ds, const montauk::abi::Ke
static const char* fit_trailing_text(const char* text, int max_w) {
const char* visible = text;
while (*visible && text_width(visible) > max_w) visible++;
while (*visible && text_width(visible) > max_w) montauk::utf8::next(&visible);
return visible;
}
@@ -420,7 +420,7 @@ void desktop_close_launcher(DesktopState* ds) {
bool desktop_handle_launcher_keyboard(DesktopState* ds, const montauk::abi::KeyEvent& key) {
launcher_update_super_state(ds, key);
uint8_t sc = key.scancode & 0x7F;
uint8_t sc = montauk::nav_scancode(key) & 0x7F;
if (sc == 0x5B || sc == 0x5C) {
return true;
@@ -456,11 +456,8 @@ bool desktop_handle_launcher_keyboard(DesktopState* ds, const montauk::abi::KeyE
}
if (key.ascii == '\b' || sc == 0x0E) {
if (ds->launcher_query_len > 0) {
ds->launcher_query_len--;
ds->launcher_query[ds->launcher_query_len] = '\0';
if (montauk::utf8::erase_last(ds->launcher_query, &ds->launcher_query_len))
desktop_update_launcher_results(ds);
}
return true;
}
@@ -495,12 +492,8 @@ bool desktop_handle_launcher_keyboard(DesktopState* ds, const montauk::abi::KeyE
return true;
}
if ((unsigned char)key.ascii >= 0x20
&& (unsigned char)key.ascii != 0x7F && ds->launcher_query_len < 63) {
ds->launcher_query[ds->launcher_query_len++] = key.ascii;
ds->launcher_query[ds->launcher_query_len] = '\0';
if (montauk::utf8::append_key(ds->launcher_query, &ds->launcher_query_len, 64, key))
desktop_update_launcher_results(ds);
}
return true;
}