view QuasimodalEventTap.m @ 20:58522f82a39e

Brought back the notification center code to QuasimodalEventTap, we can just use the in-process one instead of setting up some complicated observer protocol.
author Atul Varma <avarma@mozilla.com>
date Mon, 12 Apr 2010 00:15:06 -0700
parents 8053681846ad
children cdc615772d43
line wrap: on
line source

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

#import "QuasimodalEventTap.h"

#define QUASIMODE_KEY kCGEventFlagMaskAlternate
#define MAX_STR_LEN 10

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

static CGEventRef eventTapCallback(CGEventTapProxy proxy,
                                   CGEventType type,
                                   CGEventRef event,
                                   void *refcon);

@implementation QuasimodalEventTap
- (void)sendSomeKeyEvent {
  NSArray *keys = [NSArray arrayWithObjects: @"event", nil];
  NSArray *values = [NSArray arrayWithObjects: @"someKey", nil];
  NSDictionary *dict = [NSDictionary dictionaryWithObjects: values
                                     forKeys: keys];

  [center postNotificationName: @"QuasimodalEventTap"
          object: name
          userInfo: dict];
}

- (CGEventRef)processEventWithProxy: (CGEventTapProxy)proxy
                               type: (CGEventType)type
                              event: (CGEventRef)event {
  BOOL passOnEvent = !inQuasimode;

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

    if (inQuasimode) {
      if (!(flags & QUASIMODE_KEY)) {
        NSArray *keys = [NSArray arrayWithObjects: @"event", nil];
        NSArray *values = [NSArray arrayWithObjects: @"quasimodeEnd", nil];
        NSDictionary *dict = [NSDictionary dictionaryWithObjects: values
                                           forKeys: keys];

        [center postNotificationName: @"QuasimodalEventTap"
                object: name
                userInfo: dict];

        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) {
        NSArray *keys = [NSArray arrayWithObjects: @"event", nil];
        NSArray *values = [NSArray arrayWithObjects: @"quasimodeStart", nil];
        NSDictionary *dict = [NSDictionary dictionaryWithObjects: values
                                           forKeys: keys];

        [center postNotificationName: @"QuasimodalEventTap"
                object: name
                userInfo: dict];

        inQuasimode = YES;
        passOnEvent = NO;
        numQuasimodalKeyDowns = 0;
        DEBUG_MSG("Enter quasimode\n");
      } else {
        [self 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];

      NSArray *keys = [NSArray arrayWithObjects: @"event", @"chars",
                               @"keycode", nil];
      NSArray *values = [NSArray arrayWithObjects: eventType, chars,
                                 keycodeNum, nil];
      NSDictionary *dict = [NSDictionary dictionaryWithObjects: values
                                         forKeys: keys];

      [center postNotificationName: @"QuasimodalEventTap"
              object: name
              userInfo: dict];
    } else {
      [self sendSomeKeyEvent];
    }
  }

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

- (id)initWithName:(NSString *)objectName {
  if (self = [super init]) {
    numQuasimodalKeyDowns = 0;
    inQuasimode = NO;

    name = [objectName copy];
    center = [NSNotificationCenter defaultCenter];

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

    portRef = CGEventTapCreate(kCGHIDEventTap,
                               kCGHeadInsertEventTap,
                               0,
                               mask,
                               eventTapCallback,
                               self);

    if (portRef == NULL)
      printf( "CGEventTapCreate() failed.\n" );

    rlSrcRef = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
                                             portRef,
                                             0);

    CFRunLoopAddSource(CFRunLoopGetCurrent(),
                       rlSrcRef,
                       kCFRunLoopDefaultMode);
  }
  return self;
}

- (void)finalize {
  CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
                        rlSrcRef,
                        kCFRunLoopDefaultMode);

  CFRelease(rlSrcRef);
  CFRelease(portRef);
}
@end

static CGEventRef eventTapCallback(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 {
    QuasimodalEventTap *tap = (QuasimodalEventTap *) refcon;
    retval = [tap processEventWithProxy: proxy
                  type: type
                  event: event];
  }

  [pool release];

  return retval;
}