Измените 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" для отправки другого символа. Благодарю.
Ответы
Ответ 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)
}
}
}