From 5dfb672986b57b6246d0c5d2c3c9a8fd3d138543 Mon Sep 17 00:00:00 2001 From: Jesse Miller Date: Wed, 2 Sep 2026 14:32:04 -0600 Subject: [PATCH 1/4] surface: restore mouse_shape when modifier overrides end hard-coded .default & .text overrode a previously set OSC22 pointer shape, this was a regression introduced in 6e8ed4e8b. --- src/surface_mouse.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/surface_mouse.zig b/src/surface_mouse.zig index 8fa53d240..58371310d 100644 --- a/src/surface_mouse.zig +++ b/src/surface_mouse.zig @@ -88,7 +88,7 @@ pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { // Normal override state return .text; } else { - return .default; + return self.mouse_shape; } }, @@ -97,7 +97,7 @@ pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { // Crosshair (rectangle select) return .crosshair; } else { - return .text; + return self.mouse_shape; } }, From 6674aa3ba88e4e316af4106746e4b2091868df79 Mon Sep 17 00:00:00 2001 From: Jesse Miller Date: Wed, 2 Sep 2026 14:32:04 -0600 Subject: [PATCH 2/4] surface: update keyToMouseShape tests to expect mouse_shape Update the tests to expect the mouse_shape back, not the hardcoded .default or .text --- src/surface_mouse.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/surface_mouse.zig b/src/surface_mouse.zig index 58371310d..f506967ff 100644 --- a/src/surface_mouse.zig +++ b/src/surface_mouse.zig @@ -239,7 +239,7 @@ test "keyToMouseShape" { } { - // crosshair -> default (mouse tracking) + // no override restores the application shape (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .x10, @@ -249,7 +249,7 @@ test "keyToMouseShape" { .hidden = false, }; - const want: MouseShape = .default; + const want: MouseShape = .crosshair; const got = m.keyToMouseShape(); try testing.expect(want == got); } @@ -271,7 +271,7 @@ test "keyToMouseShape" { } { - // text -> default (mouse tracking) + // no override restores the application shape (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .shift_left, .mouse_event = .x10, @@ -281,7 +281,7 @@ test "keyToMouseShape" { .hidden = false, }; - const want: MouseShape = .default; + const want: MouseShape = .text; const got = m.keyToMouseShape(); try testing.expect(want == got); } @@ -319,7 +319,7 @@ test "keyToMouseShape" { } { - // crosshair -> text (no mouse tracking) + // no override restores the application shape (no mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .none, @@ -329,7 +329,7 @@ test "keyToMouseShape" { .hidden = false, }; - const want: MouseShape = .text; + const want: MouseShape = .crosshair; const got = m.keyToMouseShape(); try testing.expect(want == got); } From 3a766ccf501c669298e5b648a47d217e99bff618 Mon Sep 17 00:00:00 2001 From: Jesse Miller Date: Wed, 2 Sep 2026 14:32:04 -0600 Subject: [PATCH 3/4] surface: show .text (i-beam) while shift is held without mouse tracking I think this is the expected behavoir when a custom OSC22 pointer is set. Once shift is released it will return to mouse_shape (whatever the pointer was before shit held). --- src/surface_mouse.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/surface_mouse.zig b/src/surface_mouse.zig index f506967ff..24bf3c575 100644 --- a/src/surface_mouse.zig +++ b/src/surface_mouse.zig @@ -96,6 +96,11 @@ pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { if (isRectangleSelectState(self.mods)) { // Crosshair (rectangle select) return .crosshair; + } else if (isMouseModeOverrideState(self.mods)) { + // Shift shows an I-beam so selection is obvious even when + // the application cursor is not text (OSC 22). Release + // restores mouse_shape below. + return .text; } else { return self.mouse_shape; } From 0fb6d29404aab9c54c6fbce3cebd630960938117 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Wed, 2 Sep 2026 18:23:11 -0700 Subject: [PATCH 4/4] SurfaceMouse: simplify keyToMouseShape keyToMouseShape was initially designed with more of a transition table model in mind to handle key presses/overrides based on very specific cursor states. This never materialized, so I think it's safe to just simply the process of handling overrides and/or passing along the current cursor state from the terminal in the event of key presses. Also removed a test that is essentially a duplicate of one before it now (returning current surface shape in the event of no overrides). --- src/surface_mouse.zig | 74 +++++++++---------------------------------- 1 file changed, 15 insertions(+), 59 deletions(-) diff --git a/src/surface_mouse.zig b/src/surface_mouse.zig index 24bf3c575..83362ee6a 100644 --- a/src/surface_mouse.zig +++ b/src/surface_mouse.zig @@ -31,23 +31,10 @@ over_link: bool, /// True if the mouse pointer is currently hidden. hidden: bool, -/// Translates key state to mouse shape (cursor) state, based on a state -/// machine. -/// -/// There are 4 current states: -/// -/// * text: starting state, displays a text bar. -/// * default: default state when in a mouse tracking mode. (e.g. vim, etc). -/// Displays an arrow pointer. -/// * pointer: default state when over a link. Displays a pointing finger. -/// * crosshair: any above state can transition to this when the rectangle -/// select keys are pressed (ctrl/super+alt). -/// -/// Additionally, default can transition back to text if one of the shift keys -/// are pressed during mouse tracking mode. -/// -/// Any secondary state transitions back to its default state when the -/// appropriate keys are released. +/// Translates key state to mouse shape, called during key events. This mainly +/// handles overrides on key presses depending on whether or not we are in +/// mouse tracking mode, however it is also responsible for resetting cursor +/// state on any particular key releases. /// /// null is returned when the mouse shape does not need changing. pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { @@ -62,24 +49,10 @@ pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { return null; } - // Set our current default state - var current_shape_state: MouseShape = undefined; - if (self.mouse_event != .none) { - // In mouse tracking mode, should be default (arrow pointer) - current_shape_state = .default; - } else { - // Default terminal mode, should be text (text bar) - current_shape_state = .text; - } - - // Transition table. - // - // TODO: This could be updated eventually to be a true transition table if - // we move to a full stateful mouse surface, e.g. very specific inputs - // transitioning state based on previous state, versus flags like "is the - // mouse over a link", etc. - switch (current_shape_state) { - .default => { + // Handle possible overrides depending on mouse tracking state. + switch (self.mouse_event != .none) { + true => { + // In mouse tracking mode if (isMouseModeOverrideState(self.mods) and isRectangleSelectState(self.mods)) { // Crosshair (rectangle select), only set if we are also // overriding (e.g. shift+ctrl+alt) @@ -87,12 +60,11 @@ pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { } else if (isMouseModeOverrideState(self.mods)) { // Normal override state return .text; - } else { - return self.mouse_shape; } }, - .text => { + false => { + // Default terminal mode if (isRectangleSelectState(self.mods)) { // Crosshair (rectangle select) return .crosshair; @@ -101,14 +73,14 @@ pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { // the application cursor is not text (OSC 22). Release // restores mouse_shape below. return .text; - } else { - return self.mouse_shape; } }, - - // Fall back on default state - else => unreachable, } + + // No overrides means we just revert back to the stored terminal mouse + // shape. Note that this may be different than what has been currently sent + // to the apprt, so this will force the reset. + return self.mouse_shape; } fn eligibleMouseShapeKeyEvent(physical_key: input.Key) bool { @@ -275,22 +247,6 @@ test "keyToMouseShape" { try testing.expect(want == got); } - { - // no override restores the application shape (mouse tracking) - const m: SurfaceMouse = .{ - .physical_key = .shift_left, - .mouse_event = .x10, - .mouse_shape = .text, - .mods = .{}, - .over_link = false, - .hidden = false, - }; - - const want: MouseShape = .text; - const got = m.keyToMouseShape(); - try testing.expect(want == got); - } - { // text, no mods (no mouse tracking) const m: SurfaceMouse = .{