Измените NSEvent, чтобы отправить другую клавишу, чем та, которая была нажата - PullRequest
21 голосов
/ 26 апреля 2011

Я пытаюсь создать клавиатуру 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
    }];
}

Любая помощь в достижении любой из этих целей? В основном, изменение NSEvent "keyboardEvent" для отправки другого символа. Благодарю.

Ответы [ 3 ]

45 голосов
/ 26 апреля 2011

Вы не можете сделать это с NSEvent API, но вы можете сделать это с CGEventTap.Вы можете создать активную запись события и зарегистрировать обратный вызов , который получает CGEventRef и может изменить его (при необходимости) и вернуть его для изменения фактического потока событий.


РЕДАКТИРОВАТЬ

Вот простая программа, которая во время работы заменяет каждое нажатие клавиши "b" на "v":

#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);
}

(Забавная история: как я и былредактируя этот пост, я продолжал пытаться написать «заменяет каждое нажатие клавиши« b »», но оно продолжало появляться как «заменяет каждое нажатие клавиши« v »». Я был сбит с толку. Потом я вспомнил, что не остановил приложениееще.)

4 голосов
/ 28 апреля 2015

Я столкнулся с этим ответом, мне нужно было сделать то же самое, но только для событий в моем собственном приложении , не глобальных. Существует гораздо более простое решение для этой гораздо более простой проблемы, которую я отмечаю здесь, в случае, если это полезно для всех остальных:

  • Я перехватил событие в окне, создав переопределение для 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)
    }
}

}

0 голосов
/ 04 апреля 2019

Swift 4+ версия ответа james_alvarez :

class KeyInterceptorWindow: NSWindow {
    override func sendEvent(_ event: NSEvent) {
        if [.keyDown, .keyUp].contains(event.type) {
            let newEvent = NSEvent.keyEvent(with: event.type,
                                            location: event.locationInWindow,
                                            modifierFlags: event.modifierFlags,
                                            timestamp: event.timestamp,
                                            windowNumber: event.windowNumber,
                                            context: nil,
                                            characters: "H",
                                            charactersIgnoringModifiers: event.charactersIgnoringModifiers ?? "",
                                            isARepeat: event.isARepeat,
                                            keyCode: event.keyCode)

            if let newEvent = newEvent {
                super.sendEvent(newEvent)
            }
        } else {
            super.sendEvent(event)
        }
    }
}
...