修改NSEvent以发送与按下的键不同的键

cks*_*ubs 21 macos objective-c keyboard-hook nsevent

我正在尝试创建一个OS X键盘钩子用于辅助技术目的(即不要担心,而不是键盘记录器).

当用户按下某个键时,我想阻止真正的按键并发送假按键(我选择的字符).

我有以下代码:

- (void) hookTheKeyboard {
    CGEventMask keyboardMask = CGEventMaskBit(kCGEventKeyDown);
    id eventHandler = [NSEvent addGlobalMonitorForEventsMatchingMask:keyboardMask handler:^(NSEvent *keyboardEvent) {
        NSLog(@"keyDown: %c", [[keyboardEvent characters] characterAtIndex:0]);
        //Want to: Stop the keyboard input
        //Want to: Send another key input instead
    }];
}
Run Code Online (Sandbox Code Playgroud)

有任何帮助实现这些目标吗?基本上修改NSEvent"keyboardEvent"以发送不同的字符.谢谢.

Dav*_*ong 45

您无法使用NSEventAPI 执行此操作,但您可以使用API 执行此操作CGEventTap.您可以创建一个活动事件点击并注册一个回调,该回调接收CGEventRef并可以修改它(如果需要)并返回它以修改实际的事件流.


编辑

这是一个简单的程序,在运行时,用"v"替换每个"b"键击:

#import <Cocoa/Cocoa.h>

CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
  //0x0b is the virtual keycode for "b"
  //0x09 is the virtual keycode for "v"
  if (CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode) == 0x0B) {
    CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, 0x09);
  }

  return event;
}

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

  CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents, myCGEventCallback, NULL);

  if (!eventTap) {
    NSLog(@"Couldn't create event tap!");
    exit(1);
  }

  runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);

  CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);

  CGEventTapEnable(eventTap, true);

  CFRunLoopRun();

  CFRelease(eventTap);
  CFRelease(runLoopSource);
  [pool release];

  exit(0);
}
Run Code Online (Sandbox Code Playgroud)

(有趣的故事:当我正在编辑这篇文章时,我一直试着写"替换每一个'b'击键",但它仍然以"替换每个'v'键击"的形式出现.我很困惑.然后我记得那个我还没有停止应用程序.)

  • 万一它有帮助:CGEvent apis都是C.不需要objc. (4认同)
  • 我必须在"系统偏好设置">"辅助功能"中"启用对辅助设备的访问"才能使其正常工作.如果您使用管理员帐户登录,我认为这对CGEventTap不是必需的. (4认同)

jam*_*rez 5

我遇到了这个答案,需要做同样的事情,但只针对我自己的应用程序中的事件而不是 global 。对于这个更简单的问题,有一个更简单的解决方案,我在这里指出它对其他人有用:

  • 我通过为 sendEvent: 创建覆盖来拦截窗口中的事件。然后我检查关键事件(KeyUp 或 KeyDown),然后简单地使用几乎所有来自前一个事件的数据创建一个新事件,然后用这个事件调用 NSWindow 超类。

这对我来说似乎很完美,我什至不必修改 keyCode 部分 - 但也许这可能是一个问题......

Swift 中的示例:

class KeyInterceptorWindow : NSWindow {

    override func sendEvent(theEvent: NSEvent) {

        if theEvent.type == .KeyDown || theEvent.type == .KeyUp {
            println(theEvent.description)
            let newEvent = NSEvent.keyEventWithType(theEvent.type, 
                location: theEvent.locationInWindow, 
                modifierFlags: theEvent.modifierFlags, 
                timestamp: theEvent.timestamp, 
                windowNumber: theEvent.windowNumber, 
                context: theEvent.context, 
                characters: "H", 
                charactersIgnoringModifiers: theEvent.charactersIgnoringModifiers!, 
                isARepeat: theEvent.ARepeat, 
                keyCode: theEvent.keyCode)
            super.sendEvent(newEvent!)
        } else {
            super.sendEvent(theEvent)
        }

    }

}
Run Code Online (Sandbox Code Playgroud)