fix(apple): stop the macOS beep on every keystroke while streaming
ci / rust (push) Has been cancelled
ci / rust (push) Has been cancelled
GCKeyboard reads the HID state directly, so the key NSEvents kept traveling the responder chain unhandled — and an unhandled keyDown makes NSWindow play the "invalid input" sound on every keystroke. InputCapture now installs a local event monitor for its lifetime that swallows key events, except ⌘-combos, which still reach the local app (the HUD's ⌘D disconnect, ⌘Q) in addition to the host. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -145,8 +145,11 @@ signing, bundle id `io.unom.punktfunk`. Notes:
|
|||||||
nothing sticks down host-side. While the stream has focus the LOCAL cursor is hidden
|
nothing sticks down host-side. While the stream has focus the LOCAL cursor is hidden
|
||||||
and frozen mid-view (`CursorCapture` in StreamView.swift — the host renders its own
|
and frozen mid-view (`CursorCapture` in StreamView.swift — the host renders its own
|
||||||
cursor; the local one diverges from it and a stray click would focus another app);
|
cursor; the local one diverges from it and a stray click would focus another app);
|
||||||
Cmd+Tab frees it, ⌘D disconnects. Local shortcuts (⌘-anything) still also reach the
|
Cmd+Tab frees it, ⌘D disconnects. While captured, key NSEvents are swallowed by a
|
||||||
host; a capture toggle is a small follow-up. One live capture per process (the GC
|
local event monitor (GC reads HID directly; without it every keystroke bubbles up the
|
||||||
|
responder chain unhandled and NSWindow beeps) — except ⌘-combos, which still reach
|
||||||
|
the local app (⌘D/⌘Q) in addition to the host; a capture toggle is a small
|
||||||
|
follow-up. One live capture per process (the GC
|
||||||
mouse/keyboard singletons have a single handler slot — ownership is tracked so a stale
|
mouse/keyboard singletons have a single handler slot — ownership is tracked so a stale
|
||||||
capture's stop() can't clobber a newer one).
|
capture's stop() can't clobber a newer one).
|
||||||
9. **iOS**: same package (`BUILD_IOS=1` for the xcframework slice); `StreamView` needs the
|
9. **iOS**: same package (`BUILD_IOS=1` for the xcframework slice); `StreamView` needs the
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public final class InputCapture {
|
|||||||
private var observers: [NSObjectProtocol] = []
|
private var observers: [NSObjectProtocol] = []
|
||||||
private var mice: [GCMouse] = []
|
private var mice: [GCMouse] = []
|
||||||
private var keyboards: [GCKeyboard] = []
|
private var keyboards: [GCKeyboard] = []
|
||||||
|
private var keyEventMonitor: Any?
|
||||||
|
|
||||||
// Main-queue-only state (see header comment).
|
// Main-queue-only state (see header comment).
|
||||||
private var residualX: Float = 0
|
private var residualX: Float = 0
|
||||||
@@ -66,12 +67,26 @@ public final class InputCapture {
|
|||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
self?.releaseAll()
|
self?.releaseAll()
|
||||||
})
|
})
|
||||||
|
// GC reads the HID state directly — the NSEvents still travel the responder
|
||||||
|
// chain, where every unhandled keyDown makes NSWindow beep ("invalid input").
|
||||||
|
// Swallow key events while captured, EXCEPT ⌘-combos: those stay local (the
|
||||||
|
// HUD's ⌘D disconnect, ⌘Q, …) in addition to reaching the host via GC.
|
||||||
|
keyEventMonitor = NSEvent.addLocalMonitorForEvents(
|
||||||
|
matching: [.keyDown, .keyUp]
|
||||||
|
) { event in
|
||||||
|
event.modifierFlags.intersection(.deviceIndependentFlagsMask).contains(.command)
|
||||||
|
? event : nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stop() {
|
public func stop() {
|
||||||
releaseAll()
|
releaseAll()
|
||||||
observers.forEach(NotificationCenter.default.removeObserver(_:))
|
observers.forEach(NotificationCenter.default.removeObserver(_:))
|
||||||
observers.removeAll()
|
observers.removeAll()
|
||||||
|
if let monitor = keyEventMonitor {
|
||||||
|
NSEvent.removeMonitor(monitor)
|
||||||
|
keyEventMonitor = nil
|
||||||
|
}
|
||||||
// Don't clobber the handlers if a newer capture has taken the global devices.
|
// Don't clobber the handlers if a newer capture has taken the global devices.
|
||||||
if Self.activeCapture === self || Self.activeCapture == nil {
|
if Self.activeCapture === self || Self.activeCapture == nil {
|
||||||
for mouse in mice {
|
for mouse in mice {
|
||||||
|
|||||||
Reference in New Issue
Block a user