forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathShellClickThrough.swift
More file actions
130 lines (121 loc) · 4.93 KB
/
Copy pathShellClickThrough.swift
File metadata and controls
130 lines (121 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//
// ShellClickThrough.swift — the transparent shell window stops swallowing the desktop's clicks.
//
// `ShellWindowChrome` makes the main window a transparent rectangle the size of the
// glass. Leftover air — the rounded-corner triangles, the gaps between stacked panels — would
// still be an invisible click sink: the window server routes every click to the window under the
// cursor no matter what is drawn there. Same failure class as the notch panel's dead zone
// (PR #11372), on the shell window.
//
// Same mechanism, too: a view-level `hitTest` nil cannot make a window click-through, so the
// window-level `ignoresMouseEvents` is kept in sync with the pointer — ignored over air,
// interactive over content. "Content" is answered by `InkGlassHitRegions`: every glass surface
// registers its live extent, and a modal barrier registers the whole host while it is up.
//
// Drag lives on the visible top bar (`ShellWindowDragHandle`). Resizing survives via an
// interactive rim along the window's frame edge, which is the visible panel edge.
//
import AppKit
import Combine
import OmiTheme
/// Pure policy: which points of the shell window own the pointer.
enum ShellClickThroughPolicy {
/// Interactive band kept along the window's frame edge so the chrome-less, resizable window can
/// still be resized. The glass fills the window (`DesktopWindowLayoutPolicy.windowInset` is 0),
/// so this rim *is* the visible panel edge. Matches AppKit's effective resize zone.
static let resizeRim: CGFloat = 8
static func acceptsMouseHit(
localPoint: NSPoint,
windowSize: NSSize,
isResizable: Bool,
contentContains: (NSPoint) -> Bool
) -> Bool {
if isResizable {
let nearEdge =
localPoint.x <= resizeRim || localPoint.x >= windowSize.width - resizeRim
|| localPoint.y <= resizeRim || localPoint.y >= windowSize.height - resizeRim
if nearEdge { return true }
}
return contentContains(localPoint)
}
}
/// Keeps the shell window's `ignoresMouseEvents` in sync with the pointer. One instance per shell
/// window, owned by `ShellWindowAttachmentView` so it binds to the exact `NSWindow` hosting the
/// shell and dies with it.
@MainActor
final class ShellMouseInterceptionSync {
private nonisolated(unsafe) var monitors: [Any] = []
private var visibilityObservation: NSKeyValueObservation?
private var pollingCancellable: AnyCancellable?
private weak var window: NSWindow?
init(window: NSWindow) {
self.window = window
let scheduleReconciliation: @Sendable () -> Void = { [weak self] in
DispatchQueue.main.async { self?.sync() }
}
if let global = NSEvent.addGlobalMonitorForEvents(
matching: [.mouseMoved, .leftMouseDragged],
handler: { _ in scheduleReconciliation() })
{
monitors.append(global)
}
if let local = NSEvent.addLocalMonitorForEvents(
matching: [.mouseMoved],
handler: { event in
scheduleReconciliation()
return event
})
{
monitors.append(local)
}
visibilityObservation = window.observe(\.isVisible, options: [.new]) { [weak self] _, _ in
MainActor.assumeIsolated { self?.sync() }
}
sync()
}
deinit {
monitors.forEach(NSEvent.removeMonitor)
}
/// Restores the ordinary always-interactive window before the sync goes away (e.g. the
/// automation harness snapshots/restores presentation): a window left with
/// `ignoresMouseEvents == true` and nobody updating it is unreachable.
func detach() {
window?.ignoresMouseEvents = false
window = nil
pollingCancellable = nil
visibilityObservation = nil
}
func sync() {
guard let window else { return }
setPollingActive(window.isVisible)
let shouldIgnore = FloatingBarMouseInterceptionPolicy.shouldIgnoreMouseEvents(
mouseLocation: NSEvent.mouseLocation,
windowFrame: window.frame,
isVisible: window.isVisible,
acceptsMouseHit: { [weak window] local in
guard let window else { return true }
return ShellClickThroughPolicy.acceptsMouseHit(
localPoint: local,
windowSize: window.frame.size,
isResizable: window.styleMask.contains(.resizable),
contentContains: {
// No registered surface means nothing can vouch for this window's content, so it stays
// fully interactive rather than becoming a pass-through hole.
guard InkGlassHitRegions.shared.hasSurfaces(in: window) else { return true }
return InkGlassHitRegions.shared.containsPoint($0, in: window)
})
})
if window.ignoresMouseEvents != shouldIgnore {
window.ignoresMouseEvents = shouldIgnore
}
}
private func setPollingActive(_ active: Bool) {
guard active != (pollingCancellable != nil) else { return }
pollingCancellable =
active
? Timer.publish(every: 1.0 / 30.0, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in self?.sync() }
: nil
}
}