Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3899,11 +3899,18 @@ pub fn mouseButtonCallback(
// path for its whole lifecycle. We latch the decision here (rather than
// re-checking live modifiers in each report path) so that releasing the
// modifier before the mouse button can't leak the release as a half-click.
// Cleared when the left button is released (see the report path below).
if (button == .left and action == .press) {
self.mouse.link_click_active = self.mouse.mods.equal(input.ctrlOrSuper(.{}));
}

// Clear the latch on every left-button release, on all return paths — the
// link-open and prompt-click branches below return early, so a plain
// statement after them would be skipped and leave the latch stale. The
// press above is the only setter; this defer is the only reset.
defer if (button == .left and action == .release) {
self.mouse.link_click_active = false;
};

// Shift-click continues the previous mouse state if we have a selection.
// cursorPosCallback will also do a mouse report so we don't need to do any
// of the logic below.
Expand Down Expand Up @@ -3971,8 +3978,12 @@ pub fn mouseButtonCallback(

// Handle link clicking. We want to do this before we do mouse
// reporting or any other mouse handling because a successfully
// clicked link will swallow the event.
if (self.mouse.over_link) {
// clicked link will swallow the event. We also attempt this when a
// link click is latched (link_click_active) even if over_link was
// cleared between press and release (e.g. the modifier was released or
// the cursor drifted), so the latched click still opens its link
// rather than being swallowed by the report-suppression below.
if (self.mouse.over_link or self.mouse.link_click_active) {
const pos = try self.rt_surface.getCursorPos();
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
Expand Down Expand Up @@ -4048,12 +4059,6 @@ pub fn mouseButtonCallback(
}
}

// Always clear the link-click latch when the left button is released, even
// if mouse reporting was disabled mid-click (so the release skipped the
// report block above) — otherwise the latch could go stale and suppress a
// later click. The press sets it; this is the single unconditional reset.
if (button == .left and action == .release) self.mouse.link_click_active = false;

// For left button clicks we always record some information for
// selection/highlighting purposes.
if (button == .left and action == .press) click: {
Expand Down Expand Up @@ -4449,8 +4454,16 @@ fn linkAtPos(
break :mouse_pin pin;
};

// Get our comparison mods
const mouse_mods = self.mouseModsWithCapture(self.mouse.mods);
// Get our comparison mods. When a left link-click is in flight (the
// ctrl/super link chord was held at press; see mouseButtonCallback), use
// that chord even if the modifier was released before the button came up,
// so the latched click still resolves and opens its link instead of being
// swallowed. Outside an active link click this is just the live mods.
const effective_mods = if (self.mouse.link_click_active)
input.ctrlOrSuper(.{})
else
self.mouse.mods;
const mouse_mods = self.mouseModsWithCapture(effective_mods);

// If we have the proper modifiers set then we can check for OSC8 links.
if (mouse_mods.equal(input.ctrlOrSuper(.{}))) hyperlink: {
Expand Down
Loading