Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enum ShellClickThroughPolicy {
@MainActor
final class ShellMouseInterceptionSync {
private nonisolated(unsafe) var monitors: [Any] = []
private var visibilityObservation: NSKeyValueObservation?
private var pollingCancellable: AnyCancellable?
private weak var window: NSWindow?

Expand All @@ -72,6 +73,9 @@ final class ShellMouseInterceptionSync {
{
monitors.append(local)
}
visibilityObservation = window.observe(\.isVisible, options: [.new]) { [weak self] _, _ in
MainActor.assumeIsolated { self?.sync() }
}
sync()
}

Expand All @@ -86,6 +90,7 @@ final class ShellMouseInterceptionSync {
window?.ignoresMouseEvents = false
window = nil
pollingCancellable = nil
visibilityObservation = nil
}

func sync() {
Expand Down
30 changes: 30 additions & 0 deletions desktop/macos/Desktop/Tests/ShellClickThroughPolicyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import XCTest
/// frame — the reserved title-bar band, lane margins, and gaps between panels swallowed clicks
/// aimed at other apps, which never activated (the shell-window dead zone). The policy passes the
/// pointer through everywhere except visible content, the modal barrier's host, and the resize rim.
@MainActor
final class ShellClickThroughPolicyTests: XCTestCase {
private let windowSize = NSSize(width: 960, height: 712)

Expand Down Expand Up @@ -60,4 +61,33 @@ final class ShellClickThroughPolicyTests: XCTestCase {
contentContains: { _ in false }),
"a fixed-size window has no resize affordance to preserve")
}

/// The shell reuses one window across dismiss/summon. Reconciliation while that window is ordered
/// out must not leave it ignoring mouse events when AppKit puts it back on screen: while ignored,
/// neither its visible controls nor its local mouse monitor can recover the window.
func testOrderingShellBackOnScreenRestoresMouseInterception() {
let mouse = NSEvent.mouseLocation
let window = NSWindow(
contentRect: NSRect(x: mouse.x + 5_000, y: mouse.y + 5_000, width: 320, height: 240),
styleMask: [.borderless],
backing: .buffered,
defer: false)
window.orderFront(nil)
let sync = ShellMouseInterceptionSync(window: window)
defer {
sync.detach()
window.orderOut(nil)
}

XCTAssertFalse(window.ignoresMouseEvents)
window.orderOut(nil)
sync.sync()
XCTAssertTrue(window.ignoresMouseEvents, "the hidden shell previously entered pass-through mode")

window.orderFront(nil)

XCTAssertFalse(
window.ignoresMouseEvents,
"a visible shell must recover before the first click, without waiting for pointer movement")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"change": "Fixed the main window occasionally ignoring clicks after reopening"
}
Loading