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};
+44
View File
@@ -0,0 +1,44 @@
# Makefile for the Keyboard settings applet on MontaukOS
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
ifeq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX = $(error x86_64-montauk toolchain not found. Run ./toolchain/build-montauk-toolchain.sh first)
else
CXX := $(TOOLCHAIN_PREFIX)g++
endif
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
LIBDIR := ../../lib
CXXFLAGS := -std=gnu++20 -g -O2 -pipe -Wall -Wextra -Wno-unused-parameter \
-Wno-unused-function -ffreestanding -fno-stack-protector -fno-stack-check \
-fno-rtti -fno-exceptions -ffunction-sections -fdata-sections -msse -msse2 \
-MMD -MP -I $(PROG_INC) -isystem $(PROG_INC)/libc
LDFLAGS := -nostdlib -Wl,--gc-sections -T $(LINK_LD)
SRCS := main.cpp stb_truetype_impl.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
TARGET := $(BINDIR)/apps/keyboard/keyboard.elf
LIBS := $(LIBDIR)/libc/liblibc.a
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBS)
mkdir -p $(BINDIR)/apps/keyboard
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -c $< -o $@
-include $(OBJS:.o=.d)
clean:
rm -rf $(OBJDIR) $(TARGET)
+353
View File
@@ -0,0 +1,353 @@
/*
* main.cpp
* MontaukOS Keyboard settings applet
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/keyboard.h>
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <gui/mtk.hpp>
#include <gui/mtk/settings.hpp>
#include <gui/standalone.hpp>
extern "C" {
#include <stdio.h>
}
using namespace gui;
static constexpr int WIN_W = 620;
static constexpr int WIN_H = 460;
static constexpr int TAB_H = 36;
static constexpr int FOOTER_H = 44;
static constexpr int PAD = 18;
static constexpr int GAP = 12;
static constexpr int BUTTON_W = 88;
static constexpr int ROW_H = 36;
static constexpr int MAP_ROW_H = 24;
enum Tab { TAB_LAYOUTS = 0, TAB_KEYMAP, TAB_COUNT };
static const char* const kTabLabels[TAB_COUNT] = {"Layouts", "Key Map"};
static Tab g_tab = TAB_LAYOUTS;
static WsWindow g_win;
static montauk::keyboard::State g_state;
static montauk::keyboard::State g_saved;
static char g_username[32] = {};
static int g_mouse_x = -1;
static int g_mouse_y = -1;
static char g_status[96] = {};
static uint64_t g_status_time = 0;
static Color g_accent = colors::ACCENT;
static mtk::Theme app_theme() { return mtk::make_theme(g_accent); }
// ==== Geometry ====
static Rect tab_bar_rect() {
return {0, 0, g_win.width, TAB_H};
}
static Rect footer_rect() {
return {0, g_win.height - FOOTER_H, g_win.width, FOOTER_H};
}
static int heading_y() {
return TAB_H + 20;
}
static int list_separator_y() {
return heading_y() + system_font_height() + 20;
}
static int list_header_y() {
return list_separator_y() + 16;
}
static int list_y() {
return list_header_y() + system_font_height() + 8;
}
static Rect row_rect(int index) {
return {PAD, list_y() + index * ROW_H, g_win.width - PAD * 2, ROW_H};
}
// The checkbox owns only its indicator; the rest of the row activates the
// layout, matching how the Display applet's mode rows select on row click.
static Rect row_checkbox_rect(int index) {
Rect row = row_rect(index);
return {row.x + 8, row.y, row.w - 16, row.h};
}
static Rect row_check_hit(int index) {
Rect box = mtk::checkbox_indicator_rect(row_checkbox_rect(index));
return {box.x - 6, box.y - 6, box.w + 12, box.h + 12};
}
// Rows the key map can show without running into the footer.
static int map_visible_rows() {
int avail = footer_rect().y - 10 - list_y();
int rows = avail / MAP_ROW_H;
return rows < 0 ? 0 : rows;
}
static Rect apply_button() {
Rect foot = footer_rect();
return {foot.x + foot.w - PAD - BUTTON_W, foot.y + 7, BUTTON_W, 30};
}
static Rect revert_button() {
Rect apply = apply_button();
return {apply.x - GAP - BUTTON_W, apply.y, BUTTON_W, apply.h};
}
// ==== State ====
static bool state_equal(const montauk::keyboard::State& a,
const montauk::keyboard::State& b) {
if (a.active != b.active || a.registry.count != b.registry.count) return false;
for (int i = 0; i < a.registry.count; i++)
if (a.enabled[i] != b.enabled[i]) return false;
return true;
}
static bool dirty() { return !state_equal(g_state, g_saved); }
static void set_status(const char* text) {
montauk::strncpy(g_status, text, sizeof(g_status));
g_status_time = montauk::get_milliseconds();
}
static bool status_visible() {
return g_status[0] && montauk::get_milliseconds() - g_status_time < 4000;
}
static int enabled_count() {
int count = 0;
for (int i = 0; i < g_state.registry.count; i++)
if (g_state.enabled[i]) count++;
return count;
}
static mtk::WidgetState button_state(const Rect& rect, bool enabled = true) {
return mtk::widget_state(false, rect.contains(g_mouse_x, g_mouse_y), enabled);
}
// ==== Rendering ====
static void draw_layouts_tab(Canvas& c, const mtk::Theme& theme) {
c.text(PAD, heading_y(),
"Enabled layouts appear in the panel switcher.", theme.text_subtle);
mtk::draw_separator(c, PAD, list_separator_y(),
g_win.width - PAD * 2, theme);
c.text(PAD, list_header_y(), "AVAILABLE LAYOUTS", theme.text_muted);
const char* hint = "Click a layout to make it active";
c.text(g_win.width - PAD - text_width(hint), list_header_y(),
hint, theme.text_muted);
for (int i = 0; i < g_state.registry.count; i++) {
Rect row = row_rect(i);
bool active = g_state.active == i;
if (active)
c.fill_rounded_rect(row.x, row.y + 2, row.w, row.h - 4, 4,
theme.accent_soft);
mtk::draw_checkbox(c, row_checkbox_rect(i),
g_state.registry.items[i].name,
mtk::check_state(g_state.enabled[i]), theme, true,
row_check_hit(i).contains(g_mouse_x, g_mouse_y));
// Layout tag ("en", "no") after the name, then the active marker.
const char* tag = g_state.registry.items[i].short_name;
int text_y = row.y + (row.h - system_font_height()) / 2;
int name_w = text_width(g_state.registry.items[i].name);
c.text(row.x + 8 + 16 + theme.gap_sm + name_w + theme.gap_sm, text_y,
tag, theme.text_muted);
if (active) {
const char* state_text = "Active";
c.text(row.x + row.w - 10 - text_width(state_text), text_y,
state_text, theme.text_subtle);
}
}
}
// Key Map tab: what the active layout actually remaps. Rows are identified by
// the character the key produces unshifted, so no copy of the US scancode
// table is needed here to name the physical keys.
static void draw_char_cell(Canvas& c, int x, int y, uint8_t byte,
const mtk::Theme& theme) {
char text[2] = {(char)byte, '\0'};
if (byte == 0) {
c.text(x, y, "-", theme.text_muted);
} else {
c.text(x, y, text, theme.text);
}
}
static void draw_keymap_tab(Canvas& c, const mtk::Theme& theme) {
const montauk::keyboard::Layout& layout =
g_state.registry.items[g_state.active];
char heading[96];
snprintf(heading, sizeof(heading), "Key map for %s", layout.name);
c.text(PAD, heading_y(), heading, theme.text_subtle);
mtk::draw_separator(c, PAD, list_separator_y(),
g_win.width - PAD * 2, theme);
if (layout.key_count == 0) {
c.text(PAD, list_header_y(),
"This layout uses the built-in key map. No keys are remapped.",
theme.text_muted);
return;
}
int group_w = (g_win.width - PAD * 2 - GAP) / 2;
int per_group = (layout.key_count + 1) / 2;
int shown = per_group;
if (shown > map_visible_rows()) shown = map_visible_rows();
const int col[3] = {0, 74, 148};
for (int group = 0; group < 2; group++) {
int gx = PAD + group * (group_w + GAP);
c.text(gx + col[0], list_header_y(), "NORMAL", theme.text_muted);
c.text(gx + col[1], list_header_y(), "SHIFT", theme.text_muted);
c.text(gx + col[2], list_header_y(), "ALTGR", theme.text_muted);
for (int row = 0; row < shown; row++) {
int index = group * per_group + row;
if (index >= layout.key_count) break;
const montauk::keyboard::KeyMap& key = layout.keys[index];
int y = list_y() + row * MAP_ROW_H;
draw_char_cell(c, gx + col[0], y, key.base, theme);
draw_char_cell(c, gx + col[1], y, key.shift, theme);
draw_char_cell(c, gx + col[2], y, key.altgr, theme);
}
}
if (shown < per_group) {
char note[64];
snprintf(note, sizeof(note), "%d more keys not shown",
layout.key_count - shown * 2);
c.text(PAD, list_y() + shown * MAP_ROW_H + 4, note, theme.text_muted);
}
}
static void draw_footer(Canvas& c, const mtk::Theme& theme) {
Rect foot = footer_rect();
c.fill_rect(foot.x, foot.y, foot.w, foot.h, theme.surface);
mtk::draw_separator(c, 0, foot.y, g_win.width, theme);
const char* message = status_visible()
? g_status
: (dirty() ? "Unsaved keyboard changes" : "Keyboard settings ready");
c.text(PAD, foot.y + (foot.h - system_font_height()) / 2,
message, dirty() ? theme.text : theme.text_subtle);
if (g_tab == TAB_LAYOUTS) {
mtk::draw_button(c, revert_button(), "Revert", mtk::BUTTON_SECONDARY,
button_state(revert_button(), dirty()), theme);
mtk::draw_button(c, apply_button(), "Apply", mtk::BUTTON_PRIMARY,
button_state(apply_button(), dirty()), theme);
}
}
static void render() {
mtk::StandaloneHost host(&g_win);
Canvas c = host.canvas();
mtk::Theme theme = app_theme();
c.fill(theme.window_bg);
mtk::draw_tab_bar(c, tab_bar_rect(), kTabLabels, TAB_COUNT, (int)g_tab, theme);
if (g_tab == TAB_LAYOUTS) draw_layouts_tab(c, theme);
else draw_keymap_tab(c, theme);
draw_footer(c, theme);
host.present();
}
// ==== Input ====
static void toggle_enabled(int index) {
if (g_state.enabled[index] && enabled_count() == 1) {
set_status("At least one layout must remain enabled");
return;
}
g_state.enabled[index] = !g_state.enabled[index];
if (!g_state.enabled[g_state.active])
g_state.active = montauk::keyboard::next_enabled(g_state, g_state.active);
}
static bool handle_mouse(const montauk::abi::WinEvent& ev) {
g_mouse_x = ev.mouse.x;
g_mouse_y = ev.mouse.y;
bool pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
if (!pressed) return true;
int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, g_mouse_x, g_mouse_y);
if (tab >= 0) {
g_tab = (Tab)tab;
return true;
}
if (g_tab != TAB_LAYOUTS) return true;
for (int i = 0; i < g_state.registry.count; i++) {
if (row_check_hit(i).contains(g_mouse_x, g_mouse_y)) {
toggle_enabled(i);
return true;
}
if (row_rect(i).contains(g_mouse_x, g_mouse_y)) {
if (g_state.enabled[i]) {
g_state.active = i;
} else {
set_status("Enable a layout before making it active");
}
return true;
}
}
if (!dirty()) return true;
if (revert_button().contains(g_mouse_x, g_mouse_y)) {
g_state = g_saved;
set_status("Changes reverted");
} else if (apply_button().contains(g_mouse_x, g_mouse_y)) {
if (montauk::keyboard::save_user(g_username, g_state)) {
g_saved = g_state;
set_status("Keyboard layouts applied");
} else {
set_status("Could not save keyboard settings");
}
}
return true;
}
extern "C" void _start() {
if (!fonts::init()) montauk::exit(1);
if (montauk::getuser(g_username, sizeof(g_username)) <= 0)
montauk::strcpy(g_username, "default");
g_accent = mtk::load_system_accent();
g_state = montauk::keyboard::load_user(g_username);
g_saved = g_state;
if (!g_win.create("Keyboard", WIN_W, WIN_H)) montauk::exit(1);
render();
while (g_win.id >= 0 && !g_win.closed) {
montauk::abi::WinEvent ev;
int result = g_win.poll(&ev);
if (result < 0) break;
if (result == 0) {
if (status_visible()) render();
montauk::sleep_ms(16);
continue;
}
if (ev.type == 3) break;
if (ev.type == 1) handle_mouse(ev);
else if (ev.type == 0 && ev.key.pressed && ev.key.scancode == 0x01)
g_win.closed = true;
render();
}
g_win.destroy();
montauk::exit(0);
}
+13
View File
@@ -0,0 +1,13 @@
[app]
id = "keyboard"
name = "Keyboard"
binary = "keyboard.elf"
icon = "preferences-keyboard.svg"
[menu]
category = "System"
visible = false
[desktop]
section = "settings"
admin_only = false
@@ -0,0 +1,2 @@
#define STB_TRUETYPE_IMPLEMENTATION
#include <gui/stb_truetype.h>
+4
View File
@@ -96,6 +96,10 @@ extern "C" void _start() {
while (montauk::is_key_available()) {
montauk::abi::KeyEvent key;
montauk::getkey(&key);
// Deliberately untranslated: the login screen is US-English only
// until it grows its own layout switcher. There is no user yet, so
// there is no per-user layout to apply, and silently picking one
// would leave no way to type a password in a different layout.
handle_key(ls, key);
key_changed = true;
}
+4 -3
View File
@@ -415,8 +415,9 @@ static char ascii_lower(char c) {
return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c;
}
static bool is_printable_ascii(char c) {
return c >= 32 && c < 127;
static bool is_printable_byte(char c) {
unsigned char value = (unsigned char)c;
return value >= 32 && value != 127;
}
static bool str_contains_case_insensitive(const char* text, const char* needle) {
@@ -1448,7 +1449,7 @@ static bool handle_key(const montauk::abi::KeyEvent& key, bool& quit) {
else g.filter_active = false;
return true;
}
if (!key.ctrl && !key.alt && is_printable_ascii(key.ascii)) {
if (!key.ctrl && !key.alt && is_printable_byte(key.ascii)) {
append_filter_char(key.ascii);
return true;
}
+2 -1
View File
@@ -246,7 +246,8 @@ extern "C" void _start() {
if (g_pathbar_cursor > 0) g_pathbar_cursor--;
} else if (key.scancode == 0x4D) {
if (g_pathbar_cursor < g_pathbar_len) g_pathbar_cursor++;
} else if (key.ascii >= 32 && key.ascii < 127 && g_pathbar_len < 254) {
} else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127 && g_pathbar_len < 254) {
for (int i = g_pathbar_len; i > g_pathbar_cursor; i--)
g_pathbar_text[i] = g_pathbar_text[i - 1];
g_pathbar_text[g_pathbar_cursor] = key.ascii;
+4 -2
View File
@@ -426,7 +426,8 @@ extern "C" void _start() {
if (g_pathbar_cursor > 0) g_pathbar_cursor--;
} else if (key.scancode == 0x4D) {
if (g_pathbar_cursor < g_pathbar_len) g_pathbar_cursor++;
} else if (key.ascii >= 32 && key.ascii < 127 && g_pathbar_len < 254) {
} else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127 && g_pathbar_len < 254) {
for (int i = g_pathbar_len; i > g_pathbar_cursor; i--)
g_pathbar_text[i] = g_pathbar_text[i - 1];
g_pathbar_text[g_pathbar_cursor] = key.ascii;
@@ -669,7 +670,8 @@ extern "C" void _start() {
redraw = true;
}
// Printable character: start editing or insert
else if (key.ascii >= 32 && key.ascii < 127 && !key.ctrl) {
else if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127 && !key.ctrl) {
if (!g_editing) {
clear_selection();
g_editing = true;
+14
View File
@@ -14,6 +14,7 @@
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <montauk/keyboard.h>
#include <gui/gui.hpp>
#include <gui/canvas.hpp>
#include <gui/draw.hpp>
@@ -392,6 +393,14 @@ static void term_handle_mouse(const montauk::abi::WinEvent& ev) {
}
// Keyboard handling shared by both run modes.
// User whose keyboard layout selection console mode follows.
static char g_layout_user[32] = "default";
static void init_layout_user() {
if (montauk::getuser(g_layout_user, sizeof(g_layout_user)) <= 0)
montauk::strcpy(g_layout_user, "default");
}
static void term_handle_key_core(const montauk::abi::KeyEvent& key,
int width, int height) {
if (g_tabs.tab_count <= 0) return;
@@ -586,6 +595,8 @@ static void run_console() {
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
init_layout_user();
g_tabs.tabs[0] = term_create_tab(cols, rows);
if (!g_tabs.tabs[0])
montauk::exit(1);
@@ -610,6 +621,9 @@ static void run_console() {
while (montauk::is_key_available()) {
montauk::abi::KeyEvent key;
montauk::getkey(&key);
// Console mode reads the kernel buffer directly, so it does not
// get the desktop's layout translation the windowed path does.
montauk::keyboard::translate_direct(&key, g_layout_user);
term_handle_key_core(key, sw, sh);
redraw = true;
if (g_should_exit) break;
+3 -2
View File
@@ -1063,7 +1063,8 @@ static bool handle_key(montauk::abi::KeyEvent& key, int win_id) {
}
if (key.scancode == 0x4B) { if (g_pathbar_cursor > 0) g_pathbar_cursor--; return true; }
if (key.scancode == 0x4D) { if (g_pathbar_cursor < g_pathbar_len) g_pathbar_cursor++; return true; }
if (key.ascii >= 32 && key.ascii < 127 && g_pathbar_len < 254) {
if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127 && g_pathbar_len < 254) {
for (int i = g_pathbar_len; i > g_pathbar_cursor; i--)
g_pathbar_text[i] = g_pathbar_text[i - 1];
g_pathbar_text[g_pathbar_cursor] = key.ascii;
@@ -1214,7 +1215,7 @@ static bool handle_key(montauk::abi::KeyEvent& key, int win_id) {
}
// Printable
if (key.ascii >= 32 && key.ascii < 127) {
if ((unsigned char)key.ascii >= 32 && (unsigned char)key.ascii != 127) {
if (g_has_selection) delete_selection();
insert_char(key.ascii);
return true;
+2 -2
View File
@@ -302,7 +302,7 @@ static void sort_countries() {
}
static void load_timezone_data() {
g_data = montauk::config::load("timezonedata");
g_data = montauk::data::load("timezones");
g_data_loaded = true;
g_country_count = 0;
@@ -798,7 +798,7 @@ static void render() {
if (g_tab == TAB_TIME_ZONES) {
if (g_country_count <= 0) {
canvas.text(PAD, TAB_H + 24,
"No time zone data was found in 0:/config/timezonedata.toml",
"No time zone data was found in 0:/os/data/timezones.toml",
theme.danger);
} else {
Rect country = country_list_rect();
+3 -2
View File
@@ -628,7 +628,8 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
if (wp->pathbar_cursor < wp->pathbar_len) wp->pathbar_cursor++;
return;
}
if (key.ascii >= 32 && key.ascii < 127 && wp->pathbar_len < 254) {
if ((unsigned char)key.ascii >= 32
&& (unsigned char)key.ascii != 127 && wp->pathbar_len < 254) {
for (int i = wp->pathbar_len; i > wp->pathbar_cursor; i--)
wp->pathbar_text[i] = wp->pathbar_text[i - 1];
wp->pathbar_text[wp->pathbar_cursor] = key.ascii;
@@ -885,7 +886,7 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
return;
}
if (key.ascii >= 32 && key.ascii < 127) {
if ((unsigned char)key.ascii >= 32 && (unsigned char)key.ascii != 127) {
if (wp->has_selection) wp_delete_selection(wp);
wp_insert_char(wp, key.ascii);
wp_history_checkpoint(wp);