diff --git a/src/gui/wio/app.zig b/src/gui/wio/app.zig index 61ec5a23..0cd9edcd 100644 --- a/src/gui/wio/app.zig +++ b/src/gui/wio/app.zig @@ -490,17 +490,22 @@ fn wioLoop() void { cp.yoff, }) catch {}; } else { - const base_cp = input_translate.codepointFromButton(btn, .{}); - const shifted_cp = if (mods.shift) input_translate.codepointFromButton(btn, .{ .shift = true }) else base_cp; - if (base_cp != 0) sendKey(1, base_cp, shifted_cp, mods); + if (input_translate.codepointFromButton(btn, .{})) |base_cp| { + const shifted_cp = if (mods.shift) input_translate.codepointFromButton(btn, .{ .shift = true }) else base_cp; + sendKey(1, base_cp, shifted_cp orelse base_cp, mods); + } else { + if (input_translate.modifierCodepoint(btn)) |mod_cp| + sendKey(1, mod_cp, mod_cp, mods); + } } }, .button_repeat => |btn| { const mods = input_translate.Mods.fromButtons(held_buttons); if (input_translate.mouseButtonId(btn) == null) { - const base_cp = input_translate.codepointFromButton(btn, .{}); - const shifted_cp = if (mods.shift) input_translate.codepointFromButton(btn, .{ .shift = true }) else base_cp; - if (base_cp != 0) sendKey(2, base_cp, shifted_cp, mods); + if (input_translate.codepointFromButton(btn, .{})) |base_cp| { + const shifted_cp = if (mods.shift) input_translate.codepointFromButton(btn, .{ .shift = true }) else base_cp; + sendKey(2, base_cp, shifted_cp orelse base_cp, mods); + } } }, .button_release => |btn| { @@ -518,9 +523,12 @@ fn wioLoop() void { cp.yoff, }) catch {}; } else { - const base_cp = input_translate.codepointFromButton(btn, .{}); - const shifted_cp = if (mods.shift) input_translate.codepointFromButton(btn, .{ .shift = true }) else base_cp; - if (base_cp != 0) sendKey(3, base_cp, shifted_cp, mods); + if (input_translate.codepointFromButton(btn, .{})) |base_cp| { + const shifted_cp = if (mods.shift) input_translate.codepointFromButton(btn, .{ .shift = true }) else base_cp; + sendKey(3, base_cp, shifted_cp orelse base_cp, mods); + } else if (input_translate.modifierCodepoint(btn)) |mod_cp| { + sendKey(3, mod_cp, mod_cp, mods); + } } }, .char => |cp| { @@ -557,6 +565,18 @@ fn wioLoop() void { }, .unfocused => { window.disableTextInput(); + // Synthesize release events for any modifier keys still held so the TUI + // doesn't see them as stuck. Release in order so mods reflects reality after + // each release. + for (input_translate.modifier_buttons) |mod_btn| { + if (held_buttons.has(mod_btn)) { + held_buttons.release(mod_btn); + const mods = input_translate.Mods.fromButtons(held_buttons); + if (input_translate.modifierCodepoint(mod_btn)) |mod_cp| + sendKey(3, mod_cp, mod_cp, mods); + } + } + held_buttons = .{}; tui_pid.send(.{"focus_out"}) catch {}; }, else => {}, diff --git a/src/gui/wio/input.zig b/src/gui/wio/input.zig index 0d2a6fe9..4c22e413 100644 --- a/src/gui/wio/input.zig +++ b/src/gui/wio/input.zig @@ -58,7 +58,7 @@ pub const KeyEvent = struct { }; // Map a wio.Button to the primary codepoint for that key -pub fn codepointFromButton(b: wio.Button, mods: Mods) u21 { +pub fn codepointFromButton(b: wio.Button, mods: Mods) ?u21 { return switch (b) { .a => if (mods.shiftOnly()) 'A' else 'a', .b => if (mods.shiftOnly()) 'B' else 'b', @@ -153,7 +153,7 @@ pub fn codepointFromButton(b: wio.Button, mods: Mods) u21 { .kp_plus => vaxis.Key.kp_add, .kp_enter => vaxis.Key.kp_enter, .kp_equals => vaxis.Key.kp_equal, - else => 0, + else => null, }; } @@ -161,6 +161,32 @@ pub const mouse_button_left: u8 = 0; pub const mouse_button_middle: u8 = 1; pub const mouse_button_right: u8 = 2; +// Map modifier wio.Button values to kitty protocol codepoints (vaxis.Key.*). +// Returns 0 for non-modifier buttons. +pub fn modifierCodepoint(b: wio.Button) ?u21 { + return switch (b) { + .left_shift => vaxis.Key.left_shift, + .left_control => vaxis.Key.left_control, + .left_alt => vaxis.Key.left_alt, + .left_gui => vaxis.Key.left_super, + .right_shift => vaxis.Key.right_shift, + .right_control => vaxis.Key.right_control, + .right_alt => vaxis.Key.right_alt, + .right_gui => vaxis.Key.right_super, + .caps_lock => vaxis.Key.caps_lock, + .num_lock => vaxis.Key.num_lock, + .scroll_lock => vaxis.Key.scroll_lock, + else => null, + }; +} + +// All buttons that contribute to modifier state, for unfocus cleanup. +pub const modifier_buttons = [_]wio.Button{ + .left_shift, .left_control, .left_alt, .left_gui, + .right_shift, .right_control, .right_alt, .right_gui, + .caps_lock, .num_lock, .scroll_lock, +}; + pub fn mouseButtonId(b: wio.Button) ?u8 { return switch (b) { .mouse_left => mouse_button_left,