feat(gui): add proper support for syncing modifiers on every event
This commit is contained in:
parent
e299638f8b
commit
fa7b2e1e0a
3 changed files with 98 additions and 89 deletions
|
|
@ -43,8 +43,8 @@
|
|||
.lazy = true,
|
||||
},
|
||||
.wio = .{
|
||||
.url = "git+https://github.com/neurocyte/wio?ref=master#6186501162ace8645036e71fdb7ebad76a0dca71",
|
||||
.hash = "wio-0.0.0-8xHrr8XzBQAYPvWczIC0RU40hP-cVH2HR1zcTVfP7r8l",
|
||||
.url = "git+https://github.com/neurocyte/wio?ref=master#a5f4ccb81fb6bafa348196747a0051cc65e10db9",
|
||||
.hash = "wio-0.0.0-8xHrr0MKBgAYo_rlL6A2llfOLjikoWrPdV6Aml3OOBwJ",
|
||||
.lazy = true,
|
||||
},
|
||||
.sokol = .{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ const gui_config = @import("gui_config");
|
|||
|
||||
const log = std.log.scoped(.wio_app);
|
||||
|
||||
const press: u8 = 1;
|
||||
const repeat: u8 = 2;
|
||||
const release: u8 = 3;
|
||||
|
||||
// Re-export cursor types so renderer.zig (which imports 'app' but not 'gpu')
|
||||
// can use them without a direct dependency on the gpu module.
|
||||
pub const CursorInfo = gpu.CursorInfo;
|
||||
|
|
@ -47,6 +51,7 @@ var screen_mutex: std.Thread.Mutex = .{};
|
|||
var screen_pending: std.atomic.Value(bool) = .init(false);
|
||||
var screen_snap: ?ScreenSnapshot = null;
|
||||
var tui_pid: thespian.pid = undefined;
|
||||
var last_mods: input_translate.Mods = .{};
|
||||
var font_size_px: u16 = 16;
|
||||
var font_name_buf: [256]u8 = undefined;
|
||||
var font_name_len: usize = 0;
|
||||
|
|
@ -481,7 +486,7 @@ fn wioLoop() void {
|
|||
},
|
||||
.button_press => |btn| {
|
||||
held_buttons.press(btn);
|
||||
const mods = input_translate.Mods.fromButtons(held_buttons);
|
||||
const mods = syncModifiers();
|
||||
if (input_translate.mouseButtonId(btn)) |mb_id| {
|
||||
const cp = pixelToCellPos(mouse_pos);
|
||||
tui_pid.send(.{
|
||||
|
|
@ -496,15 +501,15 @@ fn wioLoop() void {
|
|||
} else {
|
||||
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);
|
||||
sendKey(press, base_cp, shifted_cp orelse base_cp, mods);
|
||||
} else {
|
||||
if (input_translate.modifierCodepoint(btn)) |mod_cp|
|
||||
sendKey(1, mod_cp, mod_cp, mods);
|
||||
sendKey(press, mod_cp, mod_cp, mods);
|
||||
}
|
||||
}
|
||||
},
|
||||
.button_repeat => |btn| {
|
||||
const mods = input_translate.Mods.fromButtons(held_buttons);
|
||||
const mods = syncModifiers();
|
||||
if (input_translate.mouseButtonId(btn) == null) {
|
||||
if (input_translate.codepointFromButton(btn, .{})) |base_cp| {
|
||||
const shifted_cp = if (mods.shift) input_translate.codepointFromButton(btn, .{ .shift = true }) else base_cp;
|
||||
|
|
@ -514,7 +519,7 @@ fn wioLoop() void {
|
|||
},
|
||||
.button_release => |btn| {
|
||||
held_buttons.release(btn);
|
||||
const mods = input_translate.Mods.fromButtons(held_buttons);
|
||||
const mods = syncModifiers();
|
||||
if (input_translate.mouseButtonId(btn)) |mb_id| {
|
||||
const cp = pixelToCellPos(mouse_pos);
|
||||
tui_pid.send(.{
|
||||
|
|
@ -540,8 +545,8 @@ fn wioLoop() void {
|
|||
// ASCII keys are fully handled by .button_press with correct
|
||||
// base/shifted codepoints, avoiding double-firing on X11.
|
||||
if (cp > 0x7f) {
|
||||
const mods = input_translate.Mods.fromButtons(held_buttons);
|
||||
sendKey(1, cp, cp, mods);
|
||||
const mods = syncModifiers();
|
||||
sendKey(press, cp, cp, mods);
|
||||
}
|
||||
},
|
||||
.mouse => |pos| {
|
||||
|
|
@ -564,23 +569,12 @@ fn wioLoop() void {
|
|||
tui_pid.send(.{ "RDR", "B", @as(u8, 1), btn_id, cp.col, cp.row, cp.xoff, cp.yoff }) catch {};
|
||||
},
|
||||
.focused => {
|
||||
_ = syncModifiers();
|
||||
window.enableTextInput(.{});
|
||||
tui_pid.send(.{"focus_in"}) catch {};
|
||||
},
|
||||
.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 => {
|
||||
|
|
@ -722,3 +716,27 @@ fn sendKey(kind: u8, codepoint: u21, shifted_codepoint: u21, mods: input_transla
|
|||
@as(u8, @bitCast(mods)),
|
||||
}) catch {};
|
||||
}
|
||||
|
||||
fn syncModifiers() input_translate.Mods {
|
||||
const mods = input_translate.fromWioModifiers(wio.getModifiers());
|
||||
// Synthesize release events for any modifier keys no
|
||||
// longer held so they don't appear stuck.
|
||||
if (mods.shift != last_mods.shift) {
|
||||
last_mods.shift = mods.shift;
|
||||
sendKey(if (last_mods.shift) press else release, vaxis.Key.left_shift, vaxis.Key.left_shift, last_mods);
|
||||
}
|
||||
if (mods.alt != last_mods.alt) {
|
||||
last_mods.alt = mods.alt;
|
||||
sendKey(if (last_mods.alt) press else release, vaxis.Key.left_alt, vaxis.Key.left_alt, last_mods);
|
||||
}
|
||||
if (mods.ctrl != last_mods.ctrl) {
|
||||
last_mods.ctrl = mods.ctrl;
|
||||
sendKey(if (last_mods.ctrl) press else release, vaxis.Key.left_control, vaxis.Key.left_control, last_mods);
|
||||
}
|
||||
if (mods.super != last_mods.super) {
|
||||
last_mods.super = mods.super;
|
||||
sendKey(if (last_mods.super) press else release, vaxis.Key.left_super, vaxis.Key.left_super, last_mods);
|
||||
}
|
||||
last_mods = mods;
|
||||
return mods;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,29 +7,20 @@ const vaxis = @import("vaxis");
|
|||
|
||||
// Modifiers bitmask (matches vaxis.Key.Modifiers packed struct layout used
|
||||
// by the rest of Flow's input handling).
|
||||
pub const Mods = packed struct(u8) {
|
||||
shift: bool = false,
|
||||
alt: bool = false,
|
||||
ctrl: bool = false,
|
||||
super: bool = false,
|
||||
hyper: bool = false,
|
||||
meta: bool = false,
|
||||
_pad: u2 = 0,
|
||||
pub const Mods = vaxis.Key.Modifiers;
|
||||
|
||||
pub fn fromButtons(pressed: ButtonSet) Mods {
|
||||
pub fn fromWioModifiers(modifiers: wio.Modifiers) Mods {
|
||||
return .{
|
||||
.shift = pressed.has(.left_shift) or pressed.has(.right_shift),
|
||||
.alt = pressed.has(.left_alt) or pressed.has(.right_alt),
|
||||
.ctrl = pressed.has(.left_control) or pressed.has(.right_control),
|
||||
.super = pressed.has(.left_gui) or pressed.has(.right_gui),
|
||||
.shift = modifiers.shift,
|
||||
.alt = modifiers.alt,
|
||||
.ctrl = modifiers.control,
|
||||
.super = modifiers.super,
|
||||
};
|
||||
}
|
||||
|
||||
/// True only when shift is the sole active modifier.
|
||||
pub fn shiftOnly(self: Mods) bool {
|
||||
return self.shift and !self.alt and !self.ctrl and !self.super and !self.hyper and !self.meta;
|
||||
pub fn isShifted(mods: Mods) bool {
|
||||
return mods.shift and !mods.alt and !mods.ctrl and !mods.super and !mods.hyper and !mods.meta;
|
||||
}
|
||||
};
|
||||
|
||||
// Simple set of currently held buttons (for modifier tracking)
|
||||
pub const ButtonSet = struct {
|
||||
|
|
@ -60,58 +51,58 @@ pub const KeyEvent = struct {
|
|||
// Map a wio.Button to the primary codepoint for that key
|
||||
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',
|
||||
.c => if (mods.shiftOnly()) 'C' else 'c',
|
||||
.d => if (mods.shiftOnly()) 'D' else 'd',
|
||||
.e => if (mods.shiftOnly()) 'E' else 'e',
|
||||
.f => if (mods.shiftOnly()) 'F' else 'f',
|
||||
.g => if (mods.shiftOnly()) 'G' else 'g',
|
||||
.h => if (mods.shiftOnly()) 'H' else 'h',
|
||||
.i => if (mods.shiftOnly()) 'I' else 'i',
|
||||
.j => if (mods.shiftOnly()) 'J' else 'j',
|
||||
.k => if (mods.shiftOnly()) 'K' else 'k',
|
||||
.l => if (mods.shiftOnly()) 'L' else 'l',
|
||||
.m => if (mods.shiftOnly()) 'M' else 'm',
|
||||
.n => if (mods.shiftOnly()) 'N' else 'n',
|
||||
.o => if (mods.shiftOnly()) 'O' else 'o',
|
||||
.p => if (mods.shiftOnly()) 'P' else 'p',
|
||||
.q => if (mods.shiftOnly()) 'Q' else 'q',
|
||||
.r => if (mods.shiftOnly()) 'R' else 'r',
|
||||
.s => if (mods.shiftOnly()) 'S' else 's',
|
||||
.t => if (mods.shiftOnly()) 'T' else 't',
|
||||
.u => if (mods.shiftOnly()) 'U' else 'u',
|
||||
.v => if (mods.shiftOnly()) 'V' else 'v',
|
||||
.w => if (mods.shiftOnly()) 'W' else 'w',
|
||||
.x => if (mods.shiftOnly()) 'X' else 'x',
|
||||
.y => if (mods.shiftOnly()) 'Y' else 'y',
|
||||
.z => if (mods.shiftOnly()) 'Z' else 'z',
|
||||
.@"0" => if (mods.shiftOnly()) ')' else '0',
|
||||
.@"1" => if (mods.shiftOnly()) '!' else '1',
|
||||
.@"2" => if (mods.shiftOnly()) '@' else '2',
|
||||
.@"3" => if (mods.shiftOnly()) '#' else '3',
|
||||
.@"4" => if (mods.shiftOnly()) '$' else '4',
|
||||
.@"5" => if (mods.shiftOnly()) '%' else '5',
|
||||
.@"6" => if (mods.shiftOnly()) '^' else '6',
|
||||
.@"7" => if (mods.shiftOnly()) '&' else '7',
|
||||
.@"8" => if (mods.shiftOnly()) '*' else '8',
|
||||
.@"9" => if (mods.shiftOnly()) '(' else '9',
|
||||
.a => if (isShifted(mods)) 'A' else 'a',
|
||||
.b => if (isShifted(mods)) 'B' else 'b',
|
||||
.c => if (isShifted(mods)) 'C' else 'c',
|
||||
.d => if (isShifted(mods)) 'D' else 'd',
|
||||
.e => if (isShifted(mods)) 'E' else 'e',
|
||||
.f => if (isShifted(mods)) 'F' else 'f',
|
||||
.g => if (isShifted(mods)) 'G' else 'g',
|
||||
.h => if (isShifted(mods)) 'H' else 'h',
|
||||
.i => if (isShifted(mods)) 'I' else 'i',
|
||||
.j => if (isShifted(mods)) 'J' else 'j',
|
||||
.k => if (isShifted(mods)) 'K' else 'k',
|
||||
.l => if (isShifted(mods)) 'L' else 'l',
|
||||
.m => if (isShifted(mods)) 'M' else 'm',
|
||||
.n => if (isShifted(mods)) 'N' else 'n',
|
||||
.o => if (isShifted(mods)) 'O' else 'o',
|
||||
.p => if (isShifted(mods)) 'P' else 'p',
|
||||
.q => if (isShifted(mods)) 'Q' else 'q',
|
||||
.r => if (isShifted(mods)) 'R' else 'r',
|
||||
.s => if (isShifted(mods)) 'S' else 's',
|
||||
.t => if (isShifted(mods)) 'T' else 't',
|
||||
.u => if (isShifted(mods)) 'U' else 'u',
|
||||
.v => if (isShifted(mods)) 'V' else 'v',
|
||||
.w => if (isShifted(mods)) 'W' else 'w',
|
||||
.x => if (isShifted(mods)) 'X' else 'x',
|
||||
.y => if (isShifted(mods)) 'Y' else 'y',
|
||||
.z => if (isShifted(mods)) 'Z' else 'z',
|
||||
.@"0" => if (isShifted(mods)) ')' else '0',
|
||||
.@"1" => if (isShifted(mods)) '!' else '1',
|
||||
.@"2" => if (isShifted(mods)) '@' else '2',
|
||||
.@"3" => if (isShifted(mods)) '#' else '3',
|
||||
.@"4" => if (isShifted(mods)) '$' else '4',
|
||||
.@"5" => if (isShifted(mods)) '%' else '5',
|
||||
.@"6" => if (isShifted(mods)) '^' else '6',
|
||||
.@"7" => if (isShifted(mods)) '&' else '7',
|
||||
.@"8" => if (isShifted(mods)) '*' else '8',
|
||||
.@"9" => if (isShifted(mods)) '(' else '9',
|
||||
.space => vaxis.Key.space,
|
||||
.enter => vaxis.Key.enter,
|
||||
.tab => vaxis.Key.tab,
|
||||
.backspace => vaxis.Key.backspace,
|
||||
.escape => vaxis.Key.escape,
|
||||
.minus => if (mods.shiftOnly()) '_' else '-',
|
||||
.equals => if (mods.shiftOnly()) '+' else '=',
|
||||
.left_bracket => if (mods.shiftOnly()) '{' else '[',
|
||||
.right_bracket => if (mods.shiftOnly()) '}' else ']',
|
||||
.backslash => if (mods.shiftOnly()) '|' else '\\',
|
||||
.semicolon => if (mods.shiftOnly()) ':' else ';',
|
||||
.apostrophe => if (mods.shiftOnly()) '"' else '\'',
|
||||
.grave => if (mods.shiftOnly()) '~' else '`',
|
||||
.comma => if (mods.shiftOnly()) '<' else ',',
|
||||
.dot => if (mods.shiftOnly()) '>' else '.',
|
||||
.slash => if (mods.shiftOnly()) '?' else '/',
|
||||
.minus => if (isShifted(mods)) '_' else '-',
|
||||
.equals => if (isShifted(mods)) '+' else '=',
|
||||
.left_bracket => if (isShifted(mods)) '{' else '[',
|
||||
.right_bracket => if (isShifted(mods)) '}' else ']',
|
||||
.backslash => if (isShifted(mods)) '|' else '\\',
|
||||
.semicolon => if (isShifted(mods)) ':' else ';',
|
||||
.apostrophe => if (isShifted(mods)) '"' else '\'',
|
||||
.grave => if (isShifted(mods)) '~' else '`',
|
||||
.comma => if (isShifted(mods)) '<' else ',',
|
||||
.dot => if (isShifted(mods)) '>' else '.',
|
||||
.slash => if (isShifted(mods)) '?' else '/',
|
||||
// Navigation and function keys: kitty protocol codepoints (vaxis.Key).
|
||||
.up => vaxis.Key.up,
|
||||
.down => vaxis.Key.down,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue