macos: reset mouse state on focus loss to prevent phantom drag (#11276)

Fixes https://github.com/ghostty-org/ghostty/discussions/11203

The `suppressNextLeftMouseUp` flag from #11167 wasn't being reset on
focus loss, causing stale state that led to phantom drags/selections and
scrolls if you're lucky enough.

I've followed the #11167 's path and made it reset on focus loss.

As I stated in the [vouch
request](https://github.com/ghostty-org/ghostty/discussions/11274); I'm
not experienced in Swift, just following the prior PR's steps to reset
the state. I've been using this patch for couple days and the change
looks trivial to me tho not 100% sure if I'm missing anything.

> [!NOTE]
> Used Claude Code -Opus 4.6- for navigating the codebase and reviewing
the change.
This commit is contained in:
Mitchell Hashimoto
2026-03-10 09:56:39 -07:00
committed by GitHub

View File

@@ -438,6 +438,15 @@ extension Ghostty {
guard let surface = self.surface else { return }
guard self.focused != focused else { return }
self.focused = focused
// If we lost our focus then remove the mouse event suppression so
// our mouse release event leaving the surface can properly be
// sent to stop things like mouse selection.
if !focused {
suppressNextLeftMouseUp = false
}
// Notify libghostty
ghostty_surface_set_focus(surface, focused)
// Update our secure input state if we are a password input
@@ -648,9 +657,15 @@ extension Ghostty {
let location = convert(event.locationInWindow, from: nil)
guard hitTest(location) == self else { return event }
// We always assume that we're resetting our mouse suppression
// unless we see the specific scenario below to set it.
suppressNextLeftMouseUp = false
// If we're already the first responder then no focus transfer is
// happening, so the click should continue as normal.
guard window.firstResponder !== self else { return event }
guard window.firstResponder !== self else {
return event
}
// If our window/app is already focused, then this click is only
// being used to transfer split focus. Consume it so it does not