Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764const std = @import("std");const input_action = @import("../../core/input/input_action.zig");
const InputEscapeAction = input_action.Action;const MousePointerKind = input_action.MousePointerKind;
pub const MouseInput = struct { button: u16 = 0, column: u16 = 0, row: u16 = 0, sgr_bytes: u8 = 0, discard_remaining: u8 = 0,
pub fn reset(self: *MouseInput) void { self.* = .{}; }};
fn appendCsiDigitSaturating(param: *u16, byte: u8) void { const digit: u16 = byte - '0'; const multiplied = std.math.mul(u16, param.*, 10) catch std.math.maxInt(u16); param.* = std.math.add(u16, multiplied, digit) catch std.math.maxInt(u16);}
const escape_meta_mask: u8 = 0x80;const sgr_mouse_stage: u8 = 7;const sgr_mouse_column_stage: u8 = 8;const sgr_mouse_row_stage: u8 = 9;const legacy_x10_button_stage: u8 = 10;const legacy_x10_column_stage: u8 = 11;const legacy_x10_row_stage: u8 = 12;const discarded_sgr_mouse_stage: u8 = 13;const discarded_x10_mouse_stage: u8 = 14;const control_sequence_discard_stage: u8 = 15;const kitty_escape_event_type_stage: u8 = 16;const sgr_mouse_max_bytes: u8 = 18;const control_sequence_discard_max_bytes: u16 = 32;const kitty_up_key: u16 = 57352;const kitty_down_key: u16 = 57353;const shift_modifier: u16 = 0x01;const alt_modifier: u16 = 0x02;const ctrl_modifier: u16 = 0x04;const super_modifier: u16 = 0x08;
fn composerMove(kind: input_action.MoveKind, modifiers: u16) InputEscapeAction { return .{ .composer_shortcut = .{ .move = .{ .kind = kind, .extend_selection = (modifiers & shift_modifier) != 0, } } };}
fn modifiedArrowAction(byte: u8, modifiers: u16, meta_prefixed: bool) ?InputEscapeAction { if ((modifiers & super_modifier) != 0) return switch (byte) { 'A' => composerMove(.draft_start, modifiers), 'B' => composerMove(.draft_end, modifiers), 'C' => composerMove(.line_end, modifiers), 'D' => composerMove(.line_start, modifiers), 'H' => composerMove(.draft_start, modifiers), 'F' => composerMove(.draft_end, modifiers), else => null, }; if (meta_prefixed or (modifiers & alt_modifier) != 0) return switch (byte) { 'A' => composerMove(.paragraph_up, modifiers), 'B' => composerMove(.paragraph_down, modifiers), 'C' => composerMove(.word_right, modifiers), 'D' => composerMove(.word_left, modifiers), else => null, }; if ((modifiers & ctrl_modifier) != 0) return switch (byte) { 'C' => composerMove(.word_right, modifiers), 'D' => composerMove(.word_left, modifiers), 'A' => composerMove(.visual_up, modifiers), 'B' => composerMove(.visual_down, modifiers), 'H' => composerMove(.draft_start, modifiers), 'F' => composerMove(.draft_end, modifiers), else => null, }; if ((modifiers & shift_modifier) != 0) return switch (byte) { 'A' => composerMove(.visual_up, modifiers), 'B' => composerMove(.visual_down, modifiers), 'C' => composerMove(.character_right, modifiers), 'D' => composerMove(.character_left, modifiers), 'H' => composerMove(.line_start, modifiers), 'F' => composerMove(.line_end, modifiers), else => null, }; return null;}
fn ctrlOKeyAction(meta_prefixed: bool, modifiers: u16) InputEscapeAction { if (!meta_prefixed and modifiers == 0x04) return .toggle_full_transcript; return .ignore;}
// Resolve a Kitty CSI u report (`ESC[<keycode>;<mod>u`). Shared by the// single-parameter and modifier stages, and never returns null so the leading// ESC's pending-cancel is always cleared.fn kittyUnicodeKeyAction(keycode: u16, modifiers: u16, meta_prefixed: bool) InputEscapeAction { // Strip Caps Lock (bit 6) and Num Lock (bit 7) — lock states, not modifiers. const mods = modifiers & 0x3F; if (keycode == 27 and mods == 0) return .escape; if (keycode == kitty_up_key or keycode == kitty_down_key) { if (meta_prefixed or mods != 0) { return modifiedArrowAction( if (keycode == kitty_up_key) 'A' else 'B', mods, meta_prefixed, ) orelse .ignore; } return if (keycode == kitty_up_key) .cursor_up else .cursor_down; } if (keycode == 13 and mods == ctrl_modifier and !meta_prefixed) return .{ .remapped_byte = '\r' }; if (keycode == 13 and (mods & (shift_modifier | alt_modifier)) != 0) { return .insert_newline; } if (keycode == ' ' and mods == shift_modifier and !meta_prefixed) return .{ .remapped_byte = ' ' }; if ((keycode == 'a' or keycode == 'A') and (mods & super_modifier) != 0) { return .{ .composer_shortcut = .select_all }; } if ((keycode == 'c' or keycode == 'C') and (mods & super_modifier) != 0) { return .{ .composer_shortcut = .copy_selection }; } if ((keycode == 'x' or keycode == 'X') and (mods & super_modifier) != 0) { return .{ .composer_shortcut = .cut_selection }; } if ((keycode == 'z' or keycode == 'Z') and (mods & super_modifier) != 0) { return .{ .composer_shortcut = if ((mods & shift_modifier) != 0) .redo else .undo }; } if ((mods & (shift_modifier | ctrl_modifier)) == (shift_modifier | ctrl_modifier)) { if (keycode == 'a' or keycode == 'A') return composerMove(.line_start, mods); if (keycode == 'b' or keycode == 'B') return composerMove(.character_left, mods); if (keycode == 'e' or keycode == 'E') return composerMove(.line_end, mods); if (keycode == 'f' or keycode == 'F') return composerMove(.character_right, mods); } if (keycode == 'b' and (mods & alt_modifier) != 0) { if ((mods & shift_modifier) != 0) return composerMove(.word_left, mods); return .word_left; } if (keycode == 'f' and (mods & alt_modifier) != 0) { if ((mods & shift_modifier) != 0) return composerMove(.word_right, mods); return .word_right; } if ((keycode == 'd' or keycode == 'D') and (meta_prefixed or (mods & 0x02) != 0)) return .delete_word_right; if (keycode == 'o' or keycode == 'O') return ctrlOKeyAction(meta_prefixed, mods); if ((keycode == 'r' or keycode == 'R') and (mods & 0x08) != 0) return .open_all_sessions; if (keycode == 9 and (mods & 0x01) != 0) return .toggle_permission_mode; if ((mods & 0x04) != 0) { if (keycode == '_') return .{ .remapped_byte = 31 }; if (keycode >= 'a' and keycode <= 'z') return .{ .remapped_byte = @intCast(keycode - 96) }; if (keycode >= 'A' and keycode <= 'Z') return .{ .remapped_byte = @intCast(keycode - 64) }; } if (keycode == 127 and (mods & 0x08) != 0) return .delete_to_line_start; if (keycode == 127 and (mods & 0x02) != 0) return .delete_word_left; if (keycode == 13) return .{ .remapped_byte = '\r' }; if (keycode == 9) return .{ .remapped_byte = '\t' }; if (keycode == 127) return .{ .remapped_byte = 127 }; return .ignore;}
pub fn controlByteFeatureAction(byte: u8) ?InputEscapeAction { return switch (byte) { 15 => .toggle_full_transcript, 16 => .open_model_catalog, else => null, };}
pub const MouseReportDiscardResult = enum { pending, report_end, bound, restart,};
fn baseEscapeStage(stage: u8) u8 { return stage & ~escape_meta_mask;}
fn hasMetaPrefix(stage: u8) bool { return (stage & escape_meta_mask) != 0;}
fn setBaseEscapeStage(stage: *u8, base: u8) void { stage.* = (stage.* & escape_meta_mask) | base;}
pub fn isLegacyX10PayloadStage(stage: u8) bool { return switch (baseEscapeStage(stage)) { legacy_x10_button_stage, legacy_x10_column_stage, legacy_x10_row_stage, => true, else => false, };}
pub fn isMouseReportPayloadStage(stage: u8) bool { return switch (baseEscapeStage(stage)) { sgr_mouse_stage, sgr_mouse_column_stage, sgr_mouse_row_stage, legacy_x10_button_stage, legacy_x10_column_stage, legacy_x10_row_stage, => true, else => false, };}
pub fn isMouseReportDiscardStage(stage: u8) bool { return switch (baseEscapeStage(stage)) { discarded_sgr_mouse_stage, discarded_x10_mouse_stage, => true, else => false, };}
pub fn isBareEscapeStage(stage: u8) bool { return baseEscapeStage(stage) == 1;}
pub fn isControlSequenceDiscardStage(stage: u8) bool { return baseEscapeStage(stage) == control_sequence_discard_stage;}
fn resetMouseEscapeDecode(stage: *u8, param: *u16, param2: *u16, mouse: *MouseInput) void { stage.* = 0; param.* = 0; param2.* = 0; mouse.reset();}
fn beginControlSequenceDiscard( stage: *u8, param: *u16, param2: *u16, mouse: *MouseInput, byte: u8,) ?InputEscapeAction { if (byte >= 0x40 and byte <= 0x7e) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; } if (byte >= 0x20 and byte <= 0x3f) { setBaseEscapeStage(stage, control_sequence_discard_stage); param.* = 1; param2.* = 0; mouse.reset(); return null; } resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore;}
fn consumeControlSequenceDiscardByte( stage: *u8, param: *u16, param2: *u16, mouse: *MouseInput, byte: u8,) ?InputEscapeAction { if (byte >= 0x40 and byte <= 0x7e) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; } if (byte < 0x20 or byte > 0x3f) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; }
param.* = std.math.add(u16, param.*, 1) catch std.math.maxInt(u16); if (param.* >= control_sequence_discard_max_bytes) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; } return null;}
pub fn beginMouseReportDiscard(stage: *u8, param: *u16, param2: *u16, mouse: *MouseInput) bool { const remaining = switch (baseEscapeStage(stage.*)) { sgr_mouse_stage, sgr_mouse_column_stage, sgr_mouse_row_stage, => sgr_mouse_max_bytes -| mouse.sgr_bytes, legacy_x10_button_stage => 3, legacy_x10_column_stage => 2, legacy_x10_row_stage => 1, else => return false, }; if (remaining == 0) { resetMouseEscapeDecode(stage, param, param2, mouse); return true; }
mouse.discard_remaining = remaining; param.* = 0; param2.* = 0; if (isLegacyX10PayloadStage(stage.*)) { setBaseEscapeStage(stage, discarded_x10_mouse_stage); } else { setBaseEscapeStage(stage, discarded_sgr_mouse_stage); } return true;}
pub fn consumeMouseReportDiscardByte( stage: *u8, param: *u16, param2: *u16, mouse: *MouseInput, byte: u8,) MouseReportDiscardResult { std.debug.assert(isMouseReportDiscardStage(stage.*)); if (byte == 0x1b) { resetMouseEscapeDecode(stage, param, param2, mouse); stage.* = 1; return .restart; }
if (baseEscapeStage(stage.*) == discarded_sgr_mouse_stage and (byte == 'M' or byte == 'm')) { resetMouseEscapeDecode(stage, param, param2, mouse); return .report_end; }
std.debug.assert(mouse.discard_remaining > 0); mouse.discard_remaining -= 1; if (mouse.discard_remaining != 0) return .pending;
resetMouseEscapeDecode(stage, param, param2, mouse); return .bound;}
fn noteSgrMouseByte(mouse: *MouseInput) bool { if (mouse.sgr_bytes == sgr_mouse_max_bytes) return false; mouse.sgr_bytes += 1; return true;}
fn sgrMouseAction(button: u16, column: u16, row: u16, terminator: u8) InputEscapeAction { if (column == 0 or row == 0) return .ignore; if ((button & 64) != 0) { if (terminator != 'M') return .ignore; return switch (button & 0b11) { 0 => .{ .mouse_wheel = .up }, 1 => .{ .mouse_wheel = .down }, else => .ignore, }; } if ((button & 0b11) != 0) return .ignore;
const kind: MousePointerKind = if (terminator == 'm') .release else if (terminator == 'M' and (button & 32) != 0) .drag else if (terminator == 'M') .press else return .ignore; return .{ .mouse_pointer = .{ .kind = kind, .column = column, .row = row, .shift = (button & 4) != 0, .alt = (button & 8) != 0, .ctrl = (button & 16) != 0, } };}
pub fn consumeInputEscapeByte(stage: *u8, param: *u16, param2: *u16, byte: u8) ?InputEscapeAction { var mouse = MouseInput{}; return consumeInputEscapeByteWithMouse(stage, param, param2, &mouse, byte);}
pub fn consumeInputEscapeByteWithMouse( stage: *u8, param: *u16, param2: *u16, mouse: *MouseInput, byte: u8,) ?InputEscapeAction { const base_stage = baseEscapeStage(stage.*); if (base_stage == 0) return null; if (byte == 0x1b and base_stage != 1 and !isLegacyX10PayloadStage(stage.*)) { stage.* = 1; param.* = 0; param2.* = 0; mouse.reset(); return null; }
switch (base_stage) { 1 => { if (byte == '[') { setBaseEscapeStage(stage, 2); param.* = 0; param2.* = 0; mouse.reset(); return null; } if (byte == 'O') { setBaseEscapeStage(stage, 4); param.* = 0; param2.* = 0; return null; } if (byte == '\r' or byte == '\n') { stage.* = 0; param.* = 0; param2.* = 0; return .insert_newline; } if (byte == 'o') { stage.* = 0; param.* = 0; param2.* = 0; // ESC o is Meta-O, not a Ctrl-O terminal report. return .ignore; } if (byte == 'b') { stage.* = 0; param.* = 0; param2.* = 0; return .word_left; } if (byte == 'f') { stage.* = 0; param.* = 0; param2.* = 0; return .word_right; } if (byte == 'd') { stage.* = 0; param.* = 0; param2.* = 0; return .{ .composer_shortcut = .delete_word_right }; } if (byte == 0x7f or byte == 0x08) { stage.* = 0; param.* = 0; param2.* = 0; return .delete_word_left; } if (byte == 0x1b) { stage.* = escape_meta_mask | 1; return null; } stage.* = 0; param.* = 0; param2.* = 0; return .ignore; }, 2 => { const meta_prefixed = hasMetaPrefix(stage.*); const action: ?InputEscapeAction = switch (byte) { 'A', 'B', 'C', 'D' => if (meta_prefixed) modifiedArrowAction(byte, 0, true) else switch (byte) { 'A' => .cursor_up, 'B' => .cursor_down, 'C' => .cursor_right, 'D' => .cursor_left, else => unreachable, }, 'H' => .home, 'F' => .end, 'Z' => .toggle_permission_mode, '<' => { mouse.reset(); setBaseEscapeStage(stage, sgr_mouse_stage); return null; }, 'M' => { mouse.reset(); setBaseEscapeStage(stage, legacy_x10_button_stage); return null; }, '?' => { return beginControlSequenceDiscard(stage, param, param2, mouse, byte); }, '0'...'9' => { param.* = byte - '0'; param2.* = 1; setBaseEscapeStage(stage, 3); return null; }, else => return beginControlSequenceDiscard(stage, param, param2, mouse, byte), }; stage.* = 0; param.* = 0; param2.* = 0; return action; }, control_sequence_discard_stage => { return consumeControlSequenceDiscardByte(stage, param, param2, mouse, byte); }, 3 => { if (byte >= '0' and byte <= '9') { appendCsiDigitSaturating(param, byte); if (param2.* < std.math.maxInt(u16)) param2.* += 1; return null; }
if (byte == ';') { param2.* = param.*; param.* = 0; setBaseEscapeStage(stage, 5); return null; }
if (byte == 'u') { const meta_prefixed = hasMetaPrefix(stage.*); const value = param.*; resetMouseEscapeDecode(stage, param, param2, mouse); // Single-parameter CSI u: no `;`, so modifiers == 0. return kittyUnicodeKeyAction(value, 0, meta_prefixed); } if (byte != '~') return beginControlSequenceDiscard(stage, param, param2, mouse, byte);
const value = param.*; const digit_count = param2.*; resetMouseEscapeDecode(stage, param, param2, mouse); if (digit_count == 3 and value == 200) return .paste_start; if (digit_count == 3 and value == 201) return .paste_end;
return switch (value) { 1, 7 => .home, 3 => .delete_next, 4, 8 => .end, 5 => .page_up, 6 => .page_down, else => .ignore, }; }, 4 => { const meta_prefixed = hasMetaPrefix(stage.*); const action: InputEscapeAction = switch (byte) { 'A', 'B', 'C', 'D' => if (meta_prefixed) modifiedArrowAction(byte, 0, true) orelse .ignore else switch (byte) { 'A' => .cursor_up, 'B' => .cursor_down, 'C' => .cursor_right, 'D' => .cursor_left, else => unreachable, }, 'H' => .home, 'F' => .end, else => return beginControlSequenceDiscard(stage, param, param2, mouse, byte), }; resetMouseEscapeDecode(stage, param, param2, mouse); return action; }, 5 => { if (byte >= '0' and byte <= '9') { appendCsiDigitSaturating(param, byte); return null; }
if (byte == ';') { // Reuse the two accumulators for `ESC[27;modifier;keycode~`. param2.* = param.*; param.* = 0; setBaseEscapeStage(stage, 6); return null; }
// Ghostty can report a Kitty key event type as a colon-qualified // modifier, e.g. `ESC[27;1:1u` for an Escape key press. if (byte == ':' and param2.* == 27) { param2.* = if (param.* > 0) param.* - 1 else 0; param.* = 0; setBaseEscapeStage(stage, kitty_escape_event_type_stage); return null; }
if (byte == 'u') { const meta_prefixed = hasMetaPrefix(stage.*); const keycode = param2.*; const modifiers = if (param.* > 0) param.* - 1 else 0; resetMouseEscapeDecode(stage, param, param2, mouse); return kittyUnicodeKeyAction(keycode, modifiers, meta_prefixed); }
if (byte == '~') { const keycode = param2.*; const modifiers = if (param.* > 0) param.* - 1 else 0; resetMouseEscapeDecode(stage, param, param2, mouse); if (keycode == 3 and (modifiers & 0x08) != 0) { return .delete_to_line_end; } if (keycode == 3 and ((modifiers & 0x02) != 0 or (modifiers & 0x04) != 0)) { return .delete_word_right; } if (modifiers != 0) return switch (keycode) { 1, 7 => composerMove( if ((modifiers & ctrl_modifier) != 0) .draft_start else .line_start, modifiers, ), 4, 8 => composerMove( if ((modifiers & ctrl_modifier) != 0) .draft_end else .line_end, modifiers, ), 5 => composerMove(.page_up, modifiers), 6 => composerMove(.page_down, modifiers), else => .ignore, }; return switch (keycode) { 1, 7 => .home, 3 => .delete_next, 4, 8 => .end, 5 => .page_up, 6 => .page_down, else => .ignore, }; }
const meta_prefixed = hasMetaPrefix(stage.*); const modifiers = if (param.* > 0) param.* - 1 else 0; if (byte == 'Z' and modifiers != 0) { resetMouseEscapeDecode(stage, param, param2, mouse); return .toggle_permission_mode; } if (modifiedArrowAction(byte, modifiers, meta_prefixed)) |action| { resetMouseEscapeDecode(stage, param, param2, mouse); return action; }
const action: InputEscapeAction = switch (byte) { 'A' => .cursor_up, 'B' => .cursor_down, 'C' => .cursor_right, 'D' => .cursor_left, 'H' => .home, 'F' => .end, else => return beginControlSequenceDiscard(stage, param, param2, mouse, byte), }; resetMouseEscapeDecode(stage, param, param2, mouse); return action; }, // Stage 6: third parameter (e.g. modifyOtherKeys: ESC[27;modifier;keycode~) 6 => { if (byte >= '0' and byte <= '9') { appendCsiDigitSaturating(param, byte); return null; }
if (byte == '~') { const meta_prefixed = hasMetaPrefix(stage.*); const keycode = param.*; const modifiers = if (param2.* > 0) param2.* - 1 else 0; resetMouseEscapeDecode(stage, param, param2, mouse); return kittyUnicodeKeyAction(keycode, modifiers, meta_prefixed); }
return beginControlSequenceDiscard(stage, param, param2, mouse, byte); }, // Kitty key reports with an event type: `ESC[27;modifier:event-type u`. // Only press and repeat are actionable; release must not close a panel. kitty_escape_event_type_stage => { if (byte >= '0' and byte <= '9') { appendCsiDigitSaturating(param, byte); return null; } if (byte == 'u') { const modifiers = param2.*; const event_type = param.*; resetMouseEscapeDecode(stage, param, param2, mouse); if (event_type == 1 or event_type == 2) { return kittyUnicodeKeyAction(27, modifiers, false); } return .ignore; } return beginControlSequenceDiscard(stage, param, param2, mouse, byte); }, // SGR mouse: ESC [ < button ; column ; row M/m. sgr_mouse_stage => { if (!noteSgrMouseByte(mouse) or mouse.sgr_bytes == sgr_mouse_max_bytes) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; } if (byte >= '0' and byte <= '9') { appendCsiDigitSaturating(&mouse.button, byte); return null; } if (byte == ';') { setBaseEscapeStage(stage, sgr_mouse_column_stage); return null; } resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; }, sgr_mouse_column_stage => { if (!noteSgrMouseByte(mouse) or mouse.sgr_bytes == sgr_mouse_max_bytes) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; } if (byte >= '0' and byte <= '9') { appendCsiDigitSaturating(&mouse.column, byte); return null; } if (byte == ';') { setBaseEscapeStage(stage, sgr_mouse_row_stage); return null; } resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; }, sgr_mouse_row_stage => { if (!noteSgrMouseByte(mouse)) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; } if (byte >= '0' and byte <= '9') { if (mouse.sgr_bytes == sgr_mouse_max_bytes) { resetMouseEscapeDecode(stage, param, param2, mouse); return .ignore; } appendCsiDigitSaturating(&mouse.row, byte); return null; }
const button = mouse.button; const column = mouse.column; const row = mouse.row; resetMouseEscapeDecode(stage, param, param2, mouse); return sgrMouseAction(button, column, row, byte); }, legacy_x10_button_stage => { mouse.button = byte; setBaseEscapeStage(stage, legacy_x10_column_stage); return null; }, legacy_x10_column_stage => { mouse.column = byte; setBaseEscapeStage(stage, legacy_x10_row_stage); return null; }, legacy_x10_row_stage => { const button = mouse.button; stage.* = 0; param.* = 0; param2.* = 0; mouse.reset(); if (button < 32) return .ignore; const normalized_button = button - 32; if ((normalized_button & 64) == 0) return .ignore; return switch (normalized_button & 0b11) { 0 => .{ .mouse_wheel = .up }, 1 => .{ .mouse_wheel = .down }, else => .ignore, }; }, else => { stage.* = 0; param.* = 0; param2.* = 0; return null; }, }}