5

Is there anyway to intercept and change/ignore, globally, a given shortcut in Objective-C for a Mac application?

An example is BetterTouchTool, it can override any shortcut you provide.

What I want to do is prevent the 'quit' shortcut (i.e. CMD+q) when a specific application is open (because in this instance the shortcut is inadvertantly pressed and closes the application undesirably for some people).

In short, can I listen for any global key events and then change the event before it gets delivered to its intended application?

ehftwelve
  • 2,787
  • 2
  • 20
  • 25
  • As an entirely non-programming solution, you can just change the key equivalent for that one application in System Preferences > Keyboard > Keyboard Shortcuts > Application Shortcuts > [the specific app] (I think this has moved in Mavericks, but it should still be in System Preferences somewhere.) I have my browser's Quit menu item set to ^⌥⌘Q, for example. Coding-wise, you're looking for CGEventTaps. – jscs Oct 25 '13 at 18:53
  • This does not work, as the application is launched using a bash script and is not a *.app application. I will look into CGEventTaps, do you have any immediated resources for that? – ehftwelve Oct 25 '13 at 18:58
  • Gotcha. The [Quartz Event Services reference](https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html) is the official doc. – jscs Oct 25 '13 at 18:59
  • This works for macos/pynput: https://stackoverflow.com/questions/72482840/how-to-suppress-function-keys-in-macos-quartz-pynput/ – Bernd Jun 21 '22 at 19:33

1 Answers1

5

Here's how to setup the event listener

CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap,
                                kCGHeadInsertEventTap,
                                kCGEventTapOptionDefault,
                                CGEventMaskBit(kCGEventKeyDown),
                                &KeyDownCallback,
                                NULL);

CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(NULL, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CFRelease(runLoopSource);
CGEventTapEnable(eventTap, true);

And then here is the "callback":

static CGEventRef KeyDownCallback(CGEventTapProxy proxy,
                              CGEventType type,
                              CGEventRef event,
                              void *refcon)
{
    /* Do something with the event */
    NSEvent *e = [NSEvent eventWithCGEvent:event];
    return event;
}

on the parsed NSEvent, there are the modifierFlags and keyCode properties. keyCode is the code of the key that was pressed, and modifierFlags is the different modifiers that were pressed (Shift, Alt/Option, Command, etc).

Simply return NULL; in the KeyDownCallback method to stop the event from propogating.

Note: there seems to be an issue with the event tap timing out, to solve this problem you can 'reset' the event tap.

In the KeyDownCallback method, check if the CGEventType type is kCGEventTapDisabledByTimeout like so:

if (type == kCGEventTapDisabledByTimeout)
{
    /* Reset eventTap */
    return NULL;
}

and where Reset eventTap is, execute the setup of the event listener above again.

ehftwelve
  • 2,787
  • 2
  • 20
  • 25