Skip to content

Instantly share code, notes, and snippets.

@JadenGeller
Last active April 2, 2024 02:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JadenGeller/2cdcc441f936b4b0a7b331ccf13a78ac to your computer and use it in GitHub Desktop.
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
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())
}
}
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