fix(gui): synthesize modifier release events on unfocus

This commit is contained in:
CJ van den Berg 2026-04-09 09:17:07 +02:00
parent 88c1c20340
commit bddf06c633
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 57 additions and 11 deletions

View file

@ -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 => {},

View file

@ -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,