I am trying to pass an anonymous function into a method that uses a closure.
This is not precisely the case. CGEvent.tapCreate is a wrapper for the C function CGEventTapCreate, which takes a callback that is a C function. A C function is not a closure. A closure contains both code to execute and any values that it references. It is said to “close over” those values. A C function can't close over any values; it only has access to the arguments passed to it and to global variables.
To work around this behavior, a C API that takes a callback function usually also takes an opaque pointer (void * in C parlance) which you can use to pass in whatever extra state you want. In the case of CGEvent.tapCreate, that is the userInfo or refcon argument (the documentation refers to it by both names). You're passing self as that argument, but you actually want to pass in two values: self and onNameReceived.
One way to solve this is by adding a new instance property to hold onNameReceived for use by the callback, since you can access the instance property through the self reference you recover from refcon.
My preferred solution is to wrap the event tap in a class that lets you use a Swift closure as the handler.
class EventTapWrapper {
typealias Handler = (CGEventTapProxy, CGEventType, CGEvent) -> ()
init?(
location: CGEventTapLocation, placement: CGEventTapPlacement,
events: [CGEventType], handler: @escaping Handler)
{
self.handler = handler
let mask = events.reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
let callback: CGEventTapCallBack = { (proxy, type, event, me) in
let wrapper = Unmanaged<EventTapWrapper>.fromOpaque(me!).takeUnretainedValue()
wrapper.handler(proxy, type, event)
return nil
}
// I can't create the tap until self is fully initialized,
// since I need to pass self to tapCreate.
self.tap = nil
guard let tap = CGEvent.tapCreate(
tap: location, place: placement,
options: .listenOnly, eventsOfInterest: mask,
callback: callback,
userInfo: Unmanaged.passUnretained(self).toOpaque())
else { return nil }
self.tap = tap
}
private let handler: Handler
private var tap: CFMachPort?
}
With this wrapper, we can just use a regular Swift closure as the tap handler:
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func startListeningForEvents(onNameReceived: @escaping (String) -> Void) {
self.tap = EventTapWrapper(
location: .cghidEventTap, placement: .tailAppendEventTap,
events: [.leftMouseUp],
handler: { [weak self] (proxy, type, event) in
guard let self = self else { return }
onNameReceived(self.onEventTap(proxy: proxy, type: type, event: event))
})
}
func onEventTap(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent) -> String {
return "hello"
}
private var tap: EventTapWrapper?
}
Please note that this is not a complete example (although it compiles). You also have to enable the tap (with CGEvent.tapEnable) and add it to a run loop (using CFMachPortCreateRunLoopSource and then CFRunLoopAddSource). You also need to remove the source from the run loop in the wrapper's deinit, lest it try to use the wrapper after the wrapper has been destroyed.