Last active
April 2, 2024 02:06
-
-
Save JadenGeller/2cdcc441f936b4b0a7b331ccf13a78ac to your computer and use it in GitHub Desktop.
for monitoring trackpad, touch, etc. events, useful in SwiftUI, similar to using CGTapCreate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AppKit | |
struct EventMonitor: AsyncSequence { | |
typealias Element = NSEvent | |
enum Scope { | |
case local | |
case global | |
} | |
var scope: Scope = .local | |
var mask: NSEvent.EventTypeMask | |
class AsyncIterator: AsyncIteratorProtocol { | |
var monitor: Any | |
var iterator: AsyncStream<NSEvent>.AsyncIterator | |
init(monitor: Any, iterator: AsyncStream<NSEvent>.AsyncIterator) { | |
self.monitor = monitor | |
self.iterator = iterator | |
} | |
func next() async -> NSEvent? { | |
await iterator.next() | |
} | |
deinit { | |
NSEvent.removeMonitor(monitor) | |
} | |
} | |
func makeAsyncIterator() -> AsyncIterator { | |
let (stream, continuation) = AsyncStream.makeStream(of: NSEvent.self) | |
let monitor = switch scope { | |
case .local: NSEvent.addLocalMonitorForEvents(matching: mask) { event in continuation.yield(event); return event } | |
case .global: NSEvent.addGlobalMonitorForEvents(matching: mask) { event in continuation.yield(event) } | |
} | |
guard let monitor else { preconditionFailure() } | |
return AsyncIterator(monitor: monitor, iterator: stream.makeAsyncIterator()) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct EventMonitorView: NSViewRepresentable { | |
var mask: NSEvent.EventTypeMask | |
var handle: (NSEvent, CGPoint) -> NSEvent? | |
class NSViewType: NSView { | |
var mask: NSEvent.EventTypeMask = .init() | |
var handle: (NSEvent, CGPoint) -> NSEvent? = { event, _ in event } | |
var monitor: Any? | |
func updateMonitor() { | |
if let monitor { | |
NSEvent.removeMonitor(monitor) | |
self.monitor = nil | |
} | |
if let window { | |
monitor = NSEvent.addLocalMonitorForEvents(matching: mask) { [self] event in | |
guard event.window == window else { return event } | |
let point = self.convert(event.locationInWindow, from: nil) | |
guard self.bounds.contains(point) else { return event } | |
return handle(event, point) | |
} | |
} | |
} | |
override func viewDidMoveToWindow() { | |
updateMonitor() | |
} | |
override func hitTest(_ point: NSPoint) -> NSView? { | |
nil | |
} | |
override var isFlipped: Bool { | |
true | |
} | |
} | |
func makeNSView(context: Context) -> NSViewType { | |
.init(frame: .zero) | |
} | |
func updateNSView(_ view: NSViewType, context: Context) { | |
view.mask = mask | |
view.handle = handle | |
view.updateMonitor() | |
} | |
} | |
extension View { | |
func monitorEvents(mask: NSEvent.EventTypeMask, handle: @escaping (NSEvent, CGPoint) -> NSEvent?) -> some View { | |
overlay(EventMonitorView(mask: .scrollWheel, handle: handle)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment