Подтвердить что ты не робот

Измените 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
    }];
}

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

4b9b3361

Ответ 1

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


EDIT

Здесь простая программа, которая во время работы заменяет каждое "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 "нажатие клавиши. Я был в замешательстве. Я вспомнил, что еще не остановил приложение.)

Ответ 2

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

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

}

Ответ 3

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