14

Has anyone managed to get this function to work in Swift?

Here is a reference SO post from last year: Using CGEventTapCreate Trouble with parameters in Swift

Apple Doc: https://developer.apple.com/library/prerelease/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html#//apple_ref/c/func/CGEventTapCreate

Here is how the CGEventTapCallBack is defined:

typealias CGEventTapCallBack = CFunctionPointer<((CGEventTapProxy, CGEventType, CGEvent!, UnsafeMutablePointer<Void>) -> Unmanaged<CGEvent>!)>

Here is how I've written the block:

let eventTapCallBackBlock : @objc_block
(CGEventTapProxy, CGEventType, CGEventRef, UnsafeMutablePointer<Void>) -> CGEventRef =
{ (eventTapProxy: CGEventTapProxy, eventType: CGEventType, event: CGEventRef, refcon: UnsafeMutablePointer<Void>) in
  return event
}

Then I've called CGEventTapCreate with the callback parameter like unsafeBitCast(eventTapCallBackBlock, CGEventTapCallBack.self)

I get a valid CFMachPortRef back, but at run time I get an access violation exception on first event. It would "seem" I'm close to a solution in swift in its current release state.

Using Xcode Version 6.4

Community
  • 1
  • 1
chrisp
  • 2,091
  • 4
  • 26
  • 35

1 Answers1

35

The callback parameter of CGEventTapCreate() is a C function pointer, and in Swift 1.x it is not possible call it with a Swift function argument.

However, in Swift 2 (Xcode 7), C functions that take function pointer arguments can be called using closures or global functions (with the restriction that the closure must not capture any of its local context).

As an example, here is a complete translation of Receiving, Filtering, and Modifying Key Presses and Releases to Swift:

import Foundation

func myCGEventCallback(proxy : CGEventTapProxy, type : CGEventType, event : CGEvent, refcon : UnsafeMutablePointer<Void>) -> Unmanaged<CGEvent>? {
    
    if [.KeyDown , .KeyUp].contains(type) {
        var keyCode = CGEventGetIntegerValueField(event, .KeyboardEventKeycode)
        if keyCode == 0 {
            keyCode = 6
        } else if keyCode == 6 {
            keyCode = 0
        }
        CGEventSetIntegerValueField(event, .KeyboardEventKeycode, keyCode)
    }
    return Unmanaged.passRetained(event)
}

let eventMask = (1 << CGEventType.KeyDown.rawValue) | (1 << CGEventType.KeyUp.rawValue)
guard let eventTap = CGEventTapCreate(.CGSessionEventTap,
    .HeadInsertEventTap,
    .Default,
    CGEventMask(eventMask),
    myCGEventCallback,
    nil) else {
        print("failed to create event tap")
        exit(1)
}

let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes)
CGEventTapEnable(eventTap, true)
CFRunLoopRun()

Update for Swift 3:

import Foundation

func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
    
    if [.keyDown , .keyUp].contains(type) {
        var keyCode = event.getIntegerValueField(.keyboardEventKeycode)
        if keyCode == 0 {
            keyCode = 6
        } else if keyCode == 6 {
            keyCode = 0
        }
        event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
    }
    return Unmanaged.passRetained(event)
}

let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap,
                                      place: .headInsertEventTap,
                                      options: .defaultTap,
                                      eventsOfInterest: CGEventMask(eventMask),
                                      callback: myCGEventCallback,
                                      userInfo: nil) else {
                                        print("failed to create event tap")
                                        exit(1)
}

let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
CFRunLoopRun()
Glorfindel
  • 20,880
  • 13
  • 75
  • 99
Martin R
  • 510,973
  • 84
  • 1,183
  • 1,314
  • Thanks for the response. Have had any experience with @objc_blocks to solve this situation? – chrisp Aug 08 '15 at 22:20
  • @jacobsd: CGEventTapCreate() takes a C function pointer as a parameter, not an Objective-C block. I don't think that you can solve that with a Swift 1.2 (Xcode 6.4) callback. – Martin R Aug 08 '15 at 23:09
  • 4
    Holy Heck, that's uglier than the C/Objective-C versions... sigh... at least it's *possible* – uchuugaka Nov 02 '15 at 01:16
  • 1
    Thanks for updating it for Swift 2! – uchuugaka Nov 02 '15 at 01:20
  • 1
    Any idea on how to pass a value for the refcon parameter? You're example is passing nil, and I haven't had any luck trying to find a way to pass in a value for a Swift type that I can then unwrap in the callback function and call a method on (e.g. a delegate object). – chinabuffet Nov 02 '15 at 11:58
  • @chinabuffet: Have a look at http://stackoverflow.com/questions/33294620/how-to-cast-self-to-unsafemutablepointervoid-type-in-swift. The same technique should work here as well. – Martin R Nov 02 '15 at 12:08
  • Ah dang, I had it pretty close at one point, just wasn't taking it back from the UnsafePointer to the original Type quite right... thanks! – chinabuffet Nov 03 '15 at 00:01
  • 3
    It may be worth pointing out (since it took me a few minutes to work out what the issue was) that the function callback must be an actual func, not a class method. – James Waldrop Jan 04 '16 at 07:52
  • 1
    @JamesWaldrop: Yes, the callback cannot be a instance or class method. I *can* be a closure (which captures no context). I'll update the answer later to make that more clear. Thank you for the feedback! – Martin R Jan 04 '16 at 09:04
  • Any good reason to check `if [.keyDown , .keyUp].contains(type)` when `eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)` ? – aristidesfl Feb 28 '17 at 15:38
  • @aristidesfl: Good question. I adopted that from the linked-to C code. – Martin R Feb 28 '17 at 15:41
  • @MartinR do you want to take a shot at this question? https://stackoverflow.com/questions/17773225/cgeventpost-hold-a-key-shift – aristidesfl Mar 08 '17 at 13:07