feat: add keyboard layout support

This commit is contained in:
2026-08-13 17:57:44 +02:00
parent 86e3ebfe33
commit 821543238d
36 changed files with 1132 additions and 125 deletions
@@ -352,7 +352,8 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
fm->pathbar_cursor = 0;
} else if (key.scancode == 0x4F) {
fm->pathbar_cursor = fm->pathbar_len;
} else if (key.ascii >= 32 && key.ascii < 127 && fm->pathbar_len < 254) {
} 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;
@@ -399,7 +400,8 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
} else if (key.scancode == 0x4F) {
// End
fm->rename_cursor = fm->rename_len;
} else if (key.ascii >= 32 && key.ascii < 127) {
} else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127) {
// 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--)
+56 -14
View File
@@ -7,6 +7,27 @@
#include "desktop_internal.hpp"
#include <montauk/user.h>
bool gui::desktop_refresh_keyboard_layouts(DesktopState* ds, bool force) {
if (!ds) return false;
uint64_t now = montauk::get_milliseconds();
if (!force && now - ds->keyboard_last_poll < 1000) return false;
// Only the selection can change while we run: the layout tables are
// read-only OS data, so there is no reason to re-parse them every second.
int prev_active = ds->keyboard.active;
bool prev_enabled[montauk::keyboard::MAX_LAYOUTS];
for (int i = 0; i < montauk::keyboard::MAX_LAYOUTS; i++)
prev_enabled[i] = ds->keyboard.enabled[i];
montauk::keyboard::refresh_selection(&ds->keyboard, ds->current_user);
bool changed = ds->keyboard.active != prev_active;
for (int i = 0; !changed && i < ds->keyboard.registry.count; i++)
changed = ds->keyboard.enabled[i] != prev_enabled[i];
ds->keyboard_last_poll = now;
return changed;
}
static bool try_unlock(DesktopState* ds) {
ds->lock_show_error = false;
@@ -85,7 +106,8 @@ static void handle_lock_keyboard(DesktopState* ds, const montauk::abi::KeyEvent&
}
// Printable characters
if (key.ascii >= 0x20 && key.ascii < 0x7F) {
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++;
@@ -538,6 +560,22 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
return;
}
// Keyboard layout indicator: cycle through the layouts enabled in the
// Keyboard Settings applet and persist the new active choice.
if (ds->keyboard_layout_rect.w > 0
&& ds->keyboard_layout_rect.contains(mx, my)) {
ds->keyboard.active = montauk::keyboard::next_enabled(
ds->keyboard, ds->keyboard.active);
montauk::keyboard::save_user(ds->current_user, ds->keyboard);
ds->keyboard_last_poll = montauk::get_milliseconds();
ds->app_menu_open = false;
ds->net_popup_open = false;
ds->wifi_popup_open = false;
ds->vol_popup_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
return;
}
// Volume icon
if (ds->vol_icon_rect.w > 0 && ds->vol_icon_rect.contains(mx, my)) {
ds->vol_popup_open = !ds->vol_popup_open;
@@ -801,8 +839,12 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
}
void gui::desktop_handle_keyboard(DesktopState* ds, const montauk::abi::KeyEvent& key) {
montauk::abi::KeyEvent translated_key = key;
montauk::keyboard::translate(ds->keyboard, &translated_key);
const montauk::abi::KeyEvent& routed_key = translated_key;
if (ds->screen_locked) {
handle_lock_keyboard(ds, key);
handle_lock_keyboard(ds, routed_key);
return;
}
@@ -816,7 +858,7 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const montauk::abi::KeyEvent
montauk::abi::WinEvent ev;
montauk::memset(&ev, 0, sizeof(ev));
ev.type = 0; // key
ev.key = key;
ev.key = routed_key;
montauk::win_sendevent(win->ext_win_id, &ev);
ds->launcher_open = false;
ds->app_menu_open = false;
@@ -824,37 +866,37 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const montauk::abi::KeyEvent
return;
}
if (desktop_handle_launcher_keyboard(ds, key)) {
if (desktop_handle_launcher_keyboard(ds, routed_key)) {
return;
}
// Global shortcuts (only on key press)
if (key.pressed && key.ctrl && key.alt) {
if (key.ascii == 'l' || key.ascii == 'L') {
if (routed_key.pressed && routed_key.ctrl && routed_key.alt) {
if (routed_key.ascii == 'l' || routed_key.ascii == 'L') {
desktop_lock_screen(ds);
return;
}
if (key.ascii == 't' || key.ascii == 'T') {
if (routed_key.ascii == 't' || routed_key.ascii == 'T') {
open_terminal(ds);
return;
}
if (key.ascii == 'f' || key.ascii == 'F') {
if (routed_key.ascii == 'f' || routed_key.ascii == 'F') {
open_filemanager(ds);
return;
}
if (key.ascii == 'c' || key.ascii == 'C') {
if (routed_key.ascii == 'c' || routed_key.ascii == 'C') {
open_calculator(ds);
return;
}
if (key.ascii == 'e' || key.ascii == 'E') {
if (routed_key.ascii == 'e' || routed_key.ascii == 'E') {
open_texteditor(ds);
return;
}
if (key.ascii == 'w' || key.ascii == 'W') {
if (routed_key.ascii == 'w' || routed_key.ascii == 'W') {
open_wordprocessor(ds);
return;
}
if (key.ascii == 'k' || key.ascii == 'K') {
if (routed_key.ascii == 'k' || routed_key.ascii == 'K') {
open_klog(ds);
return;
}
@@ -868,10 +910,10 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const montauk::abi::KeyEvent
montauk::abi::WinEvent ev;
montauk::memset(&ev, 0, sizeof(ev));
ev.type = 0; // key
ev.key = key;
ev.key = routed_key;
montauk::win_sendevent(win->ext_win_id, &ev);
} else if (win->on_key) {
win->on_key(win, key);
win->on_key(win, routed_key);
}
}
}
+2 -1
View File
@@ -494,7 +494,8 @@ bool desktop_handle_launcher_keyboard(DesktopState* ds, const montauk::abi::KeyE
return true;
}
if (key.ascii >= 0x20 && key.ascii < 0x7F && ds->launcher_query_len < 63) {
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';
desktop_update_launcher_results(ds);
+5
View File
@@ -405,6 +405,10 @@ void gui::desktop_init(DesktopState* ds) {
ds->vol_output = montauk::audio_get_output(-1);
ds->vol_bt_status = montauk::audio_bt_status(-1);
ds->keyboard = montauk::keyboard::load_user(ds->current_user);
ds->keyboard_layout_rect = {0, 0, 0, 0};
ds->keyboard_last_poll = montauk::get_milliseconds();
ds->closing_ext_count = 0;
ds->screen_locked = false;
@@ -813,6 +817,7 @@ void gui::desktop_run(DesktopState* ds) {
}
uint64_t now = montauk::get_milliseconds();
sceneChanged |= desktop_refresh_keyboard_layouts(ds, false);
sceneChanged |= desktop_refresh_panel_state(ds, now);
// Pick up any wallpaper buffer produced by a background loader.
+28 -2
View File
@@ -108,8 +108,34 @@ void gui::desktop_draw_panel(DesktopState* ds) {
int date_x = clock_x - date_w - 10;
draw_text(fb, date_x, clock_y, date_str, colors::PANEL_TEXT);
// Volume icon (to the left of the date)
int vol_icon_x = date_x - 16 - 12;
// Only render switcher if more than one keyboard layout is installed
int enabled_layouts = 0;
for (int i = 0; i < ds->keyboard.registry.count; i++)
if (ds->keyboard.enabled[i]) enabled_layouts++;
// Whatever text ends up leftmost is what the volume icon spaces off.
int vol_anchor_x = date_x;
if (enabled_layouts > 1) {
const char* layout_short = "en";
if (ds->keyboard.active >= 0
&& ds->keyboard.active < ds->keyboard.registry.count) {
layout_short =
ds->keyboard.registry.items[ds->keyboard.active].short_name;
}
int layout_tw = text_width(layout_short);
int layout_x = date_x - layout_tw - 10;
// Padded evenly past the glyphs so the click target is comfortable.
ds->keyboard_layout_rect = {layout_x - 5, 4, layout_tw + 10, 24};
draw_text(fb, layout_x, clock_y, layout_short, colors::PANEL_TEXT);
vol_anchor_x = layout_x;
} else {
ds->keyboard_layout_rect = {0, 0, 0, 0};
}
// Volume icon keeps its 12px icon-to-text gap whether it sits next to the
// layout tag or, with one layout, the date.
int vol_icon_x = vol_anchor_x - 12 - 16;
int vol_icon_y = (PANEL_HEIGHT - 16) / 2;
ds->vol_icon_rect = {vol_icon_x, vol_icon_y, 16, 16};