From d69056799a399058005b4950751397a31110de4a Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Mon, 7 Aug 2017 20:23:44 +0200 Subject: v0.0.1 --- src/event_tap.c | 39 ++++++++ src/event_tap.h | 25 ++++++ src/hotkey.c | 186 ++++++++++++++++++++++++++++++++++++++ src/hotkey.h | 81 +++++++++++++++++ src/hotload.c | 96 ++++++++++++++++++++ src/hotload.h | 39 ++++++++ src/locale.c | 117 ++++++++++++++++++++++++ src/locale.h | 11 +++ src/parse.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.h | 26 ++++++ src/skhd.c | 216 +++++++++++++++++++++++++++++++++++++++++++++ src/tokenize.c | 171 +++++++++++++++++++++++++++++++++++ src/tokenize.h | 66 ++++++++++++++ 13 files changed, 1343 insertions(+) create mode 100644 src/event_tap.c create mode 100644 src/event_tap.h create mode 100644 src/hotkey.c create mode 100644 src/hotkey.h create mode 100644 src/hotload.c create mode 100644 src/hotload.h create mode 100644 src/locale.c create mode 100644 src/locale.h create mode 100644 src/parse.c create mode 100644 src/parse.h create mode 100644 src/skhd.c create mode 100644 src/tokenize.c create mode 100644 src/tokenize.h (limited to 'src') diff --git a/src/event_tap.c b/src/event_tap.c new file mode 100644 index 0000000..5f44ff4 --- /dev/null +++ b/src/event_tap.c @@ -0,0 +1,39 @@ +#include "event_tap.h" + +bool event_tap_enabled(struct event_tap *event_tap) +{ + bool result = (event_tap->handle && CGEventTapIsEnabled(event_tap->handle)); + return result; +} + +bool event_tap_begin(struct event_tap *event_tap, event_tap_callback *callback) +{ + event_tap->handle = CGEventTapCreate(kCGSessionEventTap, + kCGHeadInsertEventTap, + kCGEventTapOptionDefault, + event_tap->mask, + callback, + event_tap); + + bool result = event_tap_enabled(event_tap); + if(result) { + event_tap->runloop_source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, + event_tap->handle, + 0); + CFRunLoopAddSource(CFRunLoopGetMain(), event_tap->runloop_source, kCFRunLoopCommonModes); + } + + return result; +} + +void event_tap_end(struct event_tap *event_tap) +{ + if(event_tap_enabled(event_tap)) { + CGEventTapEnable(event_tap->handle, false); + CFMachPortInvalidate(event_tap->handle); + CFRunLoopRemoveSource(CFRunLoopGetMain(), event_tap->runloop_source, kCFRunLoopCommonModes); + CFRelease(event_tap->runloop_source); + CFRelease(event_tap->handle); + event_tap->handle = NULL; + } +} diff --git a/src/event_tap.h b/src/event_tap.h new file mode 100644 index 0000000..b126ce4 --- /dev/null +++ b/src/event_tap.h @@ -0,0 +1,25 @@ +#ifndef SKHD_EVENT_TAP_H +#define SKHD_EVENT_TAP_H + +#include +#include + +struct event_tap +{ + CFMachPortRef handle; + CFRunLoopSourceRef runloop_source; + CGEventMask mask; +}; + +#define EVENT_TAP_CALLBACK(name) \ + CGEventRef name(CGEventTapProxy proxy, \ + CGEventType type, \ + CGEventRef event, \ + void *reference) +typedef EVENT_TAP_CALLBACK(event_tap_callback); + +bool event_tap_enabled(struct event_tap *event_tap); +bool event_tap_begin(struct event_tap *event_tap, event_tap_callback *callback); +void event_tap_end(struct event_tap *event_tap); + +#endif diff --git a/src/hotkey.c b/src/hotkey.c new file mode 100644 index 0000000..d34d9a0 --- /dev/null +++ b/src/hotkey.c @@ -0,0 +1,186 @@ +#include "hotkey.h" +#include "locale.h" +#include "parse.h" + +#include +#include + +#define internal static +#define local_persist static + +internal bool +execute_hotkey(struct hotkey *hotkey) +{ + local_persist char arg[] = "-c"; + local_persist char *shell = NULL; + if(!shell) + { + char *env_shell = getenv("SHELL"); + shell = env_shell ? env_shell : "/bin/bash"; + } + + int cpid = fork(); + if(cpid == 0) + { + char *exec[] = { shell, arg, hotkey->command, NULL}; + int status_code = execvp(exec[0], exec); + exit(status_code); + } + + return true; +} + +internal bool +compare_cmd(struct hotkey *a, struct hotkey *b) +{ + if(has_flags(a, Hotkey_Flag_Cmd)) { + return (has_flags(b, Hotkey_Flag_LCmd) || + has_flags(b, Hotkey_Flag_RCmd) || + has_flags(b, Hotkey_Flag_Cmd)); + } else { + return ((has_flags(a, Hotkey_Flag_LCmd) == has_flags(b, Hotkey_Flag_LCmd)) && + (has_flags(a, Hotkey_Flag_RCmd) == has_flags(b, Hotkey_Flag_RCmd)) && + (has_flags(a, Hotkey_Flag_Cmd) == has_flags(b, Hotkey_Flag_Cmd))); + } +} + +internal bool +compare_shift(struct hotkey *a, struct hotkey *b) +{ + if(has_flags(a, Hotkey_Flag_Shift)) { + return (has_flags(b, Hotkey_Flag_LShift) || + has_flags(b, Hotkey_Flag_RShift) || + has_flags(b, Hotkey_Flag_Shift)); + } else { + return ((has_flags(a, Hotkey_Flag_LShift) == has_flags(b, Hotkey_Flag_LShift)) && + (has_flags(a, Hotkey_Flag_RShift) == has_flags(b, Hotkey_Flag_RShift)) && + (has_flags(a, Hotkey_Flag_Shift) == has_flags(b, Hotkey_Flag_Shift))); + } +} + +internal bool +compare_alt(struct hotkey *a, struct hotkey *b) +{ + if(has_flags(a, Hotkey_Flag_Alt)) { + return (has_flags(b, Hotkey_Flag_LAlt) || + has_flags(b, Hotkey_Flag_RAlt) || + has_flags(b, Hotkey_Flag_Alt)); + } else { + return ((has_flags(a, Hotkey_Flag_LAlt) == has_flags(b, Hotkey_Flag_LAlt)) && + (has_flags(a, Hotkey_Flag_RAlt) == has_flags(b, Hotkey_Flag_RAlt)) && + (has_flags(a, Hotkey_Flag_Alt) == has_flags(b, Hotkey_Flag_Alt))); + } +} + +internal bool +compare_ctrl(struct hotkey *a, struct hotkey *b) +{ + if(has_flags(a, Hotkey_Flag_Control)) { + return (has_flags(b, Hotkey_Flag_LControl) || + has_flags(b, Hotkey_Flag_RControl) || + has_flags(b, Hotkey_Flag_Control)); + } else { + return ((has_flags(a, Hotkey_Flag_LControl) == has_flags(b, Hotkey_Flag_LControl)) && + (has_flags(a, Hotkey_Flag_RControl) == has_flags(b, Hotkey_Flag_RControl)) && + (has_flags(a, Hotkey_Flag_Control) == has_flags(b, Hotkey_Flag_Control))); + } +} + +internal inline bool +same_hotkey(struct hotkey *a, struct hotkey *b) +{ + if(a && b) { + return compare_cmd(a, b) && + compare_shift(a, b) && + compare_alt(a, b) && + compare_ctrl(a, b) && + a->key == b->key; + } + + return false; +} + +internal bool +find_hotkey(struct hotkey *seek, struct hotkey **result, struct hotkey *hotkeys) +{ + struct hotkey *hotkey = hotkeys; + while(hotkey) { + if(same_hotkey(hotkey, seek)) { + *result = hotkey; + return true; + } + + hotkey = hotkey->next; + } + + return false; +} + +void free_hotkeys(struct hotkey *hotkeys) +{ + struct hotkey *next, *hotkey = hotkeys; + while(hotkey) { + next = hotkey->next; + free(hotkey->command); + free(hotkey); + hotkey = next; + } +} + +bool find_and_exec_hotkey(struct hotkey *eventkey, struct hotkey *hotkeys) +{ + bool result = false; + struct hotkey *hotkey = NULL; + if(find_hotkey(eventkey, &hotkey, hotkeys)) { + if(execute_hotkey(hotkey)) { + result = has_flags(hotkey, Hotkey_Flag_Passthrough) ? false : true; + } + } + + return result; +} + +struct hotkey +cgevent_to_hotkey(CGEventFlags flags, uint32_t key) +{ + struct hotkey eventkey = {}; + eventkey.key = key; + + if((flags & Event_Mask_Cmd) == Event_Mask_Cmd) { + bool left = (flags & Event_Mask_LCmd) == Event_Mask_LCmd; + bool right = (flags & Event_Mask_RCmd) == Event_Mask_RCmd; + + if(left) add_flags(&eventkey, Hotkey_Flag_LCmd); + if(right) add_flags(&eventkey, Hotkey_Flag_RCmd); + if(!left && !right) add_flags(&eventkey, Hotkey_Flag_Cmd); + } + + if((flags & Event_Mask_Shift) == Event_Mask_Shift) { + bool left = (flags & Event_Mask_LShift) == Event_Mask_LShift; + bool right = (flags & Event_Mask_RShift) == Event_Mask_RShift; + + if(left) add_flags(&eventkey, Hotkey_Flag_LShift); + if(right) add_flags(&eventkey, Hotkey_Flag_RShift); + if(!left && !right) add_flags(&eventkey, Hotkey_Flag_Shift); + } + + if((flags & Event_Mask_Alt) == Event_Mask_Alt) { + bool left = (flags & Event_Mask_LAlt) == Event_Mask_LAlt; + bool right = (flags & Event_Mask_RAlt) == Event_Mask_RAlt; + + if(left) add_flags(&eventkey, Hotkey_Flag_LAlt); + if(right) add_flags(&eventkey, Hotkey_Flag_RAlt); + if(!left && !right) add_flags(&eventkey, Hotkey_Flag_Alt); + } + + if((flags & Event_Mask_Control) == Event_Mask_Control) { + bool left = (flags & Event_Mask_LControl) == Event_Mask_LControl; + bool right = (flags & Event_Mask_RControl) == Event_Mask_RControl; + + if(left) add_flags(&eventkey, Hotkey_Flag_LControl); + if(right) add_flags(&eventkey, Hotkey_Flag_RControl); + if(!left && !right) add_flags(&eventkey, Hotkey_Flag_Control); + } + + return eventkey; +} diff --git a/src/hotkey.h b/src/hotkey.h new file mode 100644 index 0000000..293f04f --- /dev/null +++ b/src/hotkey.h @@ -0,0 +1,81 @@ +#ifndef SKHD_HOTKEY_H +#define SKHD_HOTKEY_H + +#include +#include +#include + +#define internal static + +enum osx_event_mask +{ + Event_Mask_Alt = 0x00080000, + Event_Mask_LAlt = 0x00000020, + Event_Mask_RAlt = 0x00000040, + + Event_Mask_Shift = 0x00020000, + Event_Mask_LShift = 0x00000002, + Event_Mask_RShift = 0x00000004, + + Event_Mask_Cmd = 0x00100000, + Event_Mask_LCmd = 0x00000008, + Event_Mask_RCmd = 0x00000010, + + Event_Mask_Control = 0x00040000, + Event_Mask_LControl = 0x00000001, + Event_Mask_RControl = 0x00002000, +}; + +enum hotkey_flag +{ + Hotkey_Flag_Alt = (1 << 0), + Hotkey_Flag_LAlt = (1 << 1), + Hotkey_Flag_RAlt = (1 << 2), + + Hotkey_Flag_Shift = (1 << 3), + Hotkey_Flag_LShift = (1 << 4), + Hotkey_Flag_RShift = (1 << 5), + + Hotkey_Flag_Cmd = (1 << 6), + Hotkey_Flag_LCmd = (1 << 7), + Hotkey_Flag_RCmd = (1 << 8), + + Hotkey_Flag_Control = (1 << 9), + Hotkey_Flag_LControl = (1 << 10), + Hotkey_Flag_RControl = (1 << 11), + + Hotkey_Flag_Passthrough = (1 << 12), +}; + +struct hotkey +{ + uint32_t flags; + uint32_t key; + char *command; + struct hotkey *next; +}; + +internal inline void +add_flags(struct hotkey *hotkey, uint32_t flag) +{ + hotkey->flags |= flag; +} + +internal inline bool +has_flags(struct hotkey *hotkey, uint32_t flag) +{ + bool result = hotkey->flags & flag; + return result; +} + +internal inline void +clear_flags(struct hotkey *hotkey, uint32_t flag) +{ + hotkey->flags &= ~flag; +} + +bool find_and_exec_hotkey(struct hotkey *eventkey, struct hotkey *hotkeys); +struct hotkey cgevent_to_hotkey(CGEventFlags flags, uint32_t key); +void free_hotkeys(struct hotkey *hotkeys); + +#endif diff --git a/src/hotload.c b/src/hotload.c new file mode 100644 index 0000000..8f39b15 --- /dev/null +++ b/src/hotload.c @@ -0,0 +1,96 @@ +#include "hotload.h" +#include "hotkey.h" + +#include +#include + +#define internal static + +internal char * +copy_string(const char *s) +{ + unsigned length = strlen(s); + char *result = malloc(length + 1); + memcpy(result, s, length); + result[length] = '\0'; + return result; +} + +char *file_directory(const char *file) +{ + char *last_slash = strrchr(file, '/'); + *last_slash = '\0'; + char *directory = copy_string(file); + *last_slash = '/'; + return directory; +} + +char *file_name(const char *file) +{ + char *last_slash = strrchr(file, '/'); + char *name = copy_string(last_slash + 1); + return name; +} + +void hotloader_add_file(struct hotloader *hotloader, const char *file) +{ + if(!hotloader->enabled) { + struct watched_file watch_info; + watch_info.directory = file_directory(file); + watch_info.filename = file_name(file); + hotloader->watch_list[hotloader->watch_count++] = watch_info; + printf("hotload: watching file '%s' in directory '%s'\n", watch_info.filename, watch_info.directory); + } +} + +bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback) +{ + if(!hotloader->enabled) { + if(hotloader->watch_count) { + CFStringRef string_refs[hotloader->watch_count]; + for(unsigned index = 0; index < hotloader->watch_count; ++index) { + string_refs[index] = CFStringCreateWithCString(kCFAllocatorDefault, + hotloader->watch_list[index].directory, + kCFStringEncodingUTF8); + } + + FSEventStreamContext context = {}; + context.info = (void *) hotloader; + + hotloader->enabled = true; + hotloader->path = (CFArrayRef) CFArrayCreate(NULL, (const void **) string_refs, hotloader->watch_count, &kCFTypeArrayCallBacks); + hotloader->flags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents; + hotloader->stream = FSEventStreamCreate(NULL, + callback, + &context, + hotloader->path, + kFSEventStreamEventIdSinceNow, + 0.5, + hotloader->flags); + FSEventStreamScheduleWithRunLoop(hotloader->stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + FSEventStreamStart(hotloader->stream); + return true; + } + } + return false; +} + +void hotloader_end(struct hotloader *hotloader) +{ + if(hotloader->enabled) { + FSEventStreamStop(hotloader->stream); + FSEventStreamInvalidate(hotloader->stream); + FSEventStreamRelease(hotloader->stream); + + CFIndex count = CFArrayGetCount(hotloader->path); + for(unsigned index = 0; index < count; ++index) { + CFStringRef string_ref = (CFStringRef) CFArrayGetValueAtIndex(hotloader->path, index); + free(hotloader->watch_list[index].directory); + free(hotloader->watch_list[index].filename); + CFRelease(string_ref); + } + + CFRelease(hotloader->path); + memset(hotloader, 0, sizeof(struct hotloader)); + } +} diff --git a/src/hotload.h b/src/hotload.h new file mode 100644 index 0000000..9b2d666 --- /dev/null +++ b/src/hotload.h @@ -0,0 +1,39 @@ +#ifndef SKHD_HOTLOAD_H +#define SKHD_HOTLOAD_H + +#include +#include + +#define HOTLOADER_CALLBACK(name) void name(ConstFSEventStreamRef stream,\ + void *context,\ + size_t count,\ + void *paths,\ + const FSEventStreamEventFlags *flags,\ + const FSEventStreamEventId *ids) +typedef HOTLOADER_CALLBACK(hotloader_callback); + +struct watched_file +{ + char *directory; + char *filename; +}; + +struct hotloader +{ + FSEventStreamEventFlags flags; + FSEventStreamRef stream; + CFArrayRef path; + bool enabled; + + struct watched_file watch_list[32]; + unsigned watch_count; +}; + +bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback); +void hotloader_end(struct hotloader *hotloader); +void hotloader_add_file(struct hotloader *hotloader, const char *file); + +char *file_directory(const char *file); +char *file_name(const char *file); + +#endif diff --git a/src/locale.c b/src/locale.c new file mode 100644 index 0000000..5b262c7 --- /dev/null +++ b/src/locale.c @@ -0,0 +1,117 @@ +#include "locale.h" + +#include +#include + +#define internal static +#define local_persist static + +bool same_string(char *text, unsigned length, const char *match) +{ + const char *at = match; + unsigned index = 0; + while(*at++ == text[index++] && index < length); + return (*at == '\0' && index == length) ? true : false; +} + +internal CFStringRef +cfstring_from_keycode(CGKeyCode keycode) +{ + TISInputSourceRef keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); + CFDataRef uchr = (CFDataRef) TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData); + CFRelease(keyboard); + + UCKeyboardLayout *keyboard_layout = (UCKeyboardLayout *) CFDataGetBytePtr(uchr); + if(keyboard_layout) { + UInt32 dead_key_state = 0; + UniCharCount max_string_length = 255; + UniCharCount string_length = 0; + UniChar unicode_string[max_string_length]; + + OSStatus status = UCKeyTranslate(keyboard_layout, keycode, + kUCKeyActionDown, 0, + LMGetKbdType(), 0, + &dead_key_state, + max_string_length, + &string_length, + unicode_string); + + if(string_length == 0 && dead_key_state) { + status = UCKeyTranslate(keyboard_layout, kVK_Space, + kUCKeyActionDown, 0, + LMGetKbdType(), 0, + &dead_key_state, + max_string_length, + &string_length, + unicode_string); + } + + if(string_length > 0 && status == noErr) { + return CFStringCreateWithCharacters(NULL, unicode_string, string_length); + } + } + + return NULL; +} + +uint32_t keycode_from_char(char key) +{ + uint32_t keycode = 0; + local_persist CFMutableDictionaryRef keycode_map = NULL; + if(!keycode_map) { + keycode_map = CFDictionaryCreateMutable(kCFAllocatorDefault, 128, &kCFCopyStringDictionaryKeyCallBacks, NULL); + for(unsigned index = 0; index < 128; ++index) { + CFStringRef key_string = cfstring_from_keycode(index); + if(key_string) { + CFDictionaryAddValue(keycode_map, key_string, (const void *)index); + CFRelease(key_string); + } + } + } + + UniChar uni_char = key; + CFStringRef char_str = CFStringCreateWithCharacters(kCFAllocatorDefault, &uni_char, 1); + CFDictionaryGetValueIfPresent(keycode_map, char_str, (const void **)&keycode); + CFRelease(char_str); + + return keycode; +} + +uint32_t keycode_from_literal(char *key, unsigned length) +{ + if(same_string(key, length, "return")) { + return kVK_Return; + } else if(same_string(key, length, "tab")) { + return kVK_Tab; + } else if(same_string(key, length, "space")) { + return kVK_Space; + } else if(same_string(key, length, "backspace")) { + return kVK_Delete; + } else if(same_string(key, length, "delete")) { + return kVK_ForwardDelete; + } else if(same_string(key, length, "escape")) { + return kVK_Escape; + } else if(same_string(key, length, "home")) { + return kVK_Home; + } else if(same_string(key, length, "end")) { + return kVK_End; + } else if(same_string(key, length, "pageup")) { + return kVK_PageUp; + } else if(same_string(key, length, "pagedown")) { + return kVK_PageDown; + } else if(same_string(key, length, "help")) { + return kVK_Help; + } else if(same_string(key, length, "left")) { + return kVK_LeftArrow; + } else if(same_string(key, length, "right")) { + return kVK_RightArrow; + } else if(same_string(key, length, "up")) { + return kVK_UpArrow; + } else if(same_string(key, length, "down")) { + return kVK_DownArrow; + } else if(same_string(key, length, "f1")) { + return kVK_F1; + } else { + return 0; + } +} diff --git a/src/locale.h b/src/locale.h new file mode 100644 index 0000000..2662cee --- /dev/null +++ b/src/locale.h @@ -0,0 +1,11 @@ +#ifndef SKHD_LOCALE_H +#define SKHD_LOCALE_H + +#include +#include + +uint32_t keycode_from_char(char key); +uint32_t keycode_from_literal(char *key, unsigned length); +bool same_string(char *text, unsigned length, const char *match); + +#endif diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..bac0215 --- /dev/null +++ b/src/parse.c @@ -0,0 +1,270 @@ +#include "parse.h" +#include "tokenize.h" +#include "locale.h" +#include "hotkey.h" + +#include +#include +#include + +#define internal static + +internal char * +read_file(const char *file) +{ + unsigned length; + char *buffer = NULL; + FILE *handle = fopen(file, "r"); + + if(handle) + { + fseek(handle, 0, SEEK_END); + length = ftell(handle); + fseek(handle, 0, SEEK_SET); + buffer = malloc(length + 1); + fread(buffer, length, 1, handle); + buffer[length] = '\0'; + fclose(handle); + } + + return buffer; +} + +internal char * +copy_string_count(char *s, int length) +{ + char *result = malloc(length + 1); + memcpy(result, s, length); + result[length] = '\0'; + return result; +} + +internal uint32_t +keycode_from_hex(char *hex) +{ + uint32_t result; + sscanf(hex, "%x", &result); + return result; +} + +internal char * +parse_command(struct parser *parser) +{ + struct token command = parser_previous(parser); + char *result = copy_string_count(command.text, command.length); + printf("\tcmd: '%s'\n", result); + return result; +} + +internal uint32_t +parse_key_hex(struct parser *parser) +{ + struct token key = parser_previous(parser); + char *hex = copy_string_count(key.text, key.length); + uint32_t keycode = keycode_from_hex(hex); + free(hex); + printf("\tkey: '%.*s' (%d)\n", key.length, key.text, keycode); + return keycode; +} + +internal uint32_t +parse_key(struct parser *parser) +{ + uint32_t keycode; + struct token key = parser_previous(parser); + if(key.length == 1) { + keycode = keycode_from_char(*key.text); + } else { + keycode = keycode_from_literal(key.text, key.length); + } + printf("\tkey: '%.*s' (%d)\n", key.length, key.text, keycode); + return keycode; +} + +internal const char *modifier_flags_map[] = +{ + "alt", "lalt", "ralt", + "shift", "lshift", "rshift", + "cmd", "lcmd", "rcmd", + "ctrl", "lctrl", "rctrl", +}; +internal enum hotkey_flag modifier_flags_value[] = +{ + Hotkey_Flag_Alt, Hotkey_Flag_LAlt, Hotkey_Flag_RAlt, + Hotkey_Flag_Shift, Hotkey_Flag_LShift, Hotkey_Flag_RShift, + Hotkey_Flag_Cmd, Hotkey_Flag_LCmd, Hotkey_Flag_RCmd, + Hotkey_Flag_Control, Hotkey_Flag_LControl, Hotkey_Flag_RControl, +}; + +internal uint32_t +parse_modifier(struct parser *parser) +{ + uint32_t flags = 0; + struct token modifier = parser_previous(parser); + + for(int i = 0; i < array_count(modifier_flags_map); ++i) { + if(same_string(modifier.text, modifier.length, modifier_flags_map[i])) { + flags |= modifier_flags_value[i]; + printf("\tmod: '%s'\n", modifier_flags_map[i]); + break; + } + } + + if(parser_match(parser, Token_Plus)) { + if(parser_match(parser, Token_Modifier)) { + flags |= parse_modifier(parser); + } else { + fprintf(stderr, "(#%d:%d) expected token 'Token_Modifier', but got '%.*s'\n", + parser->current_token.line, parser->current_token.cursor, + parser->current_token.length, parser->current_token.text); + parser->error = true; + } + } + + return flags; +} + +internal struct hotkey * +parse_hotkey(struct parser *parser) +{ + struct hotkey *hotkey = malloc(sizeof(struct hotkey)); + int found_modifier; + + printf("(#%d) hotkey :: {\n", parser->current_token.line); + + if(parser_match(parser, Token_Modifier)) { + hotkey->flags = parse_modifier(parser); + if(parser->error) { + return NULL; + } + found_modifier = 1; + } else { + hotkey->flags = found_modifier = 0; + } + + if(found_modifier) { + if(!parser_match(parser, Token_Dash)) { + fprintf(stderr, "(#%d:%d) expected token '-', but got '%.*s'\n", + parser->current_token.line, parser->current_token.cursor, + parser->current_token.length, parser->current_token.text); + parser->error = true; + return NULL; + } + } + + if(parser_match(parser, Token_Key)) { + hotkey->key = parse_key(parser); + } else if(parser_match(parser, Token_Key_Hex)) { + hotkey->key = parse_key_hex(parser); + } else { + fprintf(stderr, "(#%d:%d) expected token 'Token_Key', but got '%.*s'\n", + parser->current_token.line, parser->current_token.cursor, + parser->current_token.length, parser->current_token.text); + parser->error = true; + return NULL; + } + + if(parser_match(parser, Token_Arrow)) { + hotkey->flags |= Hotkey_Flag_Passthrough; + } + + if(parser_match(parser, Token_Command)) { + hotkey->command = parse_command(parser); + } else { + fprintf(stderr, "(#%d:%d) expected token 'Token_Command', but got '%.*s'\n", + parser->current_token.line, parser->current_token.cursor, + parser->current_token.length, parser->current_token.text); + parser->error = true; + return NULL; + } + + printf("}\n"); + + hotkey->next = NULL; + return hotkey; +} + +struct hotkey * +parse_config(struct parser *parser) +{ + struct hotkey hotkeys; + struct hotkey *current_hotkey = &hotkeys; + + while(!parser_eof(parser)) { + if((parser_check(parser, Token_Modifier)) || + (parser_check(parser, Token_Key_Hex)) || + (parser_check(parser, Token_Key))) { + current_hotkey->next = parse_hotkey(parser); + current_hotkey = current_hotkey->next; + } else { + fprintf(stderr, "(#%d:%d) expected token 'Token_Modifier', 'Token_Key_Hex' or 'Token_Key', but got '%.*s'\n", + parser->current_token.line, parser->current_token.cursor, + parser->current_token.length, parser->current_token.text); + parser->error = true; + return NULL; + } + } + + return hotkeys.next; +} + +struct token +parser_peek(struct parser *parser) +{ + return parser->current_token; +} + +struct token +parser_previous(struct parser *parser) +{ + return parser->previous_token; +} + +bool parser_eof(struct parser *parser) +{ + struct token token = parser_peek(parser); + return token.type == Token_EndOfStream; +} + +struct token +parser_advance(struct parser *parser) +{ + if(!parser_eof(parser)) { + parser->previous_token = parser->current_token; + parser->current_token = get_token(&parser->tokenizer); + } + return parser_previous(parser); +} + +bool parser_check(struct parser *parser, enum token_type type) +{ + if(parser_eof(parser)) return 0; + struct token token = parser_peek(parser); + return token.type == type; +} + +bool parser_match(struct parser *parser, enum token_type type) +{ + if(parser_check(parser, type)) { + parser_advance(parser); + return true; + } + return false; +} + +bool parser_init(struct parser *parser, char *file) +{ + memset(parser, 0, sizeof(struct parser)); + char *buffer = read_file(file); + if(buffer) { + tokenizer_init(&parser->tokenizer, buffer); + parser_advance(parser); + return true; + } + return false; +} + +void parser_destroy(struct parser *parser) +{ + free(parser->tokenizer.buffer); +} diff --git a/src/parse.h b/src/parse.h new file mode 100644 index 0000000..f0ea0f7 --- /dev/null +++ b/src/parse.h @@ -0,0 +1,26 @@ +#ifndef SKHD_PARSE_H +#define SKHD_PARSE_H + +#include "tokenize.h" +#include + +struct parser +{ + struct token previous_token; + struct token current_token; + struct tokenizer tokenizer; + bool error; +}; + +struct hotkey *parse_config(struct parser *parser); + +struct token parser_peek(struct parser *parser); +struct token parser_previous(struct parser *parser); +bool parser_eof(struct parser *parser); +struct token parser_advance(struct parser *parser); +bool parser_check(struct parser *parser, enum token_type type); +bool parser_match(struct parser *parser, enum token_type type); +bool parser_init(struct parser *parser, char *file); +void parser_destroy(struct parser *parser); + +#endif diff --git a/src/skhd.c b/src/skhd.c new file mode 100644 index 0000000..b40e32c --- /dev/null +++ b/src/skhd.c @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hotload.h" +#include "event_tap.h" +#include "locale.h" +#include "tokenize.h" +#include "parse.h" +#include "hotkey.h" + +#include "hotload.c" +#include "event_tap.c" +#include "locale.c" +#include "tokenize.c" +#include "parse.c" +#include "hotkey.c" + +#define internal static +extern bool CGSIsSecureEventInputSet(); +#define secure_keyboard_entry_enabled CGSIsSecureEventInputSet + +internal unsigned major_version = 0; +internal unsigned minor_version = 0; +internal unsigned patch_version = 1; +internal char *config_file; +struct hotkey *hotkeys; + +internal void +error(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + exit(EXIT_FAILURE); +} + +internal bool +watched_io_file(struct hotloader *hotloader, char *absolutepath) +{ + bool success = false; + for(unsigned index = 0; success == 0 && index < hotloader->watch_count; ++index) { + struct watched_file *watch_info = &hotloader->watch_list[index]; + + char *directory = file_directory(absolutepath); + char *filename = file_name(absolutepath); + + if(strcmp(watch_info->directory, directory) == 0) { + if(strcmp(watch_info->filename, filename) == 0) { + success = true; + } + } + + free(filename); + free(directory); + } + + return success; +} + +internal HOTLOADER_CALLBACK(hotloader_handler) +{ + struct hotloader *hotloader = (struct hotloader *) context; + + char **files = (char **) paths; + for(unsigned index = 0; index < count; ++index) { + char *absolutepath = files[index]; + if(watched_io_file(hotloader, absolutepath)) { + /* TODO(koekeishiya): We sometimes get two events upon file save. + * Filter the duplicated event or something ?? */ + struct parser parser; + if(parser_init(&parser, absolutepath)) { + free_hotkeys(hotkeys); + hotkeys = parse_config(&parser); + parser_destroy(&parser); + } + } + } +} + +internal EVENT_TAP_CALLBACK(key_handler) +{ + switch(type) + { + case kCGEventTapDisabledByTimeout: + case kCGEventTapDisabledByUserInput: + { + printf("skhd: restarting event-tap\n"); + struct event_tap *event_tap = (struct event_tap *) reference; + CGEventTapEnable(event_tap->handle, 1); + } break; + case kCGEventKeyDown: + { + uint32_t flags = CGEventGetFlags(event); + uint32_t key = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); + struct hotkey eventkey = cgevent_to_hotkey(flags, key); + if(find_and_exec_hotkey(&eventkey, hotkeys)) { + return NULL; + } + } break; + default: {} break; + } + + return event; +} + +internal bool +parse_arguments(int argc, char **argv) +{ + int option; + const char *short_option = "vc:"; + struct option long_option[] = + { + { "version", no_argument, NULL, 'v' }, + { "config", required_argument, NULL, 'c' }, + { NULL, 0, NULL, 0 } + }; + + while((option = getopt_long(argc, argv, short_option, long_option, NULL)) != -1) { + switch(option) + { + case 'v': + { + printf("skhd version %d.%d.%d\n", major_version, minor_version, patch_version); + return true; + } break; + case 'c': + { + config_file = strdup(optarg); + } break; + } + } + + return false; +} + +internal bool +check_privileges() +{ + bool result = false; + const void *keys[] = { kAXTrustedCheckOptionPrompt }; + const void *values[] = { kCFBooleanTrue }; + + CFDictionaryRef options; + options = CFDictionaryCreate(kCFAllocatorDefault, + keys, values, sizeof(keys) / sizeof(*keys), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + result = AXIsProcessTrustedWithOptions(options); + CFRelease(options); + + return result; +} + +internal void +set_config_path() +{ + if(!config_file) { + char *home = getenv("HOME"); + if(home) { + int length = strlen(home) + strlen("/.skhdrc"); + config_file = (char *) malloc(length + 1); + strcpy(config_file, home); + strcat(config_file, "/.skhdrc"); + } else { + config_file = strdup(".skhdrc"); + } + } +} + +int main(int argc, char **argv) +{ + if(parse_arguments(argc, argv)) { + return EXIT_SUCCESS; + } + + if(secure_keyboard_entry_enabled()) { + error("skhd: secure keyboard entry is enabled! abort..\n"); + } + + if(!check_privileges()) { + error("skhd: must be run with accessibility access.\n"); + } + + signal(SIGCHLD, SIG_IGN); + set_config_path(); + printf("skhd: using config '%s'\n", config_file); + + struct parser parser; + if(parser_init(&parser, config_file)) { + hotkeys = parse_config(&parser); + parser_destroy(&parser); + } else { + error("skhd: could not open file '%s'\n", config_file); + } + + struct event_tap event_tap; + event_tap.mask = (1 << kCGEventKeyDown); + event_tap_begin(&event_tap, key_handler); + + struct hotloader hotloader = {}; + hotloader_add_file(&hotloader, config_file); + hotloader_begin(&hotloader, hotloader_handler); + + CFRunLoopRun(); + + return EXIT_SUCCESS; +} diff --git a/src/tokenize.c b/src/tokenize.c new file mode 100644 index 0000000..53f294a --- /dev/null +++ b/src/tokenize.c @@ -0,0 +1,171 @@ +#include "tokenize.h" +#define internal static + +internal int +token_equals(struct token token, const char *match) +{ + const char *at = match; + unsigned index = 0; + while(*at++ == token.text[index++] && index < token.length); + return (*at == '\0' && index == token.length) ? 1 : 0; +} + +internal void +advance(struct tokenizer *tokenizer) +{ + if(*tokenizer->at == '\n') { + tokenizer->cursor = 0; + ++tokenizer->line; + } + ++tokenizer->cursor; + ++tokenizer->at; +} + +internal void +eat_whitespace(struct tokenizer *tokenizer) +{ + while(*tokenizer->at && isspace(*tokenizer->at)) { + advance(tokenizer); + } +} + +internal void +eat_comment(struct tokenizer *tokenizer) +{ + while(*tokenizer->at && *tokenizer->at != '\n') { + advance(tokenizer); + } +} + +internal void +eat_command(struct tokenizer *tokenizer) +{ + while(*tokenizer->at && *tokenizer->at != '\n') { + if(*tokenizer->at == '\\') { + advance(tokenizer); + } + advance(tokenizer); + } +} + +internal void +eat_hex(struct tokenizer *tokenizer) +{ + while((*tokenizer->at) && + ((isdigit(*tokenizer->at)) || + (*tokenizer->at >= 'A' && *tokenizer->at <= 'F'))) { + advance(tokenizer); + } +} + +internal void +eat_identifier(struct tokenizer *tokenizer) +{ + while(*tokenizer->at && isalpha(*tokenizer->at)) { + advance(tokenizer); + } +} + +internal enum token_type +resolve_identifier_type(struct token token) +{ + if(token.length == 1) { + return Token_Key; + } + + for(int i = 0; i < array_count(token_modifier_map); ++i) { + if(token_equals(token, token_modifier_map[i])) { + return Token_Modifier; + } + } + + for(int i = 0; i < array_count(token_key_map); ++i) { + if(token_equals(token, token_key_map[i])) { + return Token_Key; + } + } + + return Token_Unknown; +} + +struct token +peek_token(struct tokenizer tokenizer) +{ + return get_token(&tokenizer); +} + +struct token +get_token(struct tokenizer *tokenizer) +{ + struct token token; + char c; + + eat_whitespace(tokenizer); + + token.length = 1; + token.text = tokenizer->at; + token.line = tokenizer->line; + token.cursor = tokenizer->cursor; + c = *token.text; + advance(tokenizer); + + switch(c) + { + case '\0': { token.type = Token_EndOfStream; } break; + case '+': { token.type = Token_Plus; } break; + case '-': + { + if(*tokenizer->at && *tokenizer->at == '>') { + advance(tokenizer); + token.length = tokenizer->at - token.text; + token.type = Token_Arrow; + } else { + token.type = Token_Dash; + } + } break; + case ':': + { + eat_whitespace(tokenizer); + + token.text = tokenizer->at; + token.line = tokenizer->line; + token.cursor = tokenizer->cursor; + + eat_command(tokenizer); + token.length = tokenizer->at - token.text; + token.type = Token_Command; + } break; + case '#': + { + eat_comment(tokenizer); + token = get_token(tokenizer); + } break; + default: + { + if(c == '0' && *tokenizer->at == 'x') { + advance(tokenizer); + eat_hex(tokenizer); + token.length = tokenizer->at - token.text; + token.type = Token_Key_Hex; + } else if(isdigit(c)) { + token.type = Token_Key; + } else if(isalpha(c)) { + eat_identifier(tokenizer); + token.length = tokenizer->at - token.text; + token.type = resolve_identifier_type(token); + } else { + token.type = Token_Unknown; + } + } break; + } + + return token; +} + +void tokenizer_init(struct tokenizer *tokenizer, char *buffer) +{ + tokenizer->buffer = buffer; + tokenizer->at = buffer; + tokenizer->line = 1; + tokenizer->cursor = 1; +} diff --git a/src/tokenize.h b/src/tokenize.h new file mode 100644 index 0000000..8b8ecf9 --- /dev/null +++ b/src/tokenize.h @@ -0,0 +1,66 @@ +#ifndef SKHD_TOKENIZE_H +#define SKHD_TOKENIZE_H + +#define array_count(a) (sizeof((a)) / sizeof(*(a))) +static const char *token_modifier_map[] = +{ + "lctrl", "ctrl", "rctrl", + "lalt", "alt", "ralt", + "lshift", "shift", "rshift", + "lcmd", "cmd", "rcmd", +}; + +static const char *token_key_map[] = +{ + "return", "tab", "space", + "backspace", "delete", "escape", + "capslock", "home", "end", + "pageup", "pagedown", "help", + "left", "right", "up", + "down", "f1", "f2", + "f3", "f4", "f5", + "f6", "f7", "f8", + "f9", "f10", "f11", + "f12", "f13", "f14", + "f15", "f16", "f17", + "f18", "f19", "f20", +}; + +enum token_type +{ + Token_Command, + Token_Modifier, + Token_Key_Hex, + Token_Key, + + Token_Plus, + Token_Dash, + Token_Arrow, + + Token_Unknown, + Token_EndOfStream, +}; + +struct token +{ + enum token_type type; + char *text; + unsigned length; + + unsigned line; + unsigned cursor; +}; + +struct tokenizer +{ + char *buffer; + char *at; + unsigned line; + unsigned cursor; +}; + +void tokenizer_init(struct tokenizer *tokenizer, char *buffer); +struct token get_token(struct tokenizer *tokenizer); +struct token peek_token(struct tokenizer tokenizer); + +#endif -- cgit v1.2.3