I'm trying to modify this code. It's a logging script that shows keypresses. Currently the log only shows the ABCs, numbers, and a combination of modifier+(key). Is it possible to show every keypress like "command", "option", "F1" and so on?
I don't know Objective-C and I'm blindly trying to change this code for a project of mine. Any help will take me a step further in this project and is much appreciated.
The repo: https://github.com/chbrown/osx-tap
My version that shows the actual keys names:
#import <Foundation/Foundation.h>
#include <ApplicationServices/ApplicationServices.h>
#import "key-tap.h"
@implementation LoggerConfig
@synthesize output;
@synthesize epoch;
@end
/* CGEventTapCallBack function signature:
proxy
A proxy for the event tap. See CGEventTapProxy. This callback function may pass this proxy to other functions such as the event-posting routines.
type
The event type of this event. See “Event Types.”
event
The incoming event. This event is owned by the caller, and you do not need to release it.
refcon
A pointer to user-defined data. You specify this pointer when you create the event tap. Several different event taps could use the same callback function, each tap with its own user-defined data.
*/
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
LoggerConfig *config = (__bridge LoggerConfig *)refcon;
@autoreleasepool {
NSMutableArray *modifiers = [NSMutableArray arrayWithCapacity:10];
CGEventFlags flags = CGEventGetFlags(event);
if ((flags & kCGEventFlagMaskShift) == kCGEventFlagMaskShift)
[modifiers addObject:@"shift"];
if ((flags & kCGEventFlagMaskControl) == kCGEventFlagMaskControl)
[modifiers addObject:@"control"];
if ((flags & kCGEventFlagMaskAlternate) == kCGEventFlagMaskAlternate)
[modifiers addObject:@"alt"];
if ((flags & kCGEventFlagMaskCommand) == kCGEventFlagMaskCommand)
[modifiers addObject:@"command"];
if ((flags & kCGEventFlagMaskSecondaryFn) == kCGEventFlagMaskSecondaryFn)
[modifiers addObject:@"fn"];
// Ignoring the following flags:
// kCGEventFlagMaskAlphaShift = NX_ALPHASHIFTMASK,
// kCGEventFlagMaskHelp = NX_HELPMASK,
// kCGEventFlagMaskNumericPad = NX_NUMERICPADMASK,
// kCGEventFlagMaskNonCoalesced = NX_NONCOALSESCEDMASK
NSString *modifierString = [modifiers componentsJoinedByString:@"+"];
// The incoming keycode. CGKeyCode is just a typedef of uint16_t, so we treat it like an int
CGKeyCode keycode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
// CGEventKeyboardGetUnicodeString
// Keypress code goes here.
NSString *action;
if (type == kCGEventKeyDown)
action = @"down";
else if (type == kCGEventKeyUp)
action = @"up";
else
action = @"other";
NSString *keytype;
switch (keycode) {
case 0: keytype = @"a"; break;
case 1: keytype = @"s"; break;
case 2: keytype = @"d"; break;
case 3: keytype = @"f"; break;
case 4: keytype = @"h"; break;
case 5: keytype = @"g"; break;
case 6: keytype = @"z"; break;
case 7: keytype = @"x"; break;
case 8: keytype = @"c"; break;
case 9: keytype = @"v"; break;
case 11: keytype = @"b"; break;
case 12: keytype = @"q"; break;
case 13: keytype = @"w"; break;
case 14: keytype = @"e"; break;
case 15: keytype = @"r"; break;
case 16: keytype = @"y"; break;
case 17: keytype = @"t"; break;
case 18: keytype = @"1"; break;
case 19: keytype = @"2"; break;
case 20: keytype = @"3"; break;
case 21: keytype = @"4"; break;
case 22: keytype = @"6"; break;
case 23: keytype = @"5"; break;
case 24: keytype = @"@"; break;
case 25: keytype = @"9"; break;
case 26: keytype = @"7"; break;
case 27: keytype = @"-"; break;
case 28: keytype = @"8"; break;
case 29: keytype = @"0"; break;
case 30: keytype = @"]"; break;
case 31: keytype = @"o"; break;
case 32: keytype = @"u"; break;
case 33: keytype = @"["; break;
case 34: keytype = @"i"; break;
case 35: keytype = @"p"; break;
case 37: keytype = @"l"; break;
case 38: keytype = @"j"; break;
case 39: keytype = @"'"; break;
case 40: keytype = @"k"; break;
case 41: keytype = @";"; break;
case 42: keytype = @"\\"; break;
case 43: keytype = @","; break;
case 44: keytype = @"/"; break;
case 45: keytype = @"n"; break;
case 46: keytype = @"m"; break;
case 47: keytype = @"."; break;
case 50: keytype = @"`"; break;
case 65: keytype = @"[decimal]"; break;
case 67: keytype = @"[asterisk]"; break;
case 69: keytype = @"[plus]"; break;
case 71: keytype = @"[clear]"; break;
case 75: keytype = @"[divide]"; break;
case 76: keytype = @"[enter]"; break;
case 78: keytype = @"[hyphen]"; break;
case 81: keytype = @"[equals]"; break;
case 82: keytype = @"0"; break;
case 83: keytype = @"1"; break;
case 84: keytype = @"2"; break;
case 85: keytype = @"3"; break;
case 86: keytype = @"4"; break;
case 87: keytype = @"5"; break;
case 88: keytype = @"6"; break;
case 89: keytype = @"7"; break;
case 91: keytype = @"8"; break;
case 92: keytype = @"9"; break;
case 36: keytype = @"[return]"; break;
case 48: keytype = @"[tab]"; break;
case 49: keytype = @" "; break;
case 51: keytype = @"[del]"; break;
case 53: keytype = @"[esc]"; break;
case 54: keytype = @"[right-cmd]"; break;
case 55: keytype = @"[left-cmd]"; break;
case 56: keytype = @"[left-shift]"; break;
case 57: keytype = @"[caps]"; break;
case 58: keytype = @"[left-option]"; break;
case 59: keytype = @"[left-ctrl]"; break;
case 60: keytype = @"[right-shift]"; break;
case 61: keytype = @"[right-option]"; break;
case 62: keytype = @"[right-ctrl]"; break;
case 63: keytype = @"[fn]"; break;
case 64: keytype = @"[f17]"; break;
case 72: keytype = @"[volup]"; break;
case 73: keytype = @"[voldown]"; break;
case 74: keytype = @"[mute]"; break;
case 79: keytype = @"[f18]"; break;
case 80: keytype = @"[f19]"; break;
case 90: keytype = @"[f20]"; break;
case 96: keytype = @"[f5]"; break;
case 97: keytype = @"[f6]"; break;
case 98: keytype = @"[f7]"; break;
case 99: keytype = @"[f3]"; break;
case 100: keytype = @"[f8]"; break;
case 101: keytype = @"[f9]"; break;
case 103: keytype = @"[f11]"; break;
case 105: keytype = @"[f13]"; break;
case 106: keytype = @"[f16]"; break;
case 107: keytype = @"[f14]"; break;
case 109: keytype = @"[f10]"; break;
case 111: keytype = @"[f12]"; break;
case 113: keytype = @"[f15]"; break;
case 114: keytype = @"[help]"; break;
case 115: keytype = @"[home]"; break;
case 116: keytype = @"[pgup]"; break;
case 117: keytype = @"[fwddel]"; break;
case 118: keytype = @"[f4]"; break;
case 119: keytype = @"[end]"; break;
case 120: keytype = @"[f2]"; break;
case 121: keytype = @"[pgdown]"; break;
case 122: keytype = @"[f1]"; break;
case 123: keytype = @"[left]"; break;
case 124: keytype = @"[right]"; break;
case 125: keytype = @"[down]"; break;
case 126: keytype = @"[up]"; break;
default: keytype = @"none"; break;
}
NSTimeInterval offset = [[NSDate date] timeIntervalSince1970] - config.epoch;
// logLine format:
// ticks since started <TAB> key code <TAB> action <TAB> modifiers
// so it'll look something like "13073 45 up shift+command"
NSString *logLine = [NSString stringWithFormat:@"%d\t%d\t%@\t%@\t%@\n",
(int)offset, keycode ,keytype, action, modifierString];
NSLog(@"> %@", logLine);
[config.output writeData:[logLine dataUsingEncoding:NSUTF8StringEncoding]];
}
// We must return the event for it to be useful.
return event;
}
int main(void) {
// set up the file that we'll be logging into
// NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LoggerConfig *config = [[LoggerConfig alloc] init];
NSUserDefaults *args = [NSUserDefaults standardUserDefaults];
// grabs command line arguments --directory
NSString *directory = [args stringForKey:@"directory"];
if (!directory) {
// default to /var/log/osx-tap
directory = @"/var/log/osx-tap";
}
NSFileManager *fileManager = [NSFileManager defaultManager];
// create directory if needed (since withIntermediateDirectories:YES,
// this shouldn't fail if the directory already exists)
bool directory_success = [fileManager createDirectoryAtPath:directory
withIntermediateDirectories:YES attributes:nil error:nil];
if (!directory_success) {
NSLog(@"Could not create directory: %@", directory);
return 1;
}
config.epoch = [[NSDate date] timeIntervalSince1970];
NSString *filename = [NSString stringWithFormat:@"%d.log", (int)config.epoch];
NSString *filepath = [NSString pathWithComponents:@[directory, filename]];
bool create_success = [fileManager createFileAtPath:filepath contents:nil attributes:nil];
if (!create_success) {
NSLog(@"Could not create file: %@", filepath);
return 1;
}
// now that it's been created, we can open the file
config.output = [NSFileHandle fileHandleForWritingAtPath:filepath];
// [config.output seekToEndOfFile];
// Create an event tap. We are interested in key ups and downs.
CGEventMask eventMask = ((1 << kCGEventKeyDown) | (1 << kCGEventKeyUp));
/*
CGEventTapCreate(CGEventTapLocation tap, CGEventTapPlacement place,
CGEventTapOptions options, CGEventMask eventsOfInterest,
CGEventTapCallBack callback, void *refcon
CGEventTapLocation tap:
kCGHIDEventTap
Specifies that an event tap is placed at the point where HID system events enter the window server.
kCGSessionEventTap
Specifies that an event tap is placed at the point where HID system and remote control events enter a login session.
kCGAnnotatedSessionEventTap
Specifies that an event tap is placed at the point where session events have been annotated to flow to an application.
CGEventTapPlacement place:
kCGHeadInsertEventTap
Specifies that a new event tap should be inserted before any pre-existing event taps at the same location.
kCGTailAppendEventTap
Specifies that a new event tap should be inserted after any pre-existing event taps at the same location.
CGEventTapOptions options:
kCGEventTapOptionDefault = 0x00000000
kCGEventTapOptionListenOnly = 0x00000001
...
CGEventTapCallBack has arguments:
(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
*/
// we don't want to discard config
// CFBridgingRetain(config);
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0,
eventMask, myCGEventCallback, (__bridge void *)config);
if (!eventTap) {
NSLog(@"failed to create event tap");
return 1;
}
if (!CGEventTapIsEnabled(eventTap)) {
NSLog(@"event tap is not enabled");
return 1;
}
/* Create a run loop source.
allocator
The allocator to use to allocate memory for the new object. Pass NULL or kCFAllocatorDefault to use the current default allocator.
port
The Mach port for which to create a CFRunLoopSource object.
order
A priority index indicating the order in which run loop sources are processed. order is currently ignored by CFMachPort run loop sources. Pass 0 for this value.
*/
CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
/* Adds a CFRunLoopSource object to a run loop mode.
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
rl
The run loop to modify.
source
The run loop source to add. The source is retained by the run loop.
mode
The run loop mode to which to add source. Use the constant kCFRunLoopCommonModes to add source to the set of objects monitored by all the common modes.
*/
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
// Enable the event tap.
// CGEventTapEnable(eventTap, true);
// Runs the current thread’s CFRunLoop object in its default mode indefinitely:
CFRunLoopRun();
return 0;
}
I've tried to understand this function but failed to do so after about 3 hours of fiddling.