view QuasimodalEventTap.m @ 18:78807eea31b7

Added EnsoKeyNotifier.m from Enso source, renamed to QuasimodalEventTap.m w/ some notification center stuff removed and formatting fixed to match this codebase.
author Atul Varma <avarma@mozilla.com>
date Sun, 11 Apr 2010 23:11:14 -0700
parents
children 8053681846ad
line wrap: on
line source

#include <AppKit/NSWorkspace.h>
#include <ApplicationServices/ApplicationServices.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDistributedNotificationCenter.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#include <stdio.h>

#define QUASIMODE_KEY kCGEventFlagMaskAlternate
#define MAX_STR_LEN 10

#ifdef DEBUG
#define DEBUG_MSG(msg) printf(msg);
#else
#define DEBUG_MSG(msg)
#endif

CGKeyCode lastQuasimodalKeyCode;
CGEventFlags lastQuasimodalKeyFlags;
int numQuasimodalKeyDowns = 0;

static BOOL inQuasimode = NO;

void sendSomeKeyEvent() {
  // TODO: Send some-key event
}

CGEventRef processEvent(CGEventTapProxy proxy,
                        CGEventType type,
                        CGEventRef event,
                        void *refcon)
{
  BOOL passOnEvent = !inQuasimode;

  if (type == kCGEventFlagsChanged) {
    CGEventFlags flags = CGEventGetFlags(event);

    if (inQuasimode) {
      if (!(flags & QUASIMODE_KEY)) {
        // TODO: Send quasimodeend event
        inQuasimode = NO;
        if (numQuasimodalKeyDowns == 1) {
          CGEventRef event[2];

          DEBUG_MSG("Re-posting single keypress\n");

          event[0] = CGEventCreateKeyboardEvent(
            NULL,
            (CGKeyCode) lastQuasimodalKeyCode,
            true
          );

          event[1] = CGEventCreateKeyboardEvent(
            NULL,
            (CGKeyCode) lastQuasimodalKeyCode,
            false
          );

          CGEventSetFlags(event[0], lastQuasimodalKeyFlags);
          CGEventSetFlags(event[1], lastQuasimodalKeyFlags);

          CGEventTapPostEvent(proxy, event[0]);
          CGEventTapPostEvent(proxy, event[1]);

          CFRelease(event[0]);
          CFRelease(event[1]);
        }
        DEBUG_MSG("Exit quasimode\n");
      }
    } else {
      if (flags & QUASIMODE_KEY) {
        // TODO: Send quasimodestart event
        inQuasimode = YES;
        passOnEvent = NO;
        numQuasimodalKeyDowns = 0;
        DEBUG_MSG("Enter quasimode\n");
      } else {
        sendSomeKeyEvent();
      }
    }
  } else {
    /* Key up/down event */

    if (inQuasimode) {
      UniChar strbuf[MAX_STR_LEN];
      UniCharCount charsCopied;

      CGEventKeyboardGetUnicodeString(
        event,
        MAX_STR_LEN,
        &charsCopied,
        strbuf
      );

      NSString *chars = [NSString stringWithCharacters: strbuf
                                  length: charsCopied];
      NSString *eventType;

      int64_t keycode = CGEventGetIntegerValueField(
        event,
        kCGKeyboardEventKeycode
      );

      if (type == kCGEventKeyDown) {
        numQuasimodalKeyDowns += 1;
        lastQuasimodalKeyCode = keycode;
        lastQuasimodalKeyFlags = CGEventGetFlags(event);
        eventType = @"keyDown";
      } else
        eventType = @"keyUp";

      NSNumber *keycodeNum = [NSNumber numberWithUnsignedInt: keycode];

      // TODO: Send event
    } else {
      sendSomeKeyEvent();
    }
  }

  if (passOnEvent)
    return event;
  else
    return NULL;
}

CGEventRef myCallback(CGEventTapProxy proxy,
                      CGEventType type,
                      CGEventRef event,
                      void *refcon)
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  CGEventRef retval;
  NSString *bundleId = [
    [[NSWorkspace sharedWorkspace] activeApplication] 
    objectForKey: @"NSApplicationBundleIdentifier"
  ];

  if (bundleId &&
      [bundleId isEqualToString: @"com.blizzard.worldofwarcraft"]) {
    retval = event;
  } else {
    retval = processEvent(proxy, type, event, refcon);
  }

  [pool release];

  return retval;
}

int main(int argc, const char *argv[] )
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  CGEventMask mask = (CGEventMaskBit(kCGEventKeyDown) |
                      CGEventMaskBit(kCGEventKeyUp) |
                      CGEventMaskBit(kCGEventFlagsChanged));

  CFMachPortRef portRef = CGEventTapCreate(kCGHIDEventTap,
                                           kCGHeadInsertEventTap,
                                           0,
                                           mask,
                                           myCallback,
                                           NULL);

  CFRunLoopSourceRef rlSrcRef;

  if (portRef == NULL) {
        printf( "CGEventTapCreate() failed.\n" );
        return -1;
  }

  rlSrcRef = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
                                           portRef,
                                           0);

  CFRunLoopAddSource(CFRunLoopGetCurrent(),
                     rlSrcRef,
                     kCFRunLoopDefaultMode);

  // TODO: Run app.

  CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
                        rlSrcRef,
                        kCFRunLoopDefaultMode);

  CFRelease( rlSrcRef );
  CFRelease( portRef );

  [pool release];

  return 0;
}