aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorkoekeishiya <aasvi93@hotmail.com>2017-08-07 20:23:44 +0200
committerkoekeishiya <aasvi93@hotmail.com>2017-08-07 20:23:44 +0200
commitd69056799a399058005b4950751397a31110de4a (patch)
tree1dee43a2f247094c58d1263cee8c8477b893e376 /src
downloadskhd-d69056799a399058005b4950751397a31110de4a.tar.gz
skhd-d69056799a399058005b4950751397a31110de4a.zip
v0.0.1
Diffstat (limited to 'src')
-rw-r--r--src/event_tap.c39
-rw-r--r--src/event_tap.h25
-rw-r--r--src/hotkey.c186
-rw-r--r--src/hotkey.h81
-rw-r--r--src/hotload.c96
-rw-r--r--src/hotload.h39
-rw-r--r--src/locale.c117
-rw-r--r--src/locale.h11
-rw-r--r--src/parse.c270
-rw-r--r--src/parse.h26
-rw-r--r--src/skhd.c216
-rw-r--r--src/tokenize.c171
-rw-r--r--src/tokenize.h66
13 files changed, 1343 insertions, 0 deletions
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 <stdbool.h>
+#include <Carbon/Carbon.h>
+
+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 <string.h>
+#include <pthread.h>
+
+#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 <Carbon/Carbon.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#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 <stdlib.h>
+#include <string.h>
+
+#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 <stdbool.h>
+#include <Carbon/Carbon.h>
+
+#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 <Carbon/Carbon.h>
+#include <IOKit/hidsystem/ev_keymap.h>
+
+#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 <stdbool.h>
+#include <stdint.h>
+
+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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <stdbool.h>
+
+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 <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <getopt.h>
+#include <signal.h>
+#include <string.h>
+
+#include <Carbon/Carbon.h>
+
+#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