4
votes

According to this post:

ShortcutRecorder record CMD+Tab

calling setCanCaptureGlobalHotKeys:YES on the ShortCutRecorder control should allow you to capture CMD+TAB. However, it doesn't seem to work. I created this small app myself to see whats going on:

OSStatus myHotKeyHandler(EventHandlerCallRef nextHandler, EventRef anEvent, void *userData)
{
    NSLog(@"YEAY WE DID A GLOBAL HOTKEY");
    return noErr;
}

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    EventHotKeyRef myHotKeyRef;
    EventHotKeyID myHotKeyID;
    EventTypeSpec eventType;

    eventType.eventClass = kEventClassKeyboard;
    eventType.eventKind = kEventHotKeyPressed;

    myHotKeyID.signature = 'mhk1';
    myHotKeyID.id = 1;

    InstallApplicationEventHandler(&myHotKeyHandler, 1, &eventType, NULL, NULL);

    OSStatus status = RegisterEventHotKey(kVK_Tab,
                                          cmdKey,
                                          myHotKeyID,
                                          GetApplicationEventTarget(),
                                          0,
                                          &myHotKeyRef);

    NSLog(@"status:%d", status);
}

@end

If I use cmdKey + optionKey, then it does work.

Is there another way to capture CMD+TAB in my own application on Mountain Lion? CMD+OPTION+TAB is not good enough for me.

1

1 Answers

5
votes

Things have changed a bit since that question was asked in 2010! ⌘⇥ is detected by Dock.app with an Event Tap, and that event no longer makes it back to the application.

You can still hook ⌘⇥, but you need to beat Dock to it with an Event Tap yourself. Here's some example code, courtesy osxbook.com:

// alterkeys.c
// http://osxbook.com
//
// Complile using the following command line:
//     gcc -Wall -o alterkeys alterkeys.c -framework ApplicationServices
//
// You need superuser privileges to create the event tap, unless accessibility
// is enabled. To do so, select the "Enable access for assistive devices"
// checkbox in the Universal Access system preference pane.

#include <ApplicationServices/ApplicationServices.h>

// This callback will be invoked every time there is a keystroke.
//
CGEventRef
myCGEventCallback(CGEventTapProxy proxy, CGEventType type,
                  CGEventRef event, void *refcon)
{
    // Paranoid sanity check.
    if ((type != kCGEventKeyDown) && (type != kCGEventKeyUp))
        return event;

    // The incoming keycode.
    CGKeyCode keycode = (CGKeyCode)CGEventGetIntegerValueField(
                                       event, kCGKeyboardEventKeycode);

    // Swap 'a' (keycode=0) and 'z' (keycode=6).
    if (keycode == (CGKeyCode)0)
        keycode = (CGKeyCode)6;
    else if (keycode == (CGKeyCode)6)
        keycode = (CGKeyCode)0;

    // Set the modified keycode field in the event.
    CGEventSetIntegerValueField(
        event, kCGKeyboardEventKeycode, (int64_t)keycode);

    // We must return the event for it to be useful.
    return event;
}

int
main(void)
{
    CFMachPortRef      eventTap;
    CGEventMask        eventMask;
    CFRunLoopSourceRef runLoopSource;

    // Create an event tap. We are interested in key presses.
    eventMask = ((1 << kCGEventKeyDown) | (1 << kCGEventKeyUp));
    eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0,
                                eventMask, myCGEventCallback, NULL);
    if (!eventTap) {
        fprintf(stderr, "failed to create event tap\n");
        exit(1);
    }

    // Create a run loop source.
    runLoopSource = CFMachPortCreateRunLoopSource(
                        kCFAllocatorDefault, eventTap, 0);

    // Add to the current run loop.
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource,
                       kCFRunLoopCommonModes);

    // Enable the event tap.
    CGEventTapEnable(eventTap, true);

    // Set it all running.
    CFRunLoopRun();

    // In a real program, one would have arranged for cleaning up.

    exit(0);
}

The downside to this is that you cannot sandbox or ship an application that uses ⌘⇥ on the App Store. Not only should it be obvious why Event Taps are not allowed in those environments (they give you the ability to terminate—and even mutate—events), but the functionality Dock provides on ⌘⇥ is pretty darn useful and can't be remapped to a different keyboard shortcut, so even Witch and Switch avoid it using it by default.