view QuasimodalEventTap.m @ 30:9aa3c2fd0baf

Quasimode app now terminates on window.close().
author Atul Varma <avarma@mozilla.com>
date Mon, 12 Apr 2010 11:42:22 -0700
parents 284fe09c6e64
children
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 MAX_STR_LEN 10

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

static NSNumber *isPressed(CGEventFlags flags,
                           CGEventFlags filter)
{
  return [NSNumber numberWithBool: ((flags & filter) != 0)];
}

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

@implementation QuasimodalEventTap
- (void)sendSomeKeyEvent {
  [center postNotificationName: @"NonQuasimodalKeypress"
          object: name
          userInfo: nil];
}

- (void)sendQuasimodeEvent:(CGEventRef)event
                  withType:(NSString *)eventType {
  UniChar strbuf[MAX_STR_LEN];
  UniCharCount charsCopied;

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

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

  int64_t keycode = CGEventGetIntegerValueField(event,
                                                kCGKeyboardEventKeycode);

  NSNumber *keycodeNum = [NSNumber numberWithUnsignedInt: keycode];

  NSArray *keys = [NSArray arrayWithObjects: @"type",
                           @"keyIdentifier",
                           @"keyLocation",
                           @"ctrlKey",
                           @"altKey",
                           @"shiftKey",
                           @"metaKey",
                           nil];

  CGEventFlags flags = CGEventGetFlags(event);

  NSArray *values = [NSArray arrayWithObjects: eventType,
                             chars,
                             keycodeNum,
                             isPressed(flags, kCGEventFlagMaskControl),
                             isPressed(flags, kCGEventFlagMaskAlternate),
                             isPressed(flags, kCGEventFlagMaskShift),
                             isPressed(flags, kCGEventFlagMaskCommand),
                             nil];

  NSDictionary *dict = [NSDictionary dictionaryWithObjects: values
                                     forKeys: keys];

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

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

  if (type == kCGEventFlagsChanged) {
    if (inQuasimode) {
      if (!(flags & quasimodeKey)) {
        [self sendQuasimodeEvent: event withType: @"quasimodeend"];

        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 & quasimodeKey) {
        [self sendQuasimodeEvent: event withType: @"quasimodestart"];

        inQuasimode = YES;
        passOnEvent = NO;
        numQuasimodalKeyDowns = 0;
        DEBUG_MSG("Enter quasimode\n");
      } else {
        [self sendSomeKeyEvent];
      }
    }
  } else {
    /* Key up/down event */

    if (inQuasimode) {
      NSString *eventType;
      int64_t keycode = CGEventGetIntegerValueField(
        event,
        kCGKeyboardEventKeycode
      );

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

      [self sendQuasimodeEvent: event withType: eventType];
    } else {
      [self sendSomeKeyEvent];
    }
  }

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

- (id)initWithName:(NSString *)objectName quasimodeKey:(CGEventFlags)key {
  if (self = [super init]) {
    quasimodeKey = key;
    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;
}