Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intercept/Disable keyboard shortcuts (ex. CMD+q)

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?

like image 403
ehftwelve Avatar asked Oct 25 '13 18:10

ehftwelve


1 Answers

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.

like image 173
2 revs Avatar answered Sep 22 '22 20:09

2 revs