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:
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] = '.';
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user