[
  {
    "path": ".gitignore",
    "content": "bin/\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Åsmund Vikane\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "> **NOTE**: If you are having issues with skhd, or are looking for feature expansion and future development,  \nyou may want to check out [this backwards compatible skhd-port](https://github.com/jackielii/skhd.zig) written in Zig by [Jackie Li](https://github.com/jackielii).  \n(I am not personally involved in this re-write.)\n\n**This repository is in maintenance mode; only critical issues that affect the core functionality of the software will be taken care of.** \n\n**skhd** is a simple hotkey daemon for macOS that focuses on responsiveness and performance.\nHotkeys are defined in a text file through a simple DSL. **skhd** is able to hotload its config file, meaning that hotkeys can be edited and updated live while **skhd** is running.\n\n**skhd** uses a pid-file to make sure that only one instance is running at any moment in time. This also allows for the ability to trigger\na manual reload of the config file by invoking `skhd --reload` at any time while an instance of **skhd** is running. The pid-file is saved\nas `/tmp/skhd_$USER.pid` and so the user that is running **skhd** must have write permission to said path.\nWhen running as a service (through launchd) log files can be found at `/tmp/skhd_$USER.out.log` and `/tmp/skhd_$USER.err.log`.\n\nlist of features\n\n| feature                    | skhd |\n|:--------------------------:|:----:|\n| hotload config file        | [x]  |\n| hotkey passthrough         | [x]  |\n| modal hotkey-system        | [x]  |\n| application specific hotkey| [x]  |\n| blacklist applications     | [x]  |\n| use media-keys as hotkey   | [x]  |\n| synthesize a key-press     | [x]  |\n\n### Install\n\nThe first time **skhd** is ran, it will request access to the accessibility API.\n\nAfter access has been granted, the application must be restarted.\n\n*Secure Keyboard Entry* must be disabled for **skhd** to receive key-events.\n\n**Homebrew**:\n\nRequires xcode-8 command-line tools.\n\n      brew install asmvik/formulae/skhd\n      skhd --start-service\n\n**Source**:\n\nRequires xcode-8 command-line tools.\n\n      git clone https://github.com/asmvik/skhd\n      make install      # release version\n      make              # debug version\n\n### Usage\n\n```\n--install-service: Install launchd service file into ~/Library/LaunchAgents/com.asmvik.skhd.plist\n    skhd --install-service\n\n--uninstall-service: Remove launchd service file ~/Library/LaunchAgents/com.asmvik.skhd.plist\n    skhd --uninstall-service\n\n--start-service: Run skhd as a service through launchd\n    skhd --start-service\n\n--restart-service: Restart skhd service\n    skhd --restart-service\n\n--stop-service: Stop skhd service from running\n    skhd --stop-service\n\n-V | --verbose: Output debug information\n    skhd -V\n\n-P | --profile: Output profiling information\n    skhd -P\n\n-v | --version: Print version number to stdout\n    skhd -v\n\n-c | --config: Specify location of config file\n    skhd -c ~/.skhdrc\n\n-o | --observe: Output keycode and modifiers of event. Ctrl+C to quit\n    skhd -o\n\n-r | --reload: Signal a running instance of skhd to reload its config file\n    skhd -r\n\n-h | --no-hotload: Disable system for hotloading config file\n    skhd -h\n\n-k | --key: Synthesize a keypress (same syntax as when defining a hotkey)\n    skhd -k \"shift + alt - 7\"\n\n-t | --text: Synthesize a line of text\n    skhd -t \"hello, worldシ\"\n```\n\n### Configuration\n\nThe default configuration file is located at one of the following places (in order):\n\n - `$XDG_CONFIG_HOME/skhd/skhdrc`\n - `$HOME/.config/skhd/skhdrc`\n - `$HOME/.skhdrc`\n\nA different location can be specified with the *--config | -c* argument.\n\nA sample config is available [here](https://github.com/asmvik/skhd/blob/master/examples/skhdrc)\n\nA list of all built-in modifier and literal keywords can be found [here](https://github.com/asmvik/skhd/issues/1)\n\nA hotkey is written according to the following rules:\n```\nhotkey       = <mode> '<' <action> | <action>\n\nmode         = 'name of mode' | <mode> ',' <mode>\n\naction       = <keysym> '[' <proc_map_lst> ']' | <keysym> '->' '[' <proc_map_lst> ']'\n               <keysym> ':' <command>          | <keysym> '->' ':' <command>\n               <keysym> ';' <mode>             | <keysym> '->' ';' <mode>\n\nkeysym       = <mod> '-' <key> | <key>\n\nmod          = 'modifier keyword' | <mod> '+' <mod>\n\nkey          = <literal> | <keycode>\n\nliteral      = 'single letter or built-in keyword'\n\nkeycode      = 'apple keyboard kVK_<Key> values (0x3C)'\n\nproc_map_lst = * <proc_map>\n\nproc_map     = <string> ':' <command> | <string>     '~' |\n               '*'      ':' <command> | '*'          '~'\n\nstring       = '\"' 'sequence of characters' '\"'\n\ncommand      = command is executed through '$SHELL -c' and\n               follows valid shell syntax. if the $SHELL environment\n               variable is not set, it will default to '/bin/bash'.\n               when bash is used, the ';' delimeter can be specified\n               to chain commands.\n\n               to allow a command to extend into multiple lines,\n               prepend '\\' at the end of the previous line.\n\n               an EOL character signifies the end of the bind.\n\n->           = keypress is not consumed by skhd\n\n*            = matches every application not specified in <proc_map_lst>\n\n~            = application is unbound and keypress is forwarded per usual, when specified in a <proc_map>\n```\n\nA mode is declared according to the following rules:\n```\n\nmode_decl = '::' <name> '@' ':' <command> | '::' <name> ':' <command> |\n            '::' <name> '@'               | '::' <name>\n\nname      = desired name for this mode,\n\n@         = capture keypresses regardless of being bound to an action\n\ncommand  = command is executed through '$SHELL -c' and\n           follows valid shell syntax. if the $SHELL environment\n           variable is not set, it will default to '/bin/bash'.\n           when bash is used, the ';' delimeter can be specified\n           to chain commands.\n\n           to allow a command to extend into multiple lines,\n           prepend '\\' at the end of the previous line.\n\n           an EOL character signifies the end of the bind.\n```\n\nGeneral options that configure the behaviour of **skhd**:\n```\n# specify a file that should be included as an additional config-file.\n# treated as an absolutepath if the filename begins with '/' otherwise\n# the file is relative to the path of the config-file it was loaded from.\n\n.load \"/Users/Koe/.config/partial_skhdrc\"\n.load \"partial_skhdrc\"\n\n# prevents skhd from monitoring events for listed processes.\n\n.blacklist [\n    \"terminal\"\n    \"qutebrowser\"\n    \"kitty\"\n    \"google chrome\"\n]\n```\n"
  },
  {
    "path": "examples/skhdrc",
    "content": "#  NOTE(asmvik): A list of all built-in modifier and literal keywords can\n#                     be found at https://github.com/asmvik/skhd/issues/1\n#\n#                     A hotkey is written according to the following rules:\n#\n#                       hotkey       = <mode> '<' <action> | <action>\n#\n#                       mode         = 'name of mode' | <mode> ',' <mode>\n#\n#                       action       = <keysym> '[' <proc_map_lst> ']' | <keysym> '->' '[' <proc_map_lst> ']'\n#                                      <keysym> ':' <command>          | <keysym> '->' ':' <command>\n#                                      <keysym> ';' <mode>             | <keysym> '->' ';' <mode>\n#\n#                       keysym       = <mod> '-' <key> | <key>\n#\n#                       mod          = 'modifier keyword' | <mod> '+' <mod>\n#\n#                       key          = <literal> | <keycode>\n#\n#                       literal      = 'single letter or built-in keyword'\n#\n#                       keycode      = 'apple keyboard kVK_<Key> values (0x3C)'\n#\n#                       proc_map_lst = * <proc_map>\n#\n#                       proc_map     = <string> ':' <command> | <string>     '~' |\n#                                      '*'      ':' <command> | '*'          '~'\n#\n#                       string       = '\"' 'sequence of characters' '\"'\n#\n#                       command      = command is executed through '$SHELL -c' and\n#                                      follows valid shell syntax. if the $SHELL environment\n#                                      variable is not set, it will default to '/bin/bash'.\n#                                      when bash is used, the ';' delimeter can be specified\n#                                      to chain commands.\n#\n#                                      to allow a command to extend into multiple lines,\n#                                      prepend '\\' at the end of the previous line.\n#\n#                                      an EOL character signifies the end of the bind.\n#\n#                       ->           = keypress is not consumed by skhd\n#\n#                       *            = matches every application not specified in <proc_map_lst>\n#\n#                       ~            = application is unbound and keypress is forwarded per usual, when specified in a <proc_map>\n#\n#  NOTE(asmvik): A mode is declared according to the following rules:\n#\n#                       mode_decl = '::' <name> '@' ':' <command> | '::' <name> ':' <command> |\n#                                   '::' <name> '@'               | '::' <name>\n#\n#                       name      = desired name for this mode,\n#\n#                       @         = capture keypresses regardless of being bound to an action\n#\n#                       command   = command is executed through '$SHELL -c' and\n#                                   follows valid shell syntax. if the $SHELL environment\n#                                   variable is not set, it will default to '/bin/bash'.\n#                                   when bash is used, the ';' delimeter can be specified\n#                                   to chain commands.\n#\n#                                   to allow a command to extend into multiple lines,\n#                                   prepend '\\' at the end of the previous line.\n#\n#                                   an EOL character signifies the end of the bind.\n\n# add an on_enter command to the default mode\n# :: default : yabai -m config active_window_border_color 0xff775759\n#\n# defines a new mode 'test' with an on_enter command, that captures keypresses\n# :: test @ : yabai -m config active_window_border_color 0xff24ccaa\n#\n# from 'default' mode, activate mode 'test'\n# cmd - x ; test\n#\n# from 'test' mode, activate mode 'default'\n# test < cmd - x ; default\n#\n# launch a new terminal instance when in either 'default' or 'test' mode\n# default, test < cmd - return : open -na /Applications/Terminal.app\n\n# application specific bindings\n#\n# cmd - n [\n#     \"kitty\"       : echo \"hello kitty\"\n#     *             : echo \"hello everyone\"\n#     \"qutebrowser\" : echo \"hello qutebrowser\"\n#     \"terminal\"    ~\n#     \"finder\"      : false\n# ]\n\n# specify a file that should be included as an additional config-file.\n# treated as an absolutepath if the filename begins with '/' otherwise\n# the file is relative to the path of the config-file it was loaded from.\n#\n# .load \"/Users/Koe/.config/partial_skhdrc\"\n# .load \"partial_skhdrc\"\n\n# prevent skhd from monitoring events for specific applications.\n#\n# .blacklist [\n#    \"kitty\"\n#    \"terminal\"\n#    \"qutebrowser\"\n# ]\n\n# open terminal, blazingly fast compared to iTerm/Hyper\ncmd - return : /Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~\n\n# open qutebrowser\ncmd + shift - return : ~/Scripts/qtb.sh\n\n# open mpv\ncmd - m : open -na /Applications/mpv.app $(pbpaste)\n"
  },
  {
    "path": "makefile",
    "content": "FRAMEWORKS     = -framework Cocoa -framework Carbon -framework CoreServices\nBUILD_PATH     = ./bin\nBUILD_FLAGS    = -std=c99 -Wall -g -O0\nSKHD_SRC       = ./src/skhd.c\nBINS           = $(BUILD_PATH)/skhd\n\n.PHONY: all clean install\n\nall: clean $(BINS)\n\ninstall: BUILD_FLAGS=-std=c99 -Wall -O2\ninstall: clean $(BINS)\n\nclean:\n\trm -rf $(BUILD_PATH)\n\n$(BUILD_PATH)/skhd: $(SKHD_SRC)\n\tmkdir -p $(BUILD_PATH)\n\tclang $^ $(BUILD_FLAGS) $(FRAMEWORKS) -o $@\n"
  },
  {
    "path": "src/carbon.c",
    "content": "#include \"carbon.h\"\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\nstatic inline char *\nfind_process_name_for_psn(ProcessSerialNumber *psn)\n{\n    CFStringRef process_name_ref;\n    if (CopyProcessName(psn, &process_name_ref) == noErr) {\n        char *process_name = copy_cfstring(process_name_ref);\n        for (char *s = process_name; *s; ++s) *s = tolower(*s);\n        CFRelease(process_name_ref);\n        return process_name;\n    }\n    return NULL;\n}\n\ninline char *\nfind_process_name_for_pid(pid_t pid)\n{\n    ProcessSerialNumber psn;\n    GetProcessForPID(pid, &psn);\n    return find_process_name_for_psn(&psn);\n}\n\nstatic inline char *\nfind_active_process_name(void)\n{\n    ProcessSerialNumber psn;\n    GetFrontProcess(&psn);\n    return find_process_name_for_psn(&psn);\n}\n#pragma clang diagnostic pop\n\nstatic OSStatus\ncarbon_event_handler(EventHandlerCallRef ref, EventRef event, void *context)\n{\n    struct carbon_event *carbon = (struct carbon_event *) context;\n\n    ProcessSerialNumber psn;\n    if (GetEventParameter(event,\n                          kEventParamProcessID,\n                          typeProcessSerialNumber,\n                          NULL,\n                          sizeof(psn),\n                          NULL,\n                          &psn) != noErr) {\n        return -1;\n    }\n\n    if (carbon->process_name) {\n        free(carbon->process_name);\n        carbon->process_name = NULL;\n    }\n\n    carbon->process_name = find_process_name_for_psn(&psn);\n\n    return noErr;\n}\n\nbool carbon_event_init(struct carbon_event *carbon)\n{\n    carbon->target = GetApplicationEventTarget();\n    carbon->handler = NewEventHandlerUPP(carbon_event_handler);\n    carbon->type.eventClass = kEventClassApplication;\n    carbon->type.eventKind = kEventAppFrontSwitched;\n    carbon->process_name = find_active_process_name();\n\n    return InstallEventHandler(carbon->target,\n                               carbon->handler,\n                               1,\n                               &carbon->type,\n                               carbon,\n                               &carbon->handler_ref) == noErr;\n}\n"
  },
  {
    "path": "src/carbon.h",
    "content": "#ifndef SKHD_CARBON_H\n#define SKHD_CARBON_H\n\n#include <Carbon/Carbon.h>\n\nstruct carbon_event\n{\n    EventTargetRef target;\n    EventHandlerUPP handler;\n    EventTypeSpec type;\n    EventHandlerRef handler_ref;\n    char * volatile process_name;\n};\n\nchar *find_process_name_for_pid(pid_t pid);\nbool carbon_event_init(struct carbon_event *carbon);\n\n#endif\n"
  },
  {
    "path": "src/event_tap.c",
    "content": "#include \"event_tap.h\"\n\nbool event_tap_enabled(struct event_tap *event_tap)\n{\n    bool result = (event_tap->handle && CGEventTapIsEnabled(event_tap->handle));\n    return result;\n}\n\nbool event_tap_begin(struct event_tap *event_tap, event_tap_callback *callback)\n{\n    event_tap->handle = CGEventTapCreate(kCGSessionEventTap,\n                                         kCGHeadInsertEventTap,\n                                         kCGEventTapOptionDefault,\n                                         event_tap->mask,\n                                         callback,\n                                         event_tap);\n\n    bool result = event_tap_enabled(event_tap);\n    if (result) {\n        event_tap->runloop_source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,\n                                                                  event_tap->handle,\n                                                                  0);\n        CFRunLoopAddSource(CFRunLoopGetMain(), event_tap->runloop_source, kCFRunLoopCommonModes);\n    }\n\n    return result;\n}\n\nvoid event_tap_end(struct event_tap *event_tap)\n{\n    if (event_tap_enabled(event_tap)) {\n        CGEventTapEnable(event_tap->handle, false);\n        CFMachPortInvalidate(event_tap->handle);\n        CFRunLoopRemoveSource(CFRunLoopGetMain(), event_tap->runloop_source, kCFRunLoopCommonModes);\n        CFRelease(event_tap->runloop_source);\n        CFRelease(event_tap->handle);\n        event_tap->handle = NULL;\n    }\n}\n"
  },
  {
    "path": "src/event_tap.h",
    "content": "#ifndef SKHD_EVENT_TAP_H\n#define SKHD_EVENT_TAP_H\n\n#include <stdbool.h>\n#include <Carbon/Carbon.h>\n\nstruct event_tap\n{\n    CFMachPortRef handle;\n    CFRunLoopSourceRef runloop_source;\n    CGEventMask mask;\n};\n\n#define EVENT_TAP_CALLBACK(name) \\\n    CGEventRef name(CGEventTapProxy proxy, \\\n                    CGEventType type, \\\n                    CGEventRef event, \\\n                    void *reference)\ntypedef EVENT_TAP_CALLBACK(event_tap_callback);\n\nbool event_tap_enabled(struct event_tap *event_tap);\nbool event_tap_begin(struct event_tap *event_tap, event_tap_callback *callback);\nvoid event_tap_end(struct event_tap *event_tap);\n\n#endif\n"
  },
  {
    "path": "src/hashtable.h",
    "content": "#ifndef HASHTABLE_H\n#define HASHTABLE_H\n\ntypedef unsigned long (*table_hash_func)(void *key);\ntypedef int (*table_compare_func)(void *key_a, void *key_b);\n\nstruct bucket\n{\n    void *key;\n    void *value;\n    struct bucket *next;\n};\nstruct table\n{\n    int count;\n    int capacity;\n    table_hash_func hash;\n    table_compare_func compare;\n    struct bucket **buckets;\n};\n\nvoid table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func compare);\nvoid table_free(struct table *table);\n\nvoid *table_find(struct table *table, void *key);\nvoid table_add(struct table *table, void *key, void *value);\nvoid *table_remove(struct table *table, void *key);\nvoid *table_reset(struct table *table, int *count);\n\n#endif\n\n#ifdef HASHTABLE_IMPLEMENTATION\n#include <stdlib.h>\n#include <string.h>\n\nstatic struct bucket *\ntable_new_bucket(void *key, void *value)\n{\n    struct bucket *bucket = malloc(sizeof(struct bucket));\n    bucket->key = key;\n    bucket->value = value;\n    bucket->next = NULL;\n    return bucket;\n}\n\nstatic struct bucket **\ntable_get_bucket(struct table *table, void *key)\n{\n    struct bucket **bucket = table->buckets + (table->hash(key) % table->capacity);\n    while (*bucket) {\n        if (table->compare((*bucket)->key, key)) {\n            break;\n        }\n        bucket = &(*bucket)->next;\n    }\n    return bucket;\n}\n\nvoid table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func compare)\n{\n    table->count = 0;\n    table->capacity = capacity;\n    table->hash = hash;\n    table->compare = compare;\n    table->buckets = malloc(sizeof(struct bucket *) * capacity);\n    memset(table->buckets, 0, sizeof(struct bucket *) * capacity);\n}\n\nvoid table_free(struct table *table)\n{\n    for (int i = 0; i < table->capacity; ++i) {\n        struct bucket *next, *bucket = table->buckets[i];\n        while (bucket) {\n            next = bucket->next;\n            free(bucket);\n            bucket = next;\n        }\n    }\n    if (table->buckets) {\n        free(table->buckets);\n        table->buckets = NULL;\n    }\n}\n\nvoid *table_find(struct table *table, void *key)\n{\n    struct bucket *bucket = *table_get_bucket(table, key);\n    return bucket ? bucket->value : NULL;\n}\n\nvoid table_add(struct table *table, void *key, void *value)\n{\n    struct bucket **bucket = table_get_bucket(table, key);\n    if (*bucket) {\n        if (!(*bucket)->value) {\n            (*bucket)->value = value;\n        }\n    } else {\n        *bucket = table_new_bucket(key, value);\n        ++table->count;\n    }\n}\n\nvoid *table_remove(struct table *table, void *key)\n{\n    void *result = NULL;\n    struct bucket *next, **bucket = table_get_bucket(table, key);\n    if (*bucket) {\n        result = (*bucket)->value;\n        next = (*bucket)->next;\n        free(*bucket);\n        *bucket = next;\n        --table->count;\n    }\n    return result;\n}\n\nvoid *table_reset(struct table *table, int *count)\n{\n    void **values;\n    int capacity;\n    int index;\n    int item;\n\n    capacity = table->capacity;\n    *count = table->count;\n    values = malloc(sizeof(void *) * table->count);\n    item = 0;\n\n    for (index = 0; index < capacity; ++index) {\n        struct bucket *next, **bucket = table->buckets + index;\n        while (*bucket) {\n            values[item++] = (*bucket)->value;\n            next = (*bucket)->next;\n            free(*bucket);\n            *bucket = next;\n            --table->count;\n        }\n    }\n\n    return values;\n}\n\n#undef HASHTABLE_IMPLEMENTATION\n#endif\n"
  },
  {
    "path": "src/hotkey.c",
    "content": "#include \"hotkey.h\"\n\n#define HOTKEY_FOUND           ((1) << 0)\n#define MODE_CAPTURE(a)        ((a) << 1)\n#define HOTKEY_PASSTHROUGH(a)  ((a) << 2)\n\n#define LRMOD_ALT   0\n#define LRMOD_CMD   6\n#define LRMOD_CTRL  9\n#define LRMOD_SHIFT 3\n#define LMOD_OFFS   1\n#define RMOD_OFFS   2\n\nstatic char arg[] = \"-c\";\nstatic char *shell = NULL;\n\nstatic uint32_t cgevent_lrmod_flag[] =\n{\n    Event_Mask_Alt,     Event_Mask_LAlt,     Event_Mask_RAlt,\n    Event_Mask_Shift,   Event_Mask_LShift,   Event_Mask_RShift,\n    Event_Mask_Cmd,     Event_Mask_LCmd,     Event_Mask_RCmd,\n    Event_Mask_Control, Event_Mask_LControl, Event_Mask_RControl,\n};\n\nstatic uint32_t hotkey_lrmod_flag[] =\n{\n    Hotkey_Flag_Alt,     Hotkey_Flag_LAlt,     Hotkey_Flag_RAlt,\n    Hotkey_Flag_Shift,   Hotkey_Flag_LShift,   Hotkey_Flag_RShift,\n    Hotkey_Flag_Cmd,     Hotkey_Flag_LCmd,     Hotkey_Flag_RCmd,\n    Hotkey_Flag_Control, Hotkey_Flag_LControl, Hotkey_Flag_RControl,\n};\n\nstatic bool\ncompare_lr_mod(struct hotkey *a, struct hotkey *b, int mod)\n{\n    bool result = has_flags(a, hotkey_lrmod_flag[mod])\n                ? has_flags(b, hotkey_lrmod_flag[mod + LMOD_OFFS]) ||\n                  has_flags(b, hotkey_lrmod_flag[mod + RMOD_OFFS]) ||\n                  has_flags(b, hotkey_lrmod_flag[mod])\n                : has_flags(a, hotkey_lrmod_flag[mod + LMOD_OFFS]) == has_flags(b, hotkey_lrmod_flag[mod + LMOD_OFFS]) &&\n                  has_flags(a, hotkey_lrmod_flag[mod + RMOD_OFFS]) == has_flags(b, hotkey_lrmod_flag[mod + RMOD_OFFS]) &&\n                  has_flags(a, hotkey_lrmod_flag[mod])             == has_flags(b, hotkey_lrmod_flag[mod]);\n    return result;\n}\n\nstatic bool\ncompare_fn(struct hotkey *a, struct hotkey *b)\n{\n    return has_flags(a, Hotkey_Flag_Fn) == has_flags(b, Hotkey_Flag_Fn);\n}\n\nstatic bool\ncompare_nx(struct hotkey *a, struct hotkey *b)\n{\n    return has_flags(a, Hotkey_Flag_NX) == has_flags(b, Hotkey_Flag_NX);\n}\n\nbool same_hotkey(struct hotkey *a, struct hotkey *b)\n{\n    return compare_lr_mod(a, b, LRMOD_ALT)   &&\n           compare_lr_mod(a, b, LRMOD_CMD)   &&\n           compare_lr_mod(a, b, LRMOD_CTRL)  &&\n           compare_lr_mod(a, b, LRMOD_SHIFT) &&\n           compare_fn(a, b) &&\n           compare_nx(a, b) &&\n           a->key == b->key;\n}\n\nunsigned long hash_hotkey(struct hotkey *a)\n{\n    return a->key;\n}\n\nbool compare_string(char *a, char *b)\n{\n    while (*a && *b && *a == *b) {\n        ++a;\n        ++b;\n    }\n    return *a == '\\0' && *b == '\\0';\n}\n\nunsigned long hash_string(char *key)\n{\n    unsigned long hash = 0, high;\n    while(*key) {\n        hash = (hash << 4) + *key++;\n        high = hash & 0xF0000000;\n        if(high) {\n            hash ^= (high >> 24);\n        }\n        hash &= ~high;\n    }\n    return hash;\n}\n\nstatic inline void\nfork_and_exec(char *command)\n{\n    int cpid = fork();\n    if (cpid == 0) {\n        setsid();\n        char *exec[] = { shell, arg, command, NULL};\n        int status_code = execvp(exec[0], exec);\n        exit(status_code);\n    }\n}\n\nstatic inline void\npassthrough(struct hotkey *hotkey, uint32_t *capture)\n{\n    *capture |= HOTKEY_PASSTHROUGH((int)has_flags(hotkey, Hotkey_Flag_Passthrough));\n}\n\nstatic inline struct hotkey *\nfind_hotkey(struct mode *mode, struct hotkey *hotkey, uint32_t *capture)\n{\n    struct hotkey *result = table_find(&mode->hotkey_map, hotkey);\n    if (result) *capture |= HOTKEY_FOUND;\n    return result;\n}\n\nstatic inline bool\nshould_capture_hotkey(uint32_t capture)\n{\n    if ((capture & HOTKEY_FOUND)) {\n        if (!(capture & MODE_CAPTURE(1)) &&\n            !(capture & HOTKEY_PASSTHROUGH(1))) {\n            return true;\n        }\n\n        if (!(capture & HOTKEY_PASSTHROUGH(1)) &&\n             (capture & MODE_CAPTURE(1))) {\n            return true;\n        }\n\n        return false;\n    }\n\n    return (capture & MODE_CAPTURE(1));\n}\n\nstatic inline char *\nfind_process_command_mapping(struct hotkey *hotkey, uint32_t *capture, struct carbon_event *carbon)\n{\n    char *result = NULL;\n    bool found = false;\n\n    for (int i = 0; i < buf_len(hotkey->process_name); ++i) {\n        if (same_string(carbon->process_name, hotkey->process_name[i])) {\n            result = hotkey->command[i];\n            found = true;\n            break;\n        }\n    }\n\n    if (!found) result = hotkey->wildcard_command;\n    if (!result) *capture &= ~HOTKEY_FOUND;\n\n    return result;\n}\n\nbool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m, struct carbon_event *carbon)\n{\n    uint32_t c = MODE_CAPTURE((int)(*m)->capture);\n    for (struct hotkey *h = find_hotkey(*m, k, &c); h; passthrough(h, &c), h = 0) {\n        char *cmd = h->command[0];\n        if (has_flags(h, Hotkey_Flag_Activate)) {\n            *m = table_find(t, cmd);\n            cmd = (*m)->command;\n        } else if (buf_len(h->process_name) > 0) {\n            cmd = find_process_command_mapping(h, &c, carbon);\n        }\n        if (cmd) fork_and_exec(cmd);\n    }\n    return should_capture_hotkey(c);\n}\n\nvoid free_mode_map(struct table *mode_map)\n{\n    struct hotkey **freed_pointers = NULL;\n\n    int mode_count;\n    void **modes = table_reset(mode_map, &mode_count);\n    for (int mode_index = 0; mode_index < mode_count; ++mode_index) {\n        struct mode *mode = (struct mode *) modes[mode_index];\n\n        int hk_count;\n        void **hotkeys = table_reset(&mode->hotkey_map, &hk_count);\n        for (int hk_index = 0; hk_index < hk_count; ++hk_index) {\n            struct hotkey *hotkey = (struct hotkey *) hotkeys[hk_index];\n\n            for (int i = 0; i < buf_len(freed_pointers); ++i) {\n                if (freed_pointers[i] == hotkey) {\n                    goto next;\n                }\n            }\n\n            buf_push(freed_pointers, hotkey);\n            buf_free(hotkey->mode_list);\n\n            for (int i = 0; i < buf_len(hotkey->process_name); ++i) {\n                free(hotkey->process_name[i]);\n            }\n            buf_free(hotkey->process_name);\n\n            for (int i = 0; i < buf_len(hotkey->command); ++i) {\n                free(hotkey->command[i]);\n            }\n            buf_free(hotkey->command);\n\n            free(hotkey);\nnext:;\n        }\n\n        if (hk_count) free(hotkeys);\n        if (mode->command) free(mode->command);\n        if (mode->name) free(mode->name);\n        free(mode);\n    }\n\n    if (mode_count) {\n        free(modes);\n        buf_free(freed_pointers);\n    }\n}\n\nvoid free_blacklist(struct table *blacklst)\n{\n    int count;\n    void **items = table_reset(blacklst, &count);\n    for (int index = 0; index < count; ++index) {\n        char *item = (char *) items[index];\n        free(item);\n    }\n}\n\nstatic void\ncgevent_lrmod_flag_to_hotkey_lrmod_flag(CGEventFlags eventflags, uint32_t *flags, int mod)\n{\n    enum osx_event_mask mask  = cgevent_lrmod_flag[mod];\n    enum osx_event_mask lmask = cgevent_lrmod_flag[mod + LMOD_OFFS];\n    enum osx_event_mask rmask = cgevent_lrmod_flag[mod + RMOD_OFFS];\n\n    if ((eventflags & mask) == mask) {\n        bool left  = (eventflags & lmask) == lmask;\n        bool right = (eventflags & rmask) == rmask;\n\n        if (left)            *flags |= hotkey_lrmod_flag[mod + LMOD_OFFS];\n        if (right)           *flags |= hotkey_lrmod_flag[mod + RMOD_OFFS];\n        if (!left && !right) *flags |= hotkey_lrmod_flag[mod];\n    }\n}\n\nstatic uint32_t\ncgevent_flags_to_hotkey_flags(uint32_t eventflags)\n{\n    uint32_t flags = 0;\n\n    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_ALT);\n    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_CMD);\n    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_CTRL);\n    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_SHIFT);\n\n    if ((eventflags & Event_Mask_Fn) == Event_Mask_Fn) {\n        flags |= Hotkey_Flag_Fn;\n    }\n\n    return flags;\n}\n\nstruct hotkey create_eventkey(CGEventRef event)\n{\n    struct hotkey eventkey = {\n        .key = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode),\n        .flags = cgevent_flags_to_hotkey_flags(CGEventGetFlags(event))\n    };\n    return eventkey;\n}\n\nbool intercept_systemkey(CGEventRef event, struct hotkey *eventkey)\n{\n    CFDataRef event_data = CGEventCreateData(kCFAllocatorDefault, event);\n    const uint8_t *data  = CFDataGetBytePtr(event_data);\n    uint8_t key_code  = data[129];\n    uint8_t key_state = data[130];\n    uint8_t key_stype = data[123];\n    CFRelease(event_data);\n\n    bool result = ((key_state == NX_KEYDOWN) &&\n                   (key_stype == NX_SUBTYPE_AUX_CONTROL_BUTTONS));\n\n    if (result) {\n        eventkey->key = key_code;\n        eventkey->flags = cgevent_flags_to_hotkey_flags(CGEventGetFlags(event)) | Hotkey_Flag_NX;\n    }\n\n    return result;\n}\n\nvoid init_shell(void)\n{\n    if (!shell) {\n        char *env_shell = getenv(\"SHELL\");\n        shell = env_shell ? env_shell : \"/bin/bash\";\n    }\n}\n"
  },
  {
    "path": "src/hotkey.h",
    "content": "#ifndef SKHD_HOTKEY_H\n#define SKHD_HOTKEY_H\n\n#include <Carbon/Carbon.h>\n#include <stdint.h>\n#include <stdbool.h>\n\n#define Modifier_Keycode_Alt     0x3A\n#define Modifier_Keycode_Shift   0x38\n#define Modifier_Keycode_Cmd     0x37\n#define Modifier_Keycode_Ctrl    0x3B\n#define Modifier_Keycode_Fn      0x3F\n\nenum osx_event_mask\n{\n    Event_Mask_Alt      = 0x00080000,\n    Event_Mask_LAlt     = 0x00000020,\n    Event_Mask_RAlt     = 0x00000040,\n    Event_Mask_Shift    = 0x00020000,\n    Event_Mask_LShift   = 0x00000002,\n    Event_Mask_RShift   = 0x00000004,\n    Event_Mask_Cmd      = 0x00100000,\n    Event_Mask_LCmd     = 0x00000008,\n    Event_Mask_RCmd     = 0x00000010,\n    Event_Mask_Control  = 0x00040000,\n    Event_Mask_LControl = 0x00000001,\n    Event_Mask_RControl = 0x00002000,\n    Event_Mask_Fn       = kCGEventFlagMaskSecondaryFn,\n};\n\nenum hotkey_flag\n{\n    Hotkey_Flag_Alt         = (1 <<  0),\n    Hotkey_Flag_LAlt        = (1 <<  1),\n    Hotkey_Flag_RAlt        = (1 <<  2),\n    Hotkey_Flag_Shift       = (1 <<  3),\n    Hotkey_Flag_LShift      = (1 <<  4),\n    Hotkey_Flag_RShift      = (1 <<  5),\n    Hotkey_Flag_Cmd         = (1 <<  6),\n    Hotkey_Flag_LCmd        = (1 <<  7),\n    Hotkey_Flag_RCmd        = (1 <<  8),\n    Hotkey_Flag_Control     = (1 <<  9),\n    Hotkey_Flag_LControl    = (1 << 10),\n    Hotkey_Flag_RControl    = (1 << 11),\n    Hotkey_Flag_Fn          = (1 << 12),\n    Hotkey_Flag_Passthrough = (1 << 13),\n    Hotkey_Flag_Activate    = (1 << 14),\n    Hotkey_Flag_NX          = (1 << 15),\n    Hotkey_Flag_Hyper       = (Hotkey_Flag_Cmd |\n                               Hotkey_Flag_Alt |\n                               Hotkey_Flag_Shift |\n                               Hotkey_Flag_Control),\n    Hotkey_Flag_Meh         = (Hotkey_Flag_Control |\n                               Hotkey_Flag_Shift |\n                               Hotkey_Flag_Alt)\n};\n\n#include \"hashtable.h\"\n\nstruct carbon_event;\n\nstruct mode\n{\n    char *name;\n    char *command;\n    bool capture;\n    bool initialized;\n    struct table hotkey_map;\n};\n\nstruct hotkey\n{\n    uint32_t flags;\n    uint32_t key;\n    char **process_name;\n    char **command;\n    char *wildcard_command;\n    struct mode **mode_list;\n};\n\nstatic inline void\nadd_flags(struct hotkey *hotkey, uint32_t flag)\n{\n    hotkey->flags |= flag;\n}\n\nstatic inline bool\nhas_flags(struct hotkey *hotkey, uint32_t flag)\n{\n    bool result = hotkey->flags & flag;\n    return result;\n}\n\nstatic inline void\nclear_flags(struct hotkey *hotkey, uint32_t flag)\n{\n    hotkey->flags &= ~flag;\n}\n\nbool compare_string(char *a, char *b);\nunsigned long hash_string(char *key);\n\nbool same_hotkey(struct hotkey *a, struct hotkey *b);\nunsigned long hash_hotkey(struct hotkey *a);\n\nstruct hotkey create_eventkey(CGEventRef event);\nbool intercept_systemkey(CGEventRef event, struct hotkey *eventkey);\n\nbool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m, struct carbon_event *carbon);\nvoid free_mode_map(struct table *mode_map);\nvoid free_blacklist(struct table *blacklst);\n\nvoid init_shell(void);\n\n#endif\n"
  },
  {
    "path": "src/hotload.c",
    "content": "#include \"hotload.h\"\n#include <sys/stat.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define FSEVENT_CALLBACK(name) void name(ConstFSEventStreamRef stream,\\\n                                         void *context,\\\n                                         size_t file_count,\\\n                                         void *file_paths,\\\n                                         const FSEventStreamEventFlags *flags,\\\n                                         const FSEventStreamEventId *ids)\n\nenum watch_kind\n{\n    WATCH_KIND_INVALID,\n    WATCH_KIND_CATALOG,\n    WATCH_KIND_FILE\n};\n\nstruct watched_catalog\n{\n    char *directory;\n    char *extension;\n};\n\nstruct watched_file\n{\n    char *absolutepath;\n    char *directory;\n    char *filename;\n};\n\nstruct watched_entry\n{\n    enum watch_kind kind;\n    union {\n        struct watched_file file_info;\n        struct watched_catalog catalog_info;\n    };\n};\n\nstatic inline bool\nsame_string(const char *a, const char *b)\n{\n    bool result = a && b && strcmp(a, b) == 0;\n    return result;\n}\n\nstatic char *\ncopy_string(const char *s)\n{\n    unsigned length = strlen(s);\n    char *result = (char *) malloc(length + 1);\n    memcpy(result, s, length);\n    result[length] = '\\0';\n    return result;\n}\n\nstatic char *\nfile_directory(char *file)\n{\n    char *last_slash = strrchr(file, '/');\n    *last_slash = '\\0';\n    char *directory = copy_string(file);\n    *last_slash = '/';\n    return directory;\n}\n\nstatic char *\nfile_name(char *file)\n{\n    char *last_slash = strrchr(file, '/');\n    char *name = copy_string(last_slash + 1);\n    return name;\n}\n\nstatic char *\nresolve_symlink(const char *file)\n{\n    struct stat buffer;\n    if (lstat(file, &buffer) != 0) {\n        return NULL;\n    }\n\n    if (S_ISDIR(buffer.st_mode)) {\n        return copy_string(file);\n    }\n\n    if (!S_ISLNK(buffer.st_mode)) {\n        return copy_string(file);\n    }\n\n    char *result = realpath(file, NULL);\n    return result;\n}\n\nstatic enum watch_kind\nresolve_watch_kind(char *file)\n{\n    struct stat buffer;\n    if (lstat(file, &buffer) != 0) {\n        return WATCH_KIND_INVALID;\n    }\n\n    if (S_ISDIR(buffer.st_mode)) {\n        return WATCH_KIND_CATALOG;\n    }\n\n    if (!S_ISLNK(buffer.st_mode)) {\n        return WATCH_KIND_FILE;\n    }\n\n    return WATCH_KIND_INVALID;\n}\n\nstatic char *\nsame_catalog(char *absolutepath, struct watched_catalog *catalog_info)\n{\n    char *last_slash = strrchr(absolutepath, '/');\n    if (!last_slash) return NULL;\n\n    char *filename = NULL;\n\n    // NOTE(koekeisihya): null terminate '/' to cut off filename\n    *last_slash = '\\0';\n\n    if (same_string(absolutepath, catalog_info->directory)) {\n        filename = !catalog_info->extension\n                 ? last_slash + 1\n                 : same_string(catalog_info->extension, strrchr(last_slash + 1, '.'))\n                 ? last_slash + 1\n                 : NULL;\n    }\n\n    // NOTE(koekeisihya): revert '/' to restore filename\n    *last_slash = '/';\n\n    return filename;\n}\n\nstatic inline bool\nsame_file(char *absolutepath, struct watched_file *file_info)\n{\n    bool result = same_string(absolutepath, file_info->absolutepath);\n    return result;\n}\n\nstatic FSEVENT_CALLBACK(hotloader_handler)\n{\n    /* NOTE(asmvik): We sometimes get two events upon file save. */\n    struct hotloader *hotloader = (struct hotloader *) context;\n    char **files = (char **) file_paths;\n\n    for (unsigned file_index = 0; file_index < file_count; ++file_index) {\n        for (unsigned watch_index = 0; watch_index < hotloader->watch_count; ++watch_index) {\n            struct watched_entry *watch_info = hotloader->watch_list + watch_index;\n            if (watch_info->kind == WATCH_KIND_CATALOG) {\n                char *filename = same_catalog(files[file_index], &watch_info->catalog_info);\n                if (!filename) continue;\n\n                hotloader->callback(files[file_index],\n                                    watch_info->catalog_info.directory,\n                                    filename);\n                break;\n            } else if (watch_info->kind == WATCH_KIND_FILE) {\n                bool match = same_file(files[file_index], &watch_info->file_info);\n                if (!match) continue;\n\n                hotloader->callback(watch_info->file_info.absolutepath,\n                                    watch_info->file_info.directory,\n                                    watch_info->file_info.filename);\n                break;\n            }\n        }\n    }\n}\n\nstatic inline void\nhotloader_add_watched_entry(struct hotloader *hotloader, struct watched_entry entry)\n{\n    if (!hotloader->watch_list) {\n        hotloader->watch_capacity = 32;\n        hotloader->watch_list = (struct watched_entry *) malloc(hotloader->watch_capacity * sizeof(struct watched_entry));\n    }\n\n    if (hotloader->watch_count >= hotloader->watch_capacity) {\n        hotloader->watch_capacity = (unsigned) ceil(hotloader->watch_capacity * 1.5f);\n        hotloader->watch_list = (struct watched_entry *) realloc(hotloader->watch_list, hotloader->watch_capacity * sizeof(struct watched_entry));\n    }\n\n    hotloader->watch_list[hotloader->watch_count++] = entry;\n}\n\nbool hotloader_add_catalog(struct hotloader *hotloader, const char *directory, const char *extension)\n{\n    if (hotloader->enabled) return false;\n\n    char *real_path = resolve_symlink(directory);\n    if (!real_path) return false;\n\n    enum watch_kind kind = resolve_watch_kind(real_path);\n    if (kind != WATCH_KIND_CATALOG) return false;\n\n    hotloader_add_watched_entry(hotloader, (struct watched_entry) {\n        .kind = WATCH_KIND_CATALOG,\n        .catalog_info = {\n            .directory = real_path,\n            .extension = extension\n                       ? copy_string(extension)\n                       : NULL\n        }\n    });\n\n    return true;\n}\n\nbool hotloader_add_file(struct hotloader *hotloader, const char *file)\n{\n    if (hotloader->enabled) return false;\n\n    char *real_path = resolve_symlink(file);\n    if (!real_path) return false;\n\n    enum watch_kind kind = resolve_watch_kind(real_path);\n    if (kind != WATCH_KIND_FILE) return false;\n\n    hotloader_add_watched_entry(hotloader, (struct watched_entry) {\n        .kind = WATCH_KIND_FILE,\n        .file_info = {\n            .absolutepath = real_path,\n            .directory = file_directory(real_path),\n            .filename = file_name(real_path)\n        }\n    });\n\n    return true;\n}\n\nbool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback)\n{\n    if (hotloader->enabled || !hotloader->watch_count) return false;\n\n    CFStringRef string_refs[hotloader->watch_count];\n    for (unsigned index = 0; index < hotloader->watch_count; ++index) {\n        struct watched_entry *watch_info = hotloader->watch_list + index;\n        char *directory = watch_info->kind == WATCH_KIND_FILE\n                        ? watch_info->file_info.directory\n                        : watch_info->catalog_info.directory;\n        string_refs[index] = CFStringCreateWithCString(kCFAllocatorDefault,\n                                                       directory,\n                                                       kCFStringEncodingUTF8);\n    }\n\n    FSEventStreamContext context = {\n        .info = hotloader\n    };\n\n    hotloader->path = (CFArrayRef) CFArrayCreate(NULL,\n                                                 (const void **) string_refs,\n                                                 hotloader->watch_count,\n                                                 &kCFTypeArrayCallBacks);\n\n    hotloader->flags = kFSEventStreamCreateFlagNoDefer |\n                       kFSEventStreamCreateFlagFileEvents;\n\n    hotloader->stream = FSEventStreamCreate(NULL,\n                                            hotloader_handler,\n                                            &context,\n                                            hotloader->path,\n                                            kFSEventStreamEventIdSinceNow,\n                                            0.5,\n                                            hotloader->flags);\n\n    FSEventStreamScheduleWithRunLoop(hotloader->stream,\n                                     CFRunLoopGetMain(),\n                                     kCFRunLoopDefaultMode);\n\n    hotloader->callback = callback;\n    FSEventStreamStart(hotloader->stream);\n    hotloader->enabled = true;\n\n    return true;\n}\n\nvoid hotloader_end(struct hotloader *hotloader)\n{\n    if (!hotloader->enabled) return;\n\n    FSEventStreamStop(hotloader->stream);\n    FSEventStreamInvalidate(hotloader->stream);\n    FSEventStreamRelease(hotloader->stream);\n\n    CFIndex count = CFArrayGetCount(hotloader->path);\n    for (unsigned index = 0; index < count; ++index) {\n        struct watched_entry *watch_info = hotloader->watch_list + index;\n        if (watch_info->kind == WATCH_KIND_FILE) {\n            free(watch_info->file_info.absolutepath);\n            free(watch_info->file_info.directory);\n            free(watch_info->file_info.filename);\n        } else if (watch_info->kind == WATCH_KIND_CATALOG) {\n            free(watch_info->catalog_info.directory);\n            if (watch_info->catalog_info.extension) {\n                free(watch_info->catalog_info.extension);\n            }\n        }\n        CFRelease(CFArrayGetValueAtIndex(hotloader->path, index));\n    }\n\n    CFRelease(hotloader->path);\n    free(hotloader->watch_list);\n    memset(hotloader, 0, sizeof(struct hotloader));\n}\n"
  },
  {
    "path": "src/hotload.h",
    "content": "#ifndef SKHD_HOTLOAD_H\n#define SKHD_HOTLOAD_H\n\n#ifndef __cplusplus\n#include <stdbool.h>\n#endif\n\n#include <Carbon/Carbon.h>\n\n#define HOTLOADER_CALLBACK(name) void name(char *absolutepath, char *directory, char *filename)\ntypedef HOTLOADER_CALLBACK(hotloader_callback);\n\nstruct watched_entry;\nstruct hotloader\n{\n    FSEventStreamEventFlags flags;\n    FSEventStreamRef stream;\n    CFArrayRef path;\n    bool enabled;\n\n    hotloader_callback *callback;\n    struct watched_entry *watch_list;\n    unsigned watch_capacity;\n    unsigned watch_count;\n};\n\nbool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback);\nvoid hotloader_end(struct hotloader *hotloader);\nbool hotloader_add_catalog(struct hotloader *hotloader, const char *directory, const char *extension);\nbool hotloader_add_file(struct hotloader *hotloader, const char *file);\n\n#endif\n"
  },
  {
    "path": "src/locale.c",
    "content": "#include \"locale.h\"\n#include \"hashtable.h\"\n#include \"sbuffer.h\"\n#include <Carbon/Carbon.h>\n#include <IOKit/hidsystem/ev_keymap.h>\n\n#define array_count(a) (sizeof((a)) / sizeof(*(a)))\n\nstatic struct table keymap_table;\nstatic char **keymap_keys;\n\nstatic char *copy_cfstring(CFStringRef string)\n{\n    CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);\n    char *result = malloc(num_bytes + 1);\n\n    // NOTE(asmvik): Boolean: typedef -> unsigned char; false = 0, true != 0\n    if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) {\n        free(result);\n        result = NULL;\n    }\n\n    return result;\n}\n\nstatic int hash_keymap(const char *a)\n{\n    unsigned long hash = 0, high;\n    while (*a) {\n        hash = (hash << 4) + *a++;\n        high = hash & 0xF0000000;\n        if (high) {\n            hash ^= (high >> 24);\n        }\n        hash &= ~high;\n    }\n    return hash;\n}\n\nstatic bool same_keymap(const char *a, const char *b)\n{\n    while (*a && *b && *a == *b) {\n        ++a;\n        ++b;\n    }\n    return *a == '\\0' && *b == '\\0';\n}\n\nstatic void free_keycode_map(void)\n{\n    for (int i = 0; i < buf_len(keymap_keys); ++i) {\n        free(keymap_keys[i]);\n    }\n\n    buf_free(keymap_keys);\n    keymap_keys = NULL;\n}\n\nstatic uint32_t layout_dependent_keycodes[] =\n{\n    kVK_ANSI_A,            kVK_ANSI_B,           kVK_ANSI_C,\n    kVK_ANSI_D,            kVK_ANSI_E,           kVK_ANSI_F,\n    kVK_ANSI_G,            kVK_ANSI_H,           kVK_ANSI_I,\n    kVK_ANSI_J,            kVK_ANSI_K,           kVK_ANSI_L,\n    kVK_ANSI_M,            kVK_ANSI_N,           kVK_ANSI_O,\n    kVK_ANSI_P,            kVK_ANSI_Q,           kVK_ANSI_R,\n    kVK_ANSI_S,            kVK_ANSI_T,           kVK_ANSI_U,\n    kVK_ANSI_V,            kVK_ANSI_W,           kVK_ANSI_X,\n    kVK_ANSI_Y,            kVK_ANSI_Z,           kVK_ANSI_0,\n    kVK_ANSI_1,            kVK_ANSI_2,           kVK_ANSI_3,\n    kVK_ANSI_4,            kVK_ANSI_5,           kVK_ANSI_6,\n    kVK_ANSI_7,            kVK_ANSI_8,           kVK_ANSI_9,\n    kVK_ANSI_Grave,        kVK_ANSI_Equal,       kVK_ANSI_Minus,\n    kVK_ANSI_RightBracket, kVK_ANSI_LeftBracket, kVK_ANSI_Quote,\n    kVK_ANSI_Semicolon,    kVK_ANSI_Backslash,   kVK_ANSI_Comma,\n    kVK_ANSI_Slash,        kVK_ANSI_Period,      kVK_ISO_Section\n};\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wint-to-void-pointer-cast\"\nbool initialize_keycode_map(void)\n{\n    UniChar chars[255];\n    UniCharCount len;\n    UInt32 state;\n\n    TISInputSourceRef keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();\n    CFDataRef uchr = (CFDataRef) TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);\n    CFRelease(keyboard);\n\n    UCKeyboardLayout *keyboard_layout = (UCKeyboardLayout *) CFDataGetBytePtr(uchr);\n    if (!keyboard_layout) return false;\n\n    free_keycode_map();\n    table_free(&keymap_table);\n    table_init(&keymap_table,\n               61,\n               (table_hash_func) hash_keymap,\n               (table_compare_func) same_keymap);\n\n    for (int i = 0; i < array_count(layout_dependent_keycodes); ++i) {\n        if (UCKeyTranslate(keyboard_layout,\n                           layout_dependent_keycodes[i],\n                           kUCKeyActionDown,\n                           0,\n                           LMGetKbdType(),\n                           kUCKeyTranslateNoDeadKeysMask,\n                           &state,\n                           array_count(chars),\n                           &len,\n                           chars) == noErr && len > 0) {\n            CFStringRef key_cfstring = CFStringCreateWithCharacters(NULL, chars, len);\n            char *key_cstring = copy_cfstring(key_cfstring);\n            CFRelease(key_cfstring);\n\n            if (key_cstring) {\n                table_add(&keymap_table, key_cstring, (void *)layout_dependent_keycodes[i]);\n                buf_push(keymap_keys, key_cstring);\n            }\n        }\n    }\n\n    return true;\n}\n#pragma clang diagnostic pop\n\nuint32_t keycode_from_char(char key)\n{\n    char lookup_key[] = { key, '\\0' };\n    uint32_t keycode = (uint32_t) (uintptr_t) table_find(&keymap_table, &lookup_key);\n    return keycode;\n}\n"
  },
  {
    "path": "src/locale.h",
    "content": "#ifndef SKHD_LOCALE_H\n#define SKHD_LOCALE_H\n\n#include <stdint.h>\n\n#define CF_NOTIFICATION_CALLBACK(name) \\\n    void name(CFNotificationCenterRef center, \\\n              void *observer, \\\n              CFNotificationName name, \\\n              const void *object, \\\n              CFDictionaryRef userInfo)\ntypedef CF_NOTIFICATION_CALLBACK(cf_notification_callback);\n\nbool initialize_keycode_map(void);\nuint32_t keycode_from_char(char key);\n\n#endif\n"
  },
  {
    "path": "src/log.h",
    "content": "#ifndef SKHD_LOG_H\n#define SKHD_LOG_H\n\nstatic bool verbose;\n\nstatic inline void\ndebug(const char *format, ...)\n{\n    if (!verbose) return;\n\n    va_list args;\n    va_start(args, format);\n    vfprintf(stdout, format, args);\n    va_end(args);\n}\n\nstatic inline void\nwarn(const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    vfprintf(stderr, format, args);\n    va_end(args);\n}\n\nstatic inline void\nerror(const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    vfprintf(stderr, format, args);\n    va_end(args);\n    exit(EXIT_FAILURE);\n}\n\nstatic inline void\nrequire(const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    vfprintf(stderr, format, args);\n    va_end(args);\n    exit(EXIT_SUCCESS);\n}\n\n#endif\n"
  },
  {
    "path": "src/notify.c",
    "content": "void notify_init(void)\n{\n    class_replaceMethod(objc_getClass(\"NSBundle\"),\n                        sel_registerName(\"bundleIdentifier\"),\n                        method_getImplementation((void *)^{return CFSTR(\"com.asmvik.skhd\");}),\n                        NULL);\n}\n\nvoid notify(const char *subtitle, const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n\n    CFStringRef format_ref = CFStringCreateWithCString(NULL, format, kCFStringEncodingUTF8);\n    CFStringRef subtitle_ref = CFStringCreateWithCString(NULL, subtitle, kCFStringEncodingUTF8);\n    CFStringRef message_ref = CFStringCreateWithFormatAndArguments(NULL, NULL, format_ref, args);\n\n    void *center = ((void * (*)(void *, SEL))objc_msgSend)((void *) objc_getClass(\"NSUserNotificationCenter\"), sel_registerName(\"defaultUserNotificationCenter\"));\n    void *notification = ((void * (*)(void *, SEL, SEL))objc_msgSend)((void *) objc_getClass(\"NSUserNotification\"), sel_registerName(\"alloc\"), sel_registerName(\"init\"));\n\n    ((void (*)(void *, SEL, CFStringRef))objc_msgSend)(notification, sel_registerName(\"setTitle:\"), CFSTR(\"skhd\"));\n    ((void (*)(void *, SEL, CFStringRef))objc_msgSend)(notification, sel_registerName(\"setSubtitle:\"), subtitle_ref);\n    ((void (*)(void *, SEL, CFStringRef))objc_msgSend)(notification, sel_registerName(\"setInformativeText:\"), message_ref);\n    ((void (*)(void *, SEL, void *))objc_msgSend)(center, sel_registerName(\"deliverNotification:\"), notification);\n\n    CFRelease(message_ref);\n    CFRelease(subtitle_ref);\n    CFRelease(format_ref);\n\n    va_end(args);\n}\n"
  },
  {
    "path": "src/parse.c",
    "content": "#include \"parse.h\"\n#include \"tokenize.h\"\n#include \"locale.h\"\n#include \"hotkey.h\"\n#include \"hashtable.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdarg.h>\n#include <stdbool.h>\n\nstatic struct mode *\nfind_or_init_default_mode(struct parser *parser)\n{\n    struct mode *default_mode;\n\n    if ((default_mode = table_find(parser->mode_map, \"default\"))) {\n        return default_mode;\n    }\n\n    default_mode = malloc(sizeof(struct mode));\n    default_mode->name = copy_string(\"default\");\n    default_mode->initialized = false;\n\n    table_init(&default_mode->hotkey_map, 131,\n               (table_hash_func) hash_hotkey,\n               (table_compare_func) same_hotkey);\n\n    default_mode->capture = false;\n    default_mode->command = NULL;\n    table_add(parser->mode_map, default_mode->name, default_mode);\n\n    return default_mode;\n}\n\nstatic char *\nread_file(const char *file)\n{\n    unsigned length;\n    char *buffer = NULL;\n    FILE *handle = fopen(file, \"r\");\n\n    if (handle) {\n        fseek(handle, 0, SEEK_END);\n        length = ftell(handle);\n        fseek(handle, 0, SEEK_SET);\n        buffer = malloc(length + 1);\n        fread(buffer, length, 1, handle);\n        buffer[length] = '\\0';\n        fclose(handle);\n    }\n\n    return buffer;\n}\n\nstatic char *\ncopy_string_count(char *s, int length)\n{\n    char *result = malloc(length + 1);\n    memcpy(result, s, length);\n    result[length] = '\\0';\n    return result;\n}\n\nstatic uint32_t\nkeycode_from_hex(char *hex)\n{\n    uint32_t result;\n    sscanf(hex, \"%x\", &result);\n    return result;\n}\n\nstatic void\nparse_command(struct parser *parser, struct hotkey *hotkey)\n{\n    struct token command = parser_previous(parser);\n    char *result = copy_string_count(command.text, command.length);\n    debug(\"\\tcmd: '%s'\\n\", result);\n    buf_push(hotkey->command, result);\n}\n\nstatic void\nparse_process_command_list(struct parser *parser, struct hotkey *hotkey)\n{\n    if (parser_match(parser, Token_String)) {\n        struct token name_token = parser_previous(parser);\n        char *name = copy_string_count(name_token.text, name_token.length);\n        for (char *s = name; *s; ++s) *s = tolower(*s);\n        buf_push(hotkey->process_name, name);\n        if (parser_match(parser, Token_Command)) {\n            parse_command(parser, hotkey);\n            parse_process_command_list(parser, hotkey);\n        } else if (parser_match(parser, Token_Unbound)) {\n            buf_push(hotkey->command, NULL);\n            parse_process_command_list(parser, hotkey);\n        } else {\n            parser_report_error(parser, parser_peek(parser), \"expected '~' or ':' followed by command\\n\");\n        }\n    } else if (parser_match(parser, Token_Wildcard)) {\n        if (parser_match(parser, Token_Command)) {\n            struct token command = parser_previous(parser);\n            char *result = copy_string_count(command.text, command.length);\n            debug(\"\\tcmd: '%s'\\n\", result);\n            hotkey->wildcard_command = result;\n            parse_process_command_list(parser, hotkey);\n        } else if (parser_match(parser, Token_Unbound)) {\n            hotkey->wildcard_command = NULL;\n            parse_process_command_list(parser, hotkey);\n        } else {\n            parser_report_error(parser, parser_peek(parser), \"expected '~' or ':' followed by command\\n\");\n        }\n    } else if (parser_match(parser, Token_EndList)) {\n        if (!buf_len(hotkey->process_name)) {\n            parser_report_error(parser, parser_previous(parser), \"list must contain at least one value\\n\");\n        }\n    } else {\n        parser_report_error(parser, parser_peek(parser), \"expected process command mapping or ']'\\n\");\n    }\n}\n\nstatic void\nparse_activate(struct parser *parser, struct hotkey *hotkey)\n{\n    parse_command(parser, hotkey);\n    hotkey->flags |= Hotkey_Flag_Activate;\n\n    if (!table_find(parser->mode_map, hotkey->command[0])) {\n        parser_report_error(parser, parser_previous(parser), \"undeclared identifier\\n\");\n    }\n}\n\nstatic uint32_t\nparse_key_hex(struct parser *parser)\n{\n    struct token key = parser_previous(parser);\n    char *hex = copy_string_count(key.text, key.length);\n    uint32_t keycode = keycode_from_hex(hex);\n    free(hex);\n    debug(\"\\tkey: '%.*s' (0x%02x)\\n\", key.length, key.text, keycode);\n    return keycode;\n}\n\nstatic uint32_t\nparse_key(struct parser *parser)\n{\n    uint32_t keycode;\n    struct token key = parser_previous(parser);\n    keycode = keycode_from_char(*key.text);\n    debug(\"\\tkey: '%c' (0x%02x)\\n\", *key.text, keycode);\n    return keycode;\n}\n\n#define KEY_HAS_IMPLICIT_FN_MOD  4\n#define KEY_HAS_IMPLICIT_NX_MOD  35\nstatic uint32_t literal_keycode_value[] =\n{\n    kVK_Return,     kVK_Tab,           kVK_Space,\n    kVK_Delete,     kVK_Escape,        kVK_ForwardDelete,\n    kVK_Home,       kVK_End,           kVK_PageUp,\n    kVK_PageDown,   kVK_Help,          kVK_LeftArrow,\n    kVK_RightArrow, kVK_UpArrow,       kVK_DownArrow,\n    kVK_F1,         kVK_F2,            kVK_F3,\n    kVK_F4,         kVK_F5,            kVK_F6,\n    kVK_F7,         kVK_F8,            kVK_F9,\n    kVK_F10,        kVK_F11,           kVK_F12,\n    kVK_F13,        kVK_F14,           kVK_F15,\n    kVK_F16,        kVK_F17,           kVK_F18,\n    kVK_F19,        kVK_F20,\n\n    NX_KEYTYPE_SOUND_UP,        NX_KEYTYPE_SOUND_DOWN,      NX_KEYTYPE_MUTE,\n    NX_KEYTYPE_PLAY,            NX_KEYTYPE_PREVIOUS,        NX_KEYTYPE_NEXT,\n    NX_KEYTYPE_REWIND,          NX_KEYTYPE_FAST,            NX_KEYTYPE_BRIGHTNESS_UP,\n    NX_KEYTYPE_BRIGHTNESS_DOWN, NX_KEYTYPE_ILLUMINATION_UP, NX_KEYTYPE_ILLUMINATION_DOWN\n};\n\nstatic inline void\nhandle_implicit_literal_flags(struct hotkey *hotkey, int literal_index)\n{\n    if ((literal_index > KEY_HAS_IMPLICIT_FN_MOD) &&\n        (literal_index < KEY_HAS_IMPLICIT_NX_MOD)) {\n        hotkey->flags |= Hotkey_Flag_Fn;\n    } else if (literal_index >= KEY_HAS_IMPLICIT_NX_MOD) {\n        hotkey->flags |= Hotkey_Flag_NX;\n    }\n}\n\nstatic void\nparse_key_literal(struct parser *parser, struct hotkey *hotkey)\n{\n    struct token key = parser_previous(parser);\n    for (int i = 0; i < array_count(literal_keycode_str); ++i) {\n        if (token_equals(key, literal_keycode_str[i])) {\n            handle_implicit_literal_flags(hotkey, i);\n            hotkey->key = literal_keycode_value[i];\n            debug(\"\\tkey: '%.*s' (0x%02x)\\n\", key.length, key.text, hotkey->key);\n            break;\n        }\n    }\n}\n\nstatic enum hotkey_flag modifier_flags_value[] =\n{\n    Hotkey_Flag_Alt,        Hotkey_Flag_LAlt,       Hotkey_Flag_RAlt,\n    Hotkey_Flag_Shift,      Hotkey_Flag_LShift,     Hotkey_Flag_RShift,\n    Hotkey_Flag_Cmd,        Hotkey_Flag_LCmd,       Hotkey_Flag_RCmd,\n    Hotkey_Flag_Control,    Hotkey_Flag_LControl,   Hotkey_Flag_RControl,\n    Hotkey_Flag_Fn,         Hotkey_Flag_Hyper,      Hotkey_Flag_Meh,\n};\n\nstatic uint32_t\nparse_modifier(struct parser *parser)\n{\n    struct token modifier = parser_previous(parser);\n    uint32_t flags = 0;\n\n    for (int i = 0; i < array_count(modifier_flags_str); ++i) {\n        if (token_equals(modifier, modifier_flags_str[i])) {\n            flags |= modifier_flags_value[i];\n            debug(\"\\tmod: '%s'\\n\", modifier_flags_str[i]);\n            break;\n        }\n    }\n\n    if (parser_match(parser, Token_Plus)) {\n        if (parser_match(parser, Token_Modifier)) {\n            flags |= parse_modifier(parser);\n        } else {\n            parser_report_error(parser, parser_peek(parser), \"expected modifier\\n\");\n        }\n    }\n\n    return flags;\n}\n\nstatic void\nparse_mode(struct parser *parser, struct hotkey *hotkey)\n{\n    struct token identifier = parser_previous(parser);\n\n    char *name = copy_string_count(identifier.text, identifier.length);\n    struct mode *mode = table_find(parser->mode_map, name);\n    free(name);\n\n    if (!mode && token_equals(identifier, \"default\")) {\n        mode = find_or_init_default_mode(parser);\n    } else if (!mode) {\n        parser_report_error(parser, identifier, \"undeclared identifier\\n\");\n        return;\n    }\n\n    buf_push(hotkey->mode_list, mode);\n    debug(\"\\tmode: '%s'\\n\", mode->name);\n\n    if (parser_match(parser, Token_Comma)) {\n        if (parser_match(parser, Token_Identifier)) {\n            parse_mode(parser, hotkey);\n        } else {\n            parser_report_error(parser, parser_peek(parser), \"expected identifier\\n\");\n        }\n    }\n}\n\nstatic struct hotkey *\nparse_hotkey(struct parser *parser)\n{\n    struct hotkey *hotkey = malloc(sizeof(struct hotkey));\n    memset(hotkey, 0, sizeof(struct hotkey));\n    bool found_modifier;\n\n    debug(\"hotkey :: #%d {\\n\", parser->current_token.line);\n\n    if (parser_match(parser, Token_Identifier)) {\n        parse_mode(parser, hotkey);\n        if (parser->error) {\n            goto err;\n        }\n    }\n\n    if (buf_len(hotkey->mode_list) > 0) {\n        if (!parser_match(parser, Token_Insert)) {\n            parser_report_error(parser, parser_peek(parser), \"expected '<'\\n\");\n            goto err;\n        }\n    } else {\n        buf_push(hotkey->mode_list, find_or_init_default_mode(parser));\n    }\n\n    if ((found_modifier = parser_match(parser, Token_Modifier))) {\n        hotkey->flags = parse_modifier(parser);\n        if (parser->error) {\n            goto err;\n        }\n    }\n\n    if (found_modifier) {\n        if (!parser_match(parser, Token_Dash)) {\n            parser_report_error(parser, parser_peek(parser), \"expected '-'\\n\");\n            goto err;\n        }\n    }\n\n    if (parser_match(parser, Token_Key)) {\n        hotkey->key = parse_key(parser);\n    } else if (parser_match(parser, Token_Key_Hex)) {\n        hotkey->key = parse_key_hex(parser);\n    } else if (parser_match(parser, Token_Literal)) {\n        parse_key_literal(parser, hotkey);\n    } else {\n        parser_report_error(parser, parser_peek(parser), \"expected key-literal\\n\");\n        goto err;\n    }\n\n    if (parser_match(parser, Token_Arrow)) {\n        hotkey->flags |= Hotkey_Flag_Passthrough;\n    }\n\n    if (parser_match(parser, Token_Command)) {\n        parse_command(parser, hotkey);\n    } else if (parser_match(parser, Token_BeginList)) {\n        parse_process_command_list(parser, hotkey);\n        if (parser->error) {\n            goto err;\n        }\n    } else if (parser_match(parser, Token_Activate)) {\n        parse_activate(parser, hotkey);\n        if (parser->error) {\n            goto err;\n        }\n    } else {\n        parser_report_error(parser, parser_peek(parser), \"expected ':' followed by command or ';' followed by mode\\n\");\n        goto err;\n    }\n\n    debug(\"}\\n\");\n    return hotkey;\n\nerr:\n    free(hotkey);\n    return NULL;\n}\n\nstatic struct mode *\nparse_mode_decl(struct parser *parser)\n{\n    struct mode *mode = malloc(sizeof(struct mode));\n    struct token identifier = parser_previous(parser);\n\n    mode->name = copy_string_count(identifier.text, identifier.length);\n    mode->initialized = true;\n\n    table_init(&mode->hotkey_map, 131,\n               (table_hash_func) hash_hotkey,\n               (table_compare_func) same_hotkey);\n\n    if (parser_match(parser, Token_Capture)) {\n        mode->capture = true;\n    } else {\n        mode->capture = false;\n    }\n\n    if (parser_match(parser, Token_Command)) {\n        mode->command = copy_string_count(parser->previous_token.text, parser->previous_token.length);\n    } else {\n        mode->command = NULL;\n    }\n\n    return mode;\n}\n\nvoid parse_declaration(struct parser *parser)\n{\n    parser_match(parser, Token_Decl);\n    if (parser_match(parser, Token_Identifier)) {\n        struct token identifier = parser_previous(parser);\n        struct mode *mode = parse_mode_decl(parser);\n\n        struct mode *existing_mode = table_find(parser->mode_map, mode->name);\n        if  (existing_mode) {\n            if (same_string(existing_mode->name, \"default\") && !existing_mode->initialized) {\n                existing_mode->initialized = true;\n                existing_mode->capture = mode->capture;\n                existing_mode->command = mode->command;\n            } else {\n                parser_report_error(parser, identifier, \"duplicate declaration '%s'\\n\", mode->name);\n                if (mode->command) free(mode->command);\n            }\n\n            free(mode->name);\n            free(mode);\n        } else {\n            table_add(parser->mode_map, mode->name, mode);\n        }\n    } else {\n        parser_report_error(parser, parser_peek(parser), \"expected identifier\\n\");\n    }\n}\n\nvoid parse_option_blacklist(struct parser *parser)\n{\n    if (parser_match(parser, Token_String)) {\n        struct token name_token = parser_previous(parser);\n        char *name = copy_string_count(name_token.text, name_token.length);\n        for (char *s = name; *s; ++s) *s = tolower(*s);\n        debug(\"\\t%s\\n\", name);\n        table_add(parser->blacklst, name, name);\n        parse_option_blacklist(parser);\n    } else if (parser_match(parser, Token_EndList)) {\n        if (parser->blacklst->count == 0) {\n            parser_report_error(parser, parser_previous(parser), \"list must contain at least one value\\n\");\n        }\n    } else {\n        parser_report_error(parser, parser_peek(parser), \"expected process name or ']'\\n\");\n    }\n}\n\nvoid parse_option_load(struct parser *parser, struct token option)\n{\n    struct token filename_token = parser_previous(parser);\n    char *filename = copy_string_count(filename_token.text, filename_token.length);\n    debug(\"\\t%s\\n\", filename);\n\n    if (*filename != '/') {\n        char *directory = file_directory(parser->file);\n\n        size_t directory_length = strlen(directory);\n        size_t filename_length  = strlen(filename);\n        size_t total_length     = directory_length + filename_length + 2;\n\n        char *absolutepath = malloc(total_length * sizeof(char));\n        snprintf(absolutepath, total_length, \"%s/%s\", directory, filename);\n        free(filename);\n\n        filename = absolutepath;\n    }\n\n    buf_push(parser->load_directives, ((struct load_directive) {\n        .file  = filename,\n        .option = option\n    }));\n}\n\nvoid parse_option(struct parser *parser)\n{\n    parser_match(parser, Token_Option);\n    struct token option = parser_previous(parser);\n    if (token_equals(option, \"blacklist\")) {\n        if (parser_match(parser, Token_BeginList)) {\n            debug(\"blacklist :: #%d {\\n\", option.line);\n            parse_option_blacklist(parser);\n            debug(\"}\\n\");\n        } else {\n            parser_report_error(parser, option, \"expected '[' followed by list of process names\\n\");\n        }\n    } else if (token_equals(option, \"load\")) {\n        if (parser_match(parser, Token_String)) {\n            debug(\"load :: #%d {\\n\", option.line);\n            parse_option_load(parser, option);\n            debug(\"}\\n\");\n        } else {\n            parser_report_error(parser, option, \"expected filename\\n\");\n        }\n    } else {\n        parser_report_error(parser, option, \"invalid option specified\\n\");\n    }\n}\n\nbool parse_config(struct parser *parser)\n{\n    struct mode *mode;\n    struct hotkey *hotkey;\n\n    while (!parser_eof(parser)) {\n        if (parser->error) break;\n\n        if ((parser_check(parser, Token_Identifier)) ||\n            (parser_check(parser, Token_Modifier)) ||\n            (parser_check(parser, Token_Literal)) ||\n            (parser_check(parser, Token_Key_Hex)) ||\n            (parser_check(parser, Token_Key))) {\n            if ((hotkey = parse_hotkey(parser))) {\n                for (int i = 0; i < buf_len(hotkey->mode_list); ++i) {\n                    mode = hotkey->mode_list[i];\n                    table_add(&mode->hotkey_map, hotkey, hotkey);\n                }\n            }\n        } else if (parser_check(parser, Token_Decl)) {\n            parse_declaration(parser);\n        } else if (parser_check(parser, Token_Option)) {\n            parse_option(parser);\n        } else {\n            parser_report_error(parser, parser_peek(parser), \"expected decl, modifier or key-literal\\n\");\n        }\n    }\n\n    if (parser->error) {\n        free_mode_map(parser->mode_map);\n        free_blacklist(parser->blacklst);\n        return false;\n    }\n\n    return true;\n}\n\nstruct hotkey *\nparse_keypress(struct parser *parser)\n{\n    if ((parser_check(parser, Token_Modifier)) ||\n        (parser_check(parser, Token_Literal)) ||\n        (parser_check(parser, Token_Key_Hex)) ||\n        (parser_check(parser, Token_Key))) {\n        struct hotkey *hotkey = malloc(sizeof(struct hotkey));\n        memset(hotkey, 0, sizeof(struct hotkey));\n        bool found_modifier;\n\n        if ((found_modifier = parser_match(parser, Token_Modifier))) {\n            hotkey->flags = parse_modifier(parser);\n            if (parser->error) {\n                goto err;\n            }\n        }\n\n        if (found_modifier) {\n            if (!parser_match(parser, Token_Dash)) {\n                goto err;\n            }\n        }\n\n        if (parser_match(parser, Token_Key)) {\n            hotkey->key = parse_key(parser);\n        } else if (parser_match(parser, Token_Key_Hex)) {\n            hotkey->key = parse_key_hex(parser);\n        } else if (parser_match(parser, Token_Literal)) {\n            parse_key_literal(parser, hotkey);\n        } else {\n            goto err;\n        }\n\n        return hotkey;\n\n    err:\n        free(hotkey);\n        return NULL;\n    }\n\n    return NULL;\n}\n\nstruct token\nparser_peek(struct parser *parser)\n{\n    return parser->current_token;\n}\n\nstruct token\nparser_previous(struct parser *parser)\n{\n    return parser->previous_token;\n}\n\nbool parser_eof(struct parser *parser)\n{\n    struct token token = parser_peek(parser);\n    return token.type == Token_EndOfStream;\n}\n\nstruct token\nparser_advance(struct parser *parser)\n{\n    if (!parser_eof(parser)) {\n        parser->previous_token = parser->current_token;\n        parser->current_token = get_token(&parser->tokenizer);\n    }\n    return parser_previous(parser);\n}\n\nbool parser_check(struct parser *parser, enum token_type type)\n{\n    if (parser_eof(parser)) return false;\n    struct token token = parser_peek(parser);\n    return token.type == type;\n}\n\nbool parser_match(struct parser *parser, enum token_type type)\n{\n    if (parser_check(parser, type)) {\n        parser_advance(parser);\n        return true;\n    }\n    return false;\n}\n\nvoid parser_report_error(struct parser *parser, struct token token, const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    fprintf(stderr, \"#%d:%d \", token.line, token.cursor);\n    vfprintf(stderr, format, args);\n    va_end(args);\n    parser->error = true;\n}\n\nvoid parser_do_directives(struct parser *parser, struct hotloader *hotloader, bool thwart_hotloader)\n{\n    bool error = false;\n\n    for (int i = 0; i < buf_len(parser->load_directives); ++i) {\n        struct load_directive load = parser->load_directives[i];\n\n        struct parser directive_parser;\n        if (parser_init(&directive_parser, parser->mode_map, parser->blacklst, load.file)) {\n            if (!thwart_hotloader) {\n                hotloader_add_file(hotloader, load.file);\n            }\n\n            if (parse_config(&directive_parser)) {\n                parser_do_directives(&directive_parser, hotloader, thwart_hotloader);\n            } else {\n                error = true;\n            }\n\n            parser_destroy(&directive_parser);\n        } else {\n            warn(\"skhd: could not open file '%s' from load directive #%d:%d\\n\", load.file, load.option.line, load.option.cursor);\n        }\n\n        free(load.file);\n    }\n    buf_free(parser->load_directives);\n\n    if (error) {\n        free_mode_map(parser->mode_map);\n        free_blacklist(parser->blacklst);\n    }\n}\n\nbool parser_init(struct parser *parser, struct table *mode_map, struct table *blacklst, char *file)\n{\n    memset(parser, 0, sizeof(struct parser));\n    char *buffer = read_file(file);\n    if (buffer) {\n        parser->file     = file;\n        parser->mode_map = mode_map;\n        parser->blacklst = blacklst;\n        tokenizer_init(&parser->tokenizer, buffer);\n        parser_advance(parser);\n        return true;\n    }\n    return false;\n}\n\nbool parser_init_text(struct parser *parser, char *text)\n{\n    memset(parser, 0, sizeof(struct parser));\n    tokenizer_init(&parser->tokenizer, text);\n    parser_advance(parser);\n    return true;\n}\n\nvoid parser_destroy(struct parser *parser)\n{\n    free(parser->tokenizer.buffer);\n}\n"
  },
  {
    "path": "src/parse.h",
    "content": "#ifndef SKHD_PARSE_H\n#define SKHD_PARSE_H\n\n#include \"tokenize.h\"\n#include <stdbool.h>\n\nstruct load_directive\n{\n    char *file;\n    struct token option;\n};\n\nstruct table;\nstruct parser\n{\n    char *file;\n    struct token previous_token;\n    struct token current_token;\n    struct tokenizer tokenizer;\n    struct table *mode_map;\n    struct table *blacklst;\n    struct load_directive *load_directives;\n    bool error;\n};\n\nbool parse_config(struct parser *parser);\nstruct hotkey *parse_keypress(struct parser *parser);\n\nstruct token parser_peek(struct parser *parser);\nstruct token parser_previous(struct parser *parser);\nbool parser_eof(struct parser *parser);\nstruct token parser_advance(struct parser *parser);\nbool parser_check(struct parser *parser, enum token_type type);\nbool parser_match(struct parser *parser, enum token_type type);\nbool parser_init(struct parser *parser, struct table *mode_map, struct table *blacklst, char *file);\nbool parser_init_text(struct parser *parser, char *text);\nvoid parser_destroy(struct parser *parser);\nvoid parser_report_error(struct parser *parser, struct token token, const char *format, ...);\n\n#endif\n"
  },
  {
    "path": "src/sbuffer.h",
    "content": "#ifndef SBUFFER_H\n#define SBUFFER_H\n\n#include <stdlib.h>\n#include <stdint.h>\n\nstruct buf_hdr\n{\n    size_t len;\n    size_t cap;\n    char buf[0];\n};\n\n#define buf_MAX(a, b) ((a) > (b) ? (a) : (b))\n#define buf_OFFSETOF(t, f) (size_t)((char *)&(((t *)0)->f) - (char *)0)\n\n#define buf__hdr(b) ((struct buf_hdr *)((char *)(b) - buf_OFFSETOF(struct buf_hdr, buf)))\n#define buf__should_grow(b, n) (buf_len(b) + (n) >= buf_cap(b))\n#define buf__fit(b, n) (buf__should_grow(b, n) ? ((b) = buf__grow_f(b, buf_len(b) + (n), sizeof(*(b)))) : 0)\n\n#define buf_len(b) ((b) ? buf__hdr(b)->len : 0)\n#define buf_cap(b) ((b) ? buf__hdr(b)->cap : 0)\n#define buf_push(b, x) (buf__fit(b, 1), (b)[buf_len(b)] = (x), buf__hdr(b)->len++)\n#define buf_last(b) ((b)[buf_len(b)-1])\n#define buf_free(b) ((b) ? free(buf__hdr(b)) : 0)\n\nstatic void *buf__grow_f(const void *buf, size_t new_len, size_t elem_size)\n{\n    size_t new_cap = buf_MAX(1 + 2*buf_cap(buf), new_len);\n    size_t new_size = buf_OFFSETOF(struct buf_hdr, buf) + new_cap*elem_size;\n    struct buf_hdr *new_hdr = realloc(buf ? buf__hdr(buf) : 0, new_size);\n    new_hdr->cap = new_cap;\n    if (!buf) {\n        new_hdr->len = 0;\n    }\n    return new_hdr->buf;\n}\n\n#endif\n"
  },
  {
    "path": "src/service.h",
    "content": "#ifndef SERVICE_H\n#define SERVICE_H\n\n#include <spawn.h>\n#include <sys/stat.h>\n\n#define MAXLEN 512\n\n#define _PATH_LAUNCHCTL   \"/bin/launchctl\"\n#define _NAME_SKHD_PLIST \"com.asmvik.skhd\"\n#define _PATH_SKHD_PLIST \"%s/Library/LaunchAgents/\"_NAME_SKHD_PLIST\".plist\"\n\n#define _SKHD_PLIST \\\n    \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\" \\\n    \"<!DOCTYPE plist PUBLIC \\\"-//Apple//DTD PLIST 1.0//EN\\\" \\\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\\\">\\n\" \\\n    \"<plist version=\\\"1.0\\\">\\n\" \\\n    \"<dict>\\n\" \\\n    \"    <key>Label</key>\\n\" \\\n    \"    <string>\"_NAME_SKHD_PLIST\"</string>\\n\" \\\n    \"    <key>ProgramArguments</key>\\n\" \\\n    \"    <array>\\n\" \\\n    \"        <string>%s</string>\\n\" \\\n    \"    </array>\\n\" \\\n    \"    <key>EnvironmentVariables</key>\\n\" \\\n    \"    <dict>\\n\" \\\n    \"        <key>PATH</key>\\n\" \\\n    \"        <string>%s</string>\\n\" \\\n    \"    </dict>\\n\" \\\n    \"    <key>RunAtLoad</key>\\n\" \\\n    \"    <true/>\\n\" \\\n    \"    <key>KeepAlive</key>\\n\" \\\n    \"    <dict>\\n\" \\\n    \"        <key>SuccessfulExit</key>\\n\" \\\n    \" \t     <false/>\\n\" \\\n    \" \t     <key>Crashed</key>\\n\" \\\n    \" \t     <true/>\\n\" \\\n    \"    </dict>\\n\" \\\n    \"    <key>StandardOutPath</key>\\n\" \\\n    \"    <string>/tmp/skhd_%s.out.log</string>\\n\" \\\n    \"    <key>StandardErrorPath</key>\\n\" \\\n    \"    <string>/tmp/skhd_%s.err.log</string>\\n\" \\\n    \"    <key>ProcessType</key>\\n\" \\\n    \"    <string>Interactive</string>\\n\" \\\n    \"    <key>Nice</key>\\n\" \\\n    \"    <integer>-20</integer>\\n\" \\\n    \"</dict>\\n\" \\\n    \"</plist>\"\n\n//\n// NOTE(asmvik): A launchd service has the following states:\n//\n//          1. Installed / Uninstalled\n//          2. Active (Enable / Disable)\n//          3. Bootstrapped (Load / Unload)\n//          4. Running (Start / Stop)\n//\n\nstatic int safe_exec(char *const argv[], bool suppress_output)\n{\n    pid_t pid;\n    posix_spawn_file_actions_t actions;\n    posix_spawn_file_actions_init(&actions);\n\n    if (suppress_output) {\n        posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, \"/dev/null\", O_WRONLY|O_APPEND, 0);\n        posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, \"/dev/null\", O_WRONLY|O_APPEND, 0);\n    }\n\n    int status = posix_spawn(&pid, argv[0], &actions, NULL, argv, NULL);\n    if (status) return 1;\n\n    while ((waitpid(pid, &status, 0) == -1) && (errno == EINTR)) {\n        usleep(1000);\n    }\n\n    if (WIFSIGNALED(status)) {\n        return 1;\n    } else if (WIFSTOPPED(status)) {\n        return 1;\n    } else {\n        return WEXITSTATUS(status);\n    }\n}\n\nstatic inline char *cfstring_copy(CFStringRef string)\n{\n    CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);\n    char *result = malloc(num_bytes + 1);\n    if (!result) return NULL;\n\n    if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) {\n        free(result);\n        result = NULL;\n    }\n\n    return result;\n}\n\nextern CFURLRef CFCopyHomeDirectoryURLForUser(void *user);\nstatic char *populate_plist_path(void)\n{\n    CFURLRef homeurl_ref = CFCopyHomeDirectoryURLForUser(NULL);\n    CFStringRef home_ref = homeurl_ref ? CFURLCopyFileSystemPath(homeurl_ref, kCFURLPOSIXPathStyle) : NULL;\n    char *home = home_ref ? cfstring_copy(home_ref) : NULL;\n\n    if (!home) {\n        error(\"skhd: unable to retrieve home directory! abort..\\n\");\n    }\n\n    int size = strlen(_PATH_SKHD_PLIST)-2 + strlen(home) + 1;\n    char *result = malloc(size);\n    if (!result) {\n        error(\"skhd: could not allocate memory for plist path! abort..\\n\");\n    }\n\n    memset(result, 0, size);\n    snprintf(result, size, _PATH_SKHD_PLIST, home);\n\n    return result;\n}\n\nstatic char *populate_plist(int *length)\n{\n    char *user = getenv(\"USER\");\n    if (!user) {\n        error(\"skhd: 'env USER' not set! abort..\\n\");\n    }\n\n    char *path_env = getenv(\"PATH\");\n    if (!path_env) {\n        error(\"skhd: 'env PATH' not set! abort..\\n\");\n    }\n\n    char exe_path[4096];\n    unsigned int exe_path_size = sizeof(exe_path);\n    if (_NSGetExecutablePath(exe_path, &exe_path_size) < 0) {\n        error(\"skhd: unable to retrieve path of executable! abort..\\n\");\n    }\n\n    int size = strlen(_SKHD_PLIST)-8 + strlen(exe_path) + strlen(path_env) + (2*strlen(user)) + 1;\n    char *result = malloc(size);\n    if (!result) {\n        error(\"skhd: could not allocate memory for plist contents! abort..\\n\");\n    }\n\n    memset(result, 0, size);\n    snprintf(result, size, _SKHD_PLIST, exe_path, path_env, user, user);\n    *length = size-1;\n\n    return result;\n}\n\n\nstatic inline bool directory_exists(char *filename)\n{\n    struct stat buffer;\n\n    if (stat(filename, &buffer) != 0) {\n        return false;\n    }\n\n    return S_ISDIR(buffer.st_mode);\n}\n\nstatic inline void ensure_directory_exists(char *skhd_plist_path)\n{\n    //\n    // NOTE(asmvik): Temporarily remove filename.\n    // We know the filepath will contain a slash, as\n    // it is controlled by us, so don't bother checking\n    // the result..\n    //\n\n    char *last_slash = strrchr(skhd_plist_path, '/');\n    *last_slash = '\\0';\n\n    if (!directory_exists(skhd_plist_path)) {\n        mkdir(skhd_plist_path, 0755);\n    }\n\n    //\n    // NOTE(asmvik): Restore original filename.\n    //\n\n    *last_slash = '/';\n}\n\nstatic int service_install_internal(char *skhd_plist_path)\n{\n    int skhd_plist_length;\n    char *skhd_plist = populate_plist(&skhd_plist_length);\n    ensure_directory_exists(skhd_plist_path);\n\n    FILE *handle = fopen(skhd_plist_path, \"w\");\n    if (!handle) return 1;\n\n    size_t bytes = fwrite(skhd_plist, skhd_plist_length, 1, handle);\n    int result = bytes == 1 ? 0 : 1;\n    fclose(handle);\n\n    return result;\n}\n\nstatic bool file_exists(char *filename);\n\nstatic int service_install(void)\n{\n    char *skhd_plist_path = populate_plist_path();\n\n    if (file_exists(skhd_plist_path)) {\n        error(\"skhd: service file '%s' is already installed! abort..\\n\", skhd_plist_path);\n    }\n\n    return service_install_internal(skhd_plist_path);\n}\n\nstatic int service_uninstall(void)\n{\n    char *skhd_plist_path = populate_plist_path();\n\n    if (!file_exists(skhd_plist_path)) {\n        error(\"skhd: service file '%s' is not installed! abort..\\n\", skhd_plist_path);\n    }\n\n    return unlink(skhd_plist_path) == 0 ? 0 : 1;\n}\n\nstatic int service_start(void)\n{\n    char *skhd_plist_path = populate_plist_path();\n    if (!file_exists(skhd_plist_path)) {\n        warn(\"skhd: service file '%s' is not installed! attempting installation..\\n\", skhd_plist_path);\n\n        int result = service_install_internal(skhd_plist_path);\n        if (result) {\n            error(\"skhd: service file '%s' could not be installed! abort..\\n\", skhd_plist_path);\n        }\n    }\n\n    char service_target[MAXLEN];\n    snprintf(service_target, sizeof(service_target), \"gui/%d/%s\", getuid(), _NAME_SKHD_PLIST);\n\n    char domain_target[MAXLEN];\n    snprintf(domain_target, sizeof(domain_target), \"gui/%d\", getuid());\n\n    //\n    // NOTE(asmvik): Check if service is bootstrapped\n    //\n\n    const char *const args[] = { _PATH_LAUNCHCTL, \"print\", service_target, NULL };\n    int is_bootstrapped = safe_exec((char *const*)args, true);\n\n    if (is_bootstrapped != 0) {\n\n        //\n        // NOTE(asmvik): Service is not bootstrapped and could be disabled.\n        // There is no way to query if the service is disabled, and we cannot\n        // bootstrap a disabled service. Try to enable the service. This will be\n        // a no-op if the service is already enabled.\n        //\n\n        const char *const args[] = { _PATH_LAUNCHCTL, \"enable\", service_target, NULL };\n        safe_exec((char *const*)args, false);\n\n        //\n        // NOTE(asmvik): Bootstrap service into the target domain.\n        // This will also start the program **iff* RunAtLoad is set to true.\n        //\n\n        const char *const args2[] = { _PATH_LAUNCHCTL, \"bootstrap\", domain_target, skhd_plist_path, NULL };\n        return safe_exec((char *const*)args2, false);\n    } else {\n\n        //\n        // NOTE(asmvik): The service has already been bootstrapped.\n        // Tell the bootstrapped service to launch immediately; it is an\n        // error to bootstrap a service that has already been bootstrapped.\n        //\n\n        const char *const args[] = { _PATH_LAUNCHCTL, \"kickstart\", service_target, NULL };\n        return safe_exec((char *const*)args, false);\n    }\n}\n\nstatic int service_restart(void)\n{\n    char *skhd_plist_path = populate_plist_path();\n    if (!file_exists(skhd_plist_path)) {\n        error(\"skhd: service file '%s' is not installed! abort..\\n\", skhd_plist_path);\n    }\n\n    char service_target[MAXLEN];\n    snprintf(service_target, sizeof(service_target), \"gui/%d/%s\", getuid(), _NAME_SKHD_PLIST);\n\n    const char *const args[] = { _PATH_LAUNCHCTL, \"kickstart\", \"-k\", service_target, NULL };\n    return safe_exec((char *const*)args, false);\n}\n\nstatic int service_stop(void)\n{\n    char *skhd_plist_path = populate_plist_path();\n    if (!file_exists(skhd_plist_path)) {\n        error(\"skhd: service file '%s' is not installed! abort..\\n\", skhd_plist_path);\n    }\n\n    char service_target[MAXLEN];\n    snprintf(service_target, sizeof(service_target), \"gui/%d/%s\", getuid(), _NAME_SKHD_PLIST);\n\n    char domain_target[MAXLEN];\n    snprintf(domain_target, sizeof(domain_target), \"gui/%d\", getuid());\n\n    //\n    // NOTE(asmvik): Check if service is bootstrapped\n    //\n\n    const char *const args[] = { _PATH_LAUNCHCTL, \"print\", service_target, NULL };\n    int is_bootstrapped = safe_exec((char *const*)args, true);\n\n    if (is_bootstrapped != 0) {\n\n        //\n        // NOTE(asmvik): Service is not bootstrapped, but the program\n        // could still be running an instance that was started **while the service\n        // was bootstrapped**, so we tell it to stop said service.\n        //\n\n        const char *const args[] = { _PATH_LAUNCHCTL, \"kill\", \"SIGTERM\", service_target, NULL };\n        return safe_exec((char *const*)args, false);\n    } else {\n\n        //\n        // NOTE(asmvik): Service is bootstrapped; we stop a potentially\n        // running instance of the program and unload the service, making it\n        // not trigger automatically in the future.\n        //\n        // This is NOT the same as disabling the service, which will prevent\n        // it from being boostrapped in the future (without explicitly re-enabling\n        // it first).\n        //\n\n        const char *const args[] = { _PATH_LAUNCHCTL, \"bootout\", domain_target, skhd_plist_path, NULL };\n        return safe_exec((char *const*)args, false);\n    }\n}\n\n#endif\n"
  },
  {
    "path": "src/skhd.c",
    "content": "#include <stdlib.h>\n#include <stdio.h>\n#include <stdbool.h>\n#include <stdarg.h>\n#include <getopt.h>\n#include <signal.h>\n#include <string.h>\n#include <fcntl.h>\n#include <sys/file.h>\n#include <sys/types.h>\n#include <sys/uio.h>\n#include <unistd.h>\n#include <Carbon/Carbon.h>\n#include <CoreFoundation/CoreFoundation.h>\n#include <objc/objc-runtime.h>\n\n#include \"timing.h\"\n#include \"log.h\"\n#define HASHTABLE_IMPLEMENTATION\n#include \"hashtable.h\"\n#include \"sbuffer.h\"\n#include \"hotload.h\"\n#include \"event_tap.h\"\n#include \"locale.h\"\n#include \"carbon.h\"\n#include \"tokenize.h\"\n#include \"parse.h\"\n#include \"hotkey.h\"\n#include \"synthesize.h\"\n#include \"service.h\"\n\n#include \"hotload.c\"\n#include \"event_tap.c\"\n#include \"locale.c\"\n#include \"carbon.c\"\n#include \"tokenize.c\"\n#include \"parse.c\"\n#include \"hotkey.c\"\n#include \"synthesize.c\"\n#include \"notify.c\"\n\nextern void NSApplicationLoad(void);\nextern CFDictionaryRef CGSCopyCurrentSessionDictionary(void);\nextern bool CGSIsSecureEventInputSet(void);\n#define secure_keyboard_entry_enabled CGSIsSecureEventInputSet\n\n#define GLOBAL_CONNECTION_CALLBACK(name) void name(uint32_t type, void *data, size_t data_length, void *context)\ntypedef GLOBAL_CONNECTION_CALLBACK(global_connection_callback);\nextern CGError CGSRegisterNotifyProc(void *handler, uint32_t type, void *context);\n\n#define SKHD_CONFIG_FILE \".skhdrc\"\n#define SKHD_PIDFILE_FMT \"/tmp/skhd_%s.pid\"\n\n#define VERSION_OPT_LONG        \"--version\"\n#define VERSION_OPT_SHRT        \"-v\"\n\n#define SERVICE_INSTALL_OPT     \"--install-service\"\n#define SERVICE_UNINSTALL_OPT   \"--uninstall-service\"\n#define SERVICE_START_OPT       \"--start-service\"\n#define SERVICE_RESTART_OPT     \"--restart-service\"\n#define SERVICE_STOP_OPT        \"--stop-service\"\n\n#define MAJOR  0\n#define MINOR  3\n#define PATCH  9\n\nstatic struct carbon_event carbon;\nstatic struct event_tap event_tap;\nstatic struct hotloader hotloader;\nstatic struct mode *current_mode;\nstatic struct table mode_map;\nstatic struct table blacklst;\nstatic bool thwart_hotloader;\nstatic char config_file[4096];\n\nstatic HOTLOADER_CALLBACK(config_handler);\n\nstatic void\nparse_config_helper(char *absolutepath)\n{\n    struct parser parser;\n    if (parser_init(&parser, &mode_map, &blacklst, absolutepath)) {\n        if (!thwart_hotloader) {\n            hotloader_end(&hotloader);\n            hotloader_add_file(&hotloader, absolutepath);\n        }\n\n        if (parse_config(&parser)) {\n            parser_do_directives(&parser, &hotloader, thwart_hotloader);\n        }\n        parser_destroy(&parser);\n\n        if (!thwart_hotloader) {\n            if (hotloader_begin(&hotloader, config_handler)) {\n                debug(\"skhd: watching files for changes:\\n\", absolutepath);\n                for (int i = 0; i < hotloader.watch_count; ++i) {\n                    debug(\"\\t%s\\n\", hotloader.watch_list[i].file_info.absolutepath);\n                }\n            } else {\n                warn(\"skhd: could not start watcher.. hotloading is not enabled\\n\");\n            }\n        }\n    } else {\n        warn(\"skhd: could not open file '%s'\\n\", absolutepath);\n    }\n\n    current_mode = table_find(&mode_map, \"default\");\n}\n\nstatic HOTLOADER_CALLBACK(config_handler)\n{\n    BEGIN_TIMED_BLOCK(\"hotload_config\");\n    debug(\"skhd: config-file has been modified.. reloading config\\n\");\n    free_mode_map(&mode_map);\n    free_blacklist(&blacklst);\n    parse_config_helper(config_file);\n    END_TIMED_BLOCK();\n}\n\nstatic CF_NOTIFICATION_CALLBACK(keymap_handler)\n{\n    BEGIN_TIMED_BLOCK(\"keymap_changed\");\n    if (initialize_keycode_map()) {\n        debug(\"skhd: input source changed.. reloading config\\n\");\n        free_mode_map(&mode_map);\n        free_blacklist(&blacklst);\n        parse_config_helper(config_file);\n    }\n    END_TIMED_BLOCK();\n}\n\nstatic EVENT_TAP_CALLBACK(key_observer_handler)\n{\n    switch (type) {\n    case kCGEventTapDisabledByTimeout:\n    case kCGEventTapDisabledByUserInput: {\n        debug(\"skhd: restarting event-tap\\n\");\n        struct event_tap *event_tap = (struct event_tap *) reference;\n        CGEventTapEnable(event_tap->handle, 1);\n    } break;\n    case kCGEventKeyDown:\n    case kCGEventFlagsChanged: {\n        uint32_t flags = CGEventGetFlags(event);\n        uint32_t keycode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);\n\n        if (keycode == kVK_ANSI_C && flags & 0x40000) {\n            exit(0);\n        }\n\n        printf(\"\\rkeycode: 0x%.2X\\tflags: \", keycode);\n        for (int i = 31; i >= 0; --i) {\n            printf(\"%c\", (flags & (1 << i)) ? '1' : '0');\n        }\n        fflush(stdout);\n\n        return NULL;\n    } break;\n    }\n    return event;\n}\n\nstatic EVENT_TAP_CALLBACK(key_handler)\n{\n    switch (type) {\n    case kCGEventTapDisabledByTimeout:\n    case kCGEventTapDisabledByUserInput: {\n        debug(\"skhd: restarting event-tap\\n\");\n        struct event_tap *event_tap = (struct event_tap *) reference;\n        CGEventTapEnable(event_tap->handle, 1);\n    } break;\n    case kCGEventKeyDown: {\n        if (table_find(&blacklst, carbon.process_name)) return event;\n        if (!current_mode) return event;\n\n        BEGIN_TIMED_BLOCK(\"handle_keypress\");\n        struct hotkey eventkey = create_eventkey(event);\n        bool result = find_and_exec_hotkey(&eventkey, &mode_map, &current_mode, &carbon);\n        END_TIMED_BLOCK();\n\n        if (result) return NULL;\n    } break;\n    case NX_SYSDEFINED: {\n        if (table_find(&blacklst, carbon.process_name)) return event;\n        if (!current_mode) return event;\n\n        struct hotkey eventkey;\n        if (intercept_systemkey(event, &eventkey)) {\n            bool result = find_and_exec_hotkey(&eventkey, &mode_map, &current_mode, &carbon);\n            if (result) return NULL;\n        }\n    } break;\n    }\n    return event;\n}\n\nstatic void sigusr1_handler(int signal)\n{\n    BEGIN_TIMED_BLOCK(\"sigusr1\");\n    debug(\"skhd: SIGUSR1 received.. reloading config\\n\");\n    free_mode_map(&mode_map);\n    free_blacklist(&blacklst);\n    parse_config_helper(config_file);\n    END_TIMED_BLOCK();\n}\n\nstatic pid_t read_pid_file(void)\n{\n    char pid_file[255] = {};\n    pid_t pid = 0;\n\n    char *user = getenv(\"USER\");\n    if (user) {\n        snprintf(pid_file, sizeof(pid_file), SKHD_PIDFILE_FMT, user);\n    } else {\n        error(\"skhd: could not create path to pid-file because 'env USER' was not set! abort..\\n\");\n    }\n\n    int handle = open(pid_file, O_RDWR);\n    if (handle == -1) {\n        error(\"skhd: could not open pid-file..\\n\");\n    }\n\n    if (flock(handle, LOCK_EX | LOCK_NB) == 0) {\n        error(\"skhd: could not locate existing instance..\\n\");\n    } else if (read(handle, &pid, sizeof(pid_t)) == -1) {\n        error(\"skhd: could not read pid-file..\\n\");\n    }\n\n    close(handle);\n    return pid;\n}\n\nstatic void create_pid_file(void)\n{\n    char pid_file[255] = {};\n    pid_t pid = getpid();\n\n    char *user = getenv(\"USER\");\n    if (user) {\n        snprintf(pid_file, sizeof(pid_file), SKHD_PIDFILE_FMT, user);\n    } else {\n        error(\"skhd: could not create path to pid-file because 'env USER' was not set! abort..\\n\");\n    }\n\n    int handle = open(pid_file, O_CREAT | O_RDWR, 0644);\n    if (handle == -1) {\n        error(\"skhd: could not create pid-file! abort..\\n\");\n    }\n\n    struct flock lockfd = {\n        .l_start  = 0,\n        .l_len    = 0,\n        .l_pid    = pid,\n        .l_type   = F_WRLCK,\n        .l_whence = SEEK_SET\n    };\n\n    if (fcntl(handle, F_SETLK, &lockfd) == -1) {\n        error(\"skhd: could not lock pid-file! abort..\\n\");\n    } else if (write(handle, &pid, sizeof(pid_t)) == -1) {\n        error(\"skhd: could not write pid-file! abort..\\n\");\n    }\n\n    // NOTE(asmvik): we intentionally leave the handle open,\n    // as calling close(..) will release the lock we just acquired.\n\n    debug(\"skhd: successfully created pid-file..\\n\");\n}\n\nstatic inline bool string_equals(const char *a, const char *b)\n{\n    return a && b && strcmp(a, b) == 0;\n}\n\nstatic bool parse_arguments(int argc, char **argv)\n{\n    if ((string_equals(argv[1], VERSION_OPT_LONG)) ||\n        (string_equals(argv[1], VERSION_OPT_SHRT))) {\n        fprintf(stdout, \"skhd-v%d.%d.%d\\n\", MAJOR, MINOR, PATCH);\n        exit(EXIT_SUCCESS);\n    }\n\n    if (string_equals(argv[1], SERVICE_INSTALL_OPT)) {\n        exit(service_install());\n    }\n\n    if (string_equals(argv[1], SERVICE_UNINSTALL_OPT)) {\n        exit(service_uninstall());\n    }\n\n    if (string_equals(argv[1], SERVICE_START_OPT)) {\n        exit(service_start());\n    }\n\n    if (string_equals(argv[1], SERVICE_RESTART_OPT)) {\n        exit(service_restart());\n    }\n\n    if (string_equals(argv[1], SERVICE_STOP_OPT)) {\n        exit(service_stop());\n    }\n\n    int option;\n    const char *short_option = \"VPvc:k:t:rho\";\n    struct option long_option[] = {\n        { \"verbose\", no_argument, NULL, 'V' },\n        { \"profile\", no_argument, NULL, 'P' },\n        { \"config\", required_argument, NULL, 'c' },\n        { \"no-hotload\", no_argument, NULL, 'h' },\n        { \"key\", required_argument, NULL, 'k' },\n        { \"text\", required_argument, NULL, 't' },\n        { \"reload\", no_argument, NULL, 'r' },\n        { \"observe\", no_argument, NULL, 'o' },\n        { NULL, 0, NULL, 0 }\n    };\n\n    while ((option = getopt_long(argc, argv, short_option, long_option, NULL)) != -1) {\n        switch (option) {\n        case 'V': {\n            verbose = true;\n        } break;\n        case 'P': {\n            profile = true;\n        } break;\n        case 'c': {\n            snprintf(config_file, sizeof(config_file), \"%s\", optarg);\n        } break;\n        case 'h': {\n            thwart_hotloader = true;\n        } break;\n        case 'k': {\n            synthesize_key(optarg);\n            return true;\n        } break;\n        case 't': {\n            synthesize_text(optarg);\n            return true;\n        } break;\n        case 'r': {\n            pid_t pid = read_pid_file();\n            if (pid) kill(pid, SIGUSR1);\n            return true;\n        } break;\n        case 'o': {\n            event_tap.mask = (1 << kCGEventKeyDown) |\n                             (1 << kCGEventFlagsChanged);\n            event_tap_begin(&event_tap, key_observer_handler);\n            CFRunLoopRun();\n        } break;\n        }\n    }\n\n    return false;\n}\n\nstatic bool check_privileges(void)\n{\n    bool result;\n    const void *keys[] = { kAXTrustedCheckOptionPrompt };\n    const void *values[] = { kCFBooleanTrue };\n\n    CFDictionaryRef options;\n    options = CFDictionaryCreate(kCFAllocatorDefault,\n                                 keys, values, sizeof(keys) / sizeof(*keys),\n                                 &kCFCopyStringDictionaryKeyCallBacks,\n                                 &kCFTypeDictionaryValueCallBacks);\n\n    result = AXIsProcessTrustedWithOptions(options);\n    CFRelease(options);\n\n    return result;\n}\n\nstatic inline bool file_exists(char *filename)\n{\n    struct stat buffer;\n\n    if (stat(filename, &buffer) != 0) {\n        return false;\n    }\n\n    if (buffer.st_mode & S_IFDIR) {\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool get_config_file(char *restrict filename, char *restrict buffer, int buffer_size)\n{\n    char *xdg_home = getenv(\"XDG_CONFIG_HOME\");\n    if (xdg_home && *xdg_home) {\n        snprintf(buffer, buffer_size, \"%s/skhd/%s\", xdg_home, filename);\n        if (file_exists(buffer)) return true;\n    }\n\n    char *home = getenv(\"HOME\");\n    if (!home) return false;\n\n    snprintf(buffer, buffer_size, \"%s/.config/skhd/%s\", home, filename);\n    if (file_exists(buffer)) return true;\n\n    snprintf(buffer, buffer_size, \"%s/.%s\", home, filename);\n    return file_exists(buffer);\n}\n\nstatic char *secure_keyboard_entry_process_info(pid_t *pid)\n{\n    char *process_name = NULL;\n\n    CFDictionaryRef session = CGSCopyCurrentSessionDictionary();\n    if (!session) return NULL;\n\n    CFNumberRef pid_ref = (CFNumberRef) CFDictionaryGetValue(session, CFSTR(\"kCGSSessionSecureInputPID\"));\n    if (pid_ref) {\n        CFNumberGetValue(pid_ref, CFNumberGetType(pid_ref), pid);\n        process_name = find_process_name_for_pid(*pid);\n    }\n\n    CFRelease(session);\n    return process_name;\n}\n\nstatic void dump_secure_keyboard_entry_process_info(void)\n{\n    pid_t pid;\n    char *process_name = secure_keyboard_entry_process_info(&pid);\n    if (process_name) {\n        error(\"skhd: secure keyboard entry is enabled by (%lld) '%s'! abort..\\n\", pid, process_name);\n    } else {\n        error(\"skhd: secure keyboard entry is enabled! abort..\\n\");\n    }\n}\n\nstatic GLOBAL_CONNECTION_CALLBACK(connection_handler)\n{\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{\n        pid_t pid;\n        char *process_name = secure_keyboard_entry_process_info(&pid);\n\n        if (type == 752) {\n            if (process_name) {\n                notify(\"Secure Keyboard Entry\", \"Enabled by '%s' (%d)\", process_name, pid);\n            } else {\n                notify(\"Secure Keyboard Entry\", \"Enabled by unknown application..\");\n            }\n        } else if (type == 753) {\n            if (process_name) {\n                notify(\"Secure Keyboard Entry\", \"Disabled by '%s' (%d)\", process_name, pid);\n            } else {\n                notify(\"Secure Keyboard Entry\", \"Disabled by unknown application..\");\n            }\n        }\n\n        if (process_name) free(process_name);\n    });\n}\n\nint main(int argc, char **argv)\n{\n    if (getuid() == 0 || geteuid() == 0) {\n        require(\"skhd: running as root is not allowed! abort..\\n\");\n    }\n\n    if (parse_arguments(argc, argv)) {\n        return EXIT_SUCCESS;\n    }\n\n    BEGIN_SCOPED_TIMED_BLOCK(\"total_time\");\n    BEGIN_SCOPED_TIMED_BLOCK(\"init\");\n    create_pid_file();\n\n    if (secure_keyboard_entry_enabled()) {\n        dump_secure_keyboard_entry_process_info();\n    }\n\n    if (!check_privileges()) {\n        require(\"skhd: must be run with accessibility access! abort..\\n\");\n    }\n\n    if (!initialize_keycode_map()) {\n        error(\"skhd: could not initialize keycode map! abort..\\n\");\n    }\n\n    if (!carbon_event_init(&carbon)) {\n        error(\"skhd: could not initialize carbon events! abort..\\n\");\n    }\n\n    if (config_file[0] == 0) {\n        get_config_file(\"skhdrc\", config_file, sizeof(config_file));\n    }\n\n    CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(),\n                                    NULL,\n                                    &keymap_handler,\n                                    kTISNotifySelectedKeyboardInputSourceChanged,\n                                    NULL,\n                                    CFNotificationSuspensionBehaviorCoalesce);\n\n    signal(SIGCHLD, SIG_IGN);\n    signal(SIGUSR1, sigusr1_handler);\n\n    init_shell();\n    table_init(&mode_map, 13, (table_hash_func) hash_string, (table_compare_func) compare_string);\n    table_init(&blacklst, 13, (table_hash_func) hash_string, (table_compare_func) compare_string);\n    END_SCOPED_TIMED_BLOCK();\n\n    BEGIN_SCOPED_TIMED_BLOCK(\"parse_config\");\n    debug(\"skhd: using config '%s'\\n\", config_file);\n    parse_config_helper(config_file);\n    END_SCOPED_TIMED_BLOCK();\n\n    BEGIN_SCOPED_TIMED_BLOCK(\"begin_eventtap\");\n    event_tap.mask = (1 << kCGEventKeyDown) | (1 << NX_SYSDEFINED);\n    event_tap_begin(&event_tap, key_handler);\n    END_SCOPED_TIMED_BLOCK();\n    END_SCOPED_TIMED_BLOCK();\n\n    NSApplicationLoad();\n    notify_init();\n    // CGSRegisterNotifyProc((void*)connection_handler, 752, NULL);\n    // CGSRegisterNotifyProc((void*)connection_handler, 753, NULL);\n\n    CFRunLoopRun();\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "src/synthesize.c",
    "content": "#include <Carbon/Carbon.h>\n\n#include \"synthesize.h\"\n#include \"locale.h\"\n#include \"parse.h\"\n#include \"hotkey.h\"\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\nstatic inline void\ncreate_and_post_keyevent(uint16_t key, bool pressed)\n{\n    CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)key, pressed);\n}\n\nstatic inline void\nsynthesize_modifiers(struct hotkey *hotkey, bool pressed)\n{\n    if (has_flags(hotkey, Hotkey_Flag_Alt)) {\n        create_and_post_keyevent(Modifier_Keycode_Alt, pressed);\n    }\n\n    if (has_flags(hotkey, Hotkey_Flag_Shift)) {\n        create_and_post_keyevent(Modifier_Keycode_Shift, pressed);\n    }\n\n    if (has_flags(hotkey, Hotkey_Flag_Cmd)) {\n        create_and_post_keyevent(Modifier_Keycode_Cmd, pressed);\n    }\n\n    if (has_flags(hotkey, Hotkey_Flag_Control)) {\n        create_and_post_keyevent(Modifier_Keycode_Ctrl, pressed);\n    }\n\n    if (has_flags(hotkey, Hotkey_Flag_Fn)) {\n        create_and_post_keyevent(Modifier_Keycode_Fn, pressed);\n    }\n}\n\nvoid synthesize_key(char *key_string)\n{\n    if (!initialize_keycode_map()) return;\n\n    struct parser parser;\n    parser_init_text(&parser, key_string);\n\n    close(1);\n    close(2);\n\n    struct hotkey *hotkey = parse_keypress(&parser);\n    if (!hotkey) return;\n\n    CGSetLocalEventsSuppressionInterval(0.0f);\n    CGEnableEventStateCombining(false);\n\n    synthesize_modifiers(hotkey, true);\n    create_and_post_keyevent(hotkey->key, true);\n\n    create_and_post_keyevent(hotkey->key, false);\n    synthesize_modifiers(hotkey, false);\n}\n\nvoid synthesize_text(char *text)\n{\n    CFStringRef text_ref = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);\n    CFIndex text_length = CFStringGetLength(text_ref);\n\n    CGEventRef de = CGEventCreateKeyboardEvent(NULL, 0, true);\n    CGEventRef ue = CGEventCreateKeyboardEvent(NULL, 0, false);\n\n    CGEventSetFlags(de, 0);\n    CGEventSetFlags(ue, 0);\n\n    UniChar c;\n    for (CFIndex i = 0; i < text_length; ++i)\n    {\n        c = CFStringGetCharacterAtIndex(text_ref, i);\n        CGEventKeyboardSetUnicodeString(de, 1, &c);\n        CGEventPost(kCGAnnotatedSessionEventTap, de);\n        usleep(1000);\n        CGEventKeyboardSetUnicodeString(ue, 1, &c);\n        CGEventPost(kCGAnnotatedSessionEventTap, ue);\n    }\n\n    CFRelease(ue);\n    CFRelease(de);\n    CFRelease(text_ref);\n}\n\n#pragma clang diagnostic pop\n"
  },
  {
    "path": "src/synthesize.h",
    "content": "#ifndef SKHD_SYNTHESIZE_H\n#define SKHD_SYNTHESIZE_H\n\nvoid synthesize_key(char *key_string);\nvoid synthesize_text(char *text);\n\n#endif\n"
  },
  {
    "path": "src/timing.h",
    "content": "#ifndef MACOS_TIMING_H\n#define MACOS_TIMING_H\n\n#include <stdint.h>\n#include <CoreServices/CoreServices.h>\n#include <mach/mach_time.h>\n\n#define BEGIN_SCOPED_TIMED_BLOCK(note) \\\n    do { \\\n        struct timing_info timing; \\\n        if (profile) begin_timing(&timing, note)\n#define END_SCOPED_TIMED_BLOCK() \\\n        if (profile) end_timing(&timing); \\\n    } while (0)\n\n#define BEGIN_TIMED_BLOCK(note) \\\n    struct timing_info timing; \\\n    if (profile) begin_timing(&timing, note)\n#define END_TIMED_BLOCK() \\\n    if (profile) end_timing(&timing)\n\nstatic bool profile;\n\nstruct timing_info\n{\n    char *note;\n    uint64_t start;\n    uint64_t end;\n    double ms;\n};\n\nvoid begin_timing(struct timing_info *timing, char *note);\nvoid end_timing(struct timing_info *timing);\n\nstatic inline uint64_t\nmacos_get_wall_clock(void)\n{\n    return mach_absolute_time();\n}\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\nstatic inline uint64_t macos_get_nanoseconds_elapsed(uint64_t start, uint64_t end)\n{\n    uint64_t elapsed = end - start;\n    Nanoseconds nano = AbsoluteToNanoseconds(*(AbsoluteTime *) &elapsed);\n    return *(uint64_t *) &nano;\n}\n#pragma clang diagnostic pop\n\nstatic inline double\nmacos_get_milliseconds_elapsed(uint64_t start, uint64_t end)\n{\n    uint64_t ns = macos_get_nanoseconds_elapsed(start, end);\n    return (double)(ns / 1000000.0);\n}\n\nstatic inline double\nmacos_get_seconds_elapsed(uint64_t start, uint64_t end)\n{\n    uint64_t ns = macos_get_nanoseconds_elapsed(start, end);\n    return (double)(ns / 1000000000.0);\n}\n\nvoid begin_timing(struct timing_info *timing, char *note) {\n    timing->note = note;\n    timing->start = macos_get_wall_clock();\n}\n\nvoid end_timing(struct timing_info *timing) {\n    timing->end = macos_get_wall_clock();\n    timing->ms  = macos_get_milliseconds_elapsed(timing->start, timing->end);\n    if (timing->note) {\n        printf(\"%6.4fms (%s)\\n\", timing->ms, timing->note);\n    } else {\n        printf(\"%6.4fms\\n\", timing->ms);\n    }\n}\n\n#endif\n"
  },
  {
    "path": "src/tokenize.c",
    "content": "#include \"tokenize.h\"\n#include <ctype.h>\n\n#define array_count(a) (sizeof((a)) / sizeof(*(a)))\n\nint token_equals(struct token token, const char *match)\n{\n    const char *at = match;\n    for (int i = 0; i < token.length; ++i, ++at) {\n        if ((*at == 0) || (token.text[i] != *at)) {\n            return false;\n        }\n    }\n    return (*at == 0);\n}\n\nstatic void\nadvance(struct tokenizer *tokenizer)\n{\n    if (*tokenizer->at == '\\n') {\n        tokenizer->cursor = 0;\n        ++tokenizer->line;\n    }\n    ++tokenizer->cursor;\n    ++tokenizer->at;\n}\n\nstatic void\neat_whitespace(struct tokenizer *tokenizer)\n{\n    while (*tokenizer->at && isspace(*tokenizer->at)) {\n        advance(tokenizer);\n    }\n}\n\nstatic void\neat_comment(struct tokenizer *tokenizer)\n{\n    while (*tokenizer->at && *tokenizer->at != '\\n') {\n        advance(tokenizer);\n    }\n}\n\nstatic void\neat_command(struct tokenizer *tokenizer)\n{\n    while (*tokenizer->at && *tokenizer->at != '\\n') {\n        if (*tokenizer->at == '\\\\') {\n            advance(tokenizer);\n        }\n        advance(tokenizer);\n    }\n}\n\nstatic void\neat_hex(struct tokenizer *tokenizer)\n{\n    while ((*tokenizer->at) &&\n           ((isdigit(*tokenizer->at)) ||\n            (*tokenizer->at >= 'A' && *tokenizer->at <= 'F'))) {\n        advance(tokenizer);\n    }\n}\n\nstatic void\neat_string(struct tokenizer *tokenizer)\n{\n    /*\n     * NOTE(asmvik): This is NOT proper string parsing code, as we do\n     * not check for escaped '\"' here. At the time of writing, this is only\n     * supposed to be used for parsing names of processes, and such names\n     * should not contain escaped quotes at all. We are lazy and simply do\n     * the most basic implementation that fulfills our current requirement.\n     */\n\n    while (*tokenizer->at && *tokenizer->at != '\"') {\n        advance(tokenizer);\n    }\n}\n\nstatic void\neat_option(struct tokenizer *tokenizer)\n{\n    while (*tokenizer->at && !isspace(*tokenizer->at)) {\n        advance(tokenizer);\n    }\n}\n\nstatic inline bool\nisidentifier(char c)\n{\n    return isalpha(c) || c == '_';\n}\n\nstatic void\neat_identifier(struct tokenizer *tokenizer)\n{\n    while ((*tokenizer->at) && isidentifier(*tokenizer->at)) {\n        advance(tokenizer);\n    }\n\n    while ((*tokenizer->at) && isdigit(*tokenizer->at)) {\n        advance(tokenizer);\n    }\n}\n\nstatic enum token_type\nresolve_identifier_type(struct token token)\n{\n    if (token.length == 1) {\n        return Token_Key;\n    }\n\n    for (int i = 0; i < array_count(modifier_flags_str); ++i) {\n        if (token_equals(token, modifier_flags_str[i])) {\n            return Token_Modifier;\n        }\n    }\n\n    for (int i = 0; i < array_count(literal_keycode_str); ++i) {\n        if (token_equals(token, literal_keycode_str[i])) {\n            return Token_Literal;\n        }\n    }\n\n    return Token_Identifier;\n}\n\nstruct token\npeek_token(struct tokenizer tokenizer)\n{\n    return get_token(&tokenizer);\n}\n\nstruct token\nget_token(struct tokenizer *tokenizer)\n{\n    struct token token;\n    char c;\n\n    eat_whitespace(tokenizer);\n\n    token.length = 1;\n    token.text = tokenizer->at;\n    token.line = tokenizer->line;\n    token.cursor = tokenizer->cursor;\n    c = *token.text;\n    advance(tokenizer);\n\n    switch (c) {\n    case '\\0':{ token.type = Token_EndOfStream; } break;\n    case '+': { token.type = Token_Plus;        } break;\n    case ',': { token.type = Token_Comma;       } break;\n    case '<': { token.type = Token_Insert;      } break;\n    case '@': { token.type = Token_Capture;     } break;\n    case '~': { token.type = Token_Unbound;     } break;\n    case '*': { token.type = Token_Wildcard;    } break;\n    case '[': { token.type = Token_BeginList;   } break;\n    case ']': { token.type = Token_EndList;     } break;\n    case '.': {\n        token.text = tokenizer->at;\n        token.line = tokenizer->line;\n        token.cursor = tokenizer->cursor;\n\n        eat_option(tokenizer);\n        token.length = tokenizer->at - token.text;\n        token.type = Token_Option;\n    } break;\n    case '\"': {\n        token.text = tokenizer->at;\n        token.line = tokenizer->line;\n        token.cursor = tokenizer->cursor;\n\n        eat_string(tokenizer);\n        token.length = tokenizer->at - token.text;\n        token.type = Token_String;\n\n        advance(tokenizer);\n    } break;\n    case '#': {\n        eat_comment(tokenizer);\n        token = get_token(tokenizer);\n    } break;\n    case '-': {\n        if (*tokenizer->at && *tokenizer->at == '>') {\n            advance(tokenizer);\n            token.length = tokenizer->at - token.text;\n            token.type = Token_Arrow;\n        } else {\n            token.type = Token_Dash;\n        }\n    } break;\n    case ';': {\n        eat_whitespace(tokenizer);\n\n        token.text = tokenizer->at;\n        token.line = tokenizer->line;\n        token.cursor = tokenizer->cursor;\n\n        eat_identifier(tokenizer);\n        token.length = tokenizer->at - token.text;\n        token.type = Token_Activate;\n    } break;\n    case ':': {\n        if (*tokenizer->at && *tokenizer->at == ':') {\n            advance(tokenizer);\n            token.length = tokenizer->at - token.text;\n            token.type = Token_Decl;\n        } else {\n            eat_whitespace(tokenizer);\n\n            token.text = tokenizer->at;\n            token.line = tokenizer->line;\n            token.cursor = tokenizer->cursor;\n\n            eat_command(tokenizer);\n            token.length = tokenizer->at - token.text;\n            token.type = Token_Command;\n        }\n    } break;\n    default:  {\n        if (c == '0' && *tokenizer->at == 'x') {\n            advance(tokenizer);\n            eat_hex(tokenizer);\n            token.length = tokenizer->at - token.text;\n            token.type = Token_Key_Hex;\n        } else if (isdigit(c)) {\n            token.type = Token_Key;\n        } else if (isalpha(c)) {\n            eat_identifier(tokenizer);\n            token.length = tokenizer->at - token.text;\n            token.type = resolve_identifier_type(token);\n        } else {\n            token.type = Token_Unknown;\n        }\n    } break;\n    }\n\n    return token;\n}\n\nvoid tokenizer_init(struct tokenizer *tokenizer, char *buffer)\n{\n    tokenizer->buffer = buffer;\n    tokenizer->at = buffer;\n    tokenizer->line = 1;\n    tokenizer->cursor = 1;\n}\n"
  },
  {
    "path": "src/tokenize.h",
    "content": "#ifndef SKHD_TOKENIZE_H\n#define SKHD_TOKENIZE_H\n\nstatic const char *modifier_flags_str[] =\n{\n    \"alt\",   \"lalt\",    \"ralt\",\n    \"shift\", \"lshift\",  \"rshift\",\n    \"cmd\",   \"lcmd\",    \"rcmd\",\n    \"ctrl\",  \"lctrl\",   \"rctrl\",\n    \"fn\",    \"hyper\",   \"meh\",\n};\n\nstatic const char *literal_keycode_str[] =\n{\n    \"return\",          \"tab\",             \"space\",\n    \"backspace\",       \"escape\",          \"delete\",\n    \"home\",            \"end\",             \"pageup\",\n    \"pagedown\",        \"insert\",          \"left\",\n    \"right\",           \"up\",              \"down\",\n    \"f1\",              \"f2\",              \"f3\",\n    \"f4\",              \"f5\",              \"f6\",\n    \"f7\",              \"f8\",              \"f9\",\n    \"f10\",             \"f11\",             \"f12\",\n    \"f13\",             \"f14\",             \"f15\",\n    \"f16\",             \"f17\",             \"f18\",\n    \"f19\",             \"f20\",\n\n    \"sound_up\",        \"sound_down\",      \"mute\",\n    \"play\",            \"previous\",        \"next\",\n    \"rewind\",          \"fast\",            \"brightness_up\",\n    \"brightness_down\", \"illumination_up\", \"illumination_down\"\n};\n\nenum token_type\n{\n    Token_Identifier,\n    Token_Activate,\n\n    Token_Command,\n    Token_Modifier,\n    Token_Literal,\n    Token_Key_Hex,\n    Token_Key,\n\n    Token_Decl,\n    Token_Comma,\n    Token_Insert,\n    Token_Plus,\n    Token_Dash,\n    Token_Arrow,\n    Token_Capture,\n    Token_Unbound,\n    Token_Wildcard,\n    Token_String,\n    Token_Option,\n\n    Token_BeginList,\n    Token_EndList,\n\n    Token_Unknown,\n    Token_EndOfStream,\n};\n\nstruct token\n{\n    enum token_type type;\n    char *text;\n    unsigned length;\n\n    unsigned line;\n    unsigned cursor;\n};\n\nstruct tokenizer\n{\n    char *buffer;\n    char *at;\n    unsigned line;\n    unsigned cursor;\n};\n\nvoid tokenizer_init(struct tokenizer *tokenizer, char *buffer);\nstruct token get_token(struct tokenizer *tokenizer);\nstruct token peek_token(struct tokenizer tokenizer);\nint token_equals(struct token token, const char *match);\n\n#endif\n"
  }
]