Full Code of asmvik/skhd for AI

master a7105d5b3db6 cached
28 files
109.1 KB
28.7k tokens
253 symbols
1 requests
Download .txt
Repository: asmvik/skhd
Branch: master
Commit: a7105d5b3db6
Files: 28
Total size: 109.1 KB

Directory structure:
gitextract_za2cgzfj/

├── .gitignore
├── LICENSE.txt
├── README.md
├── examples/
│   └── skhdrc
├── makefile
└── src/
    ├── carbon.c
    ├── carbon.h
    ├── event_tap.c
    ├── event_tap.h
    ├── hashtable.h
    ├── hotkey.c
    ├── hotkey.h
    ├── hotload.c
    ├── hotload.h
    ├── locale.c
    ├── locale.h
    ├── log.h
    ├── notify.c
    ├── parse.c
    ├── parse.h
    ├── sbuffer.h
    ├── service.h
    ├── skhd.c
    ├── synthesize.c
    ├── synthesize.h
    ├── timing.h
    ├── tokenize.c
    └── tokenize.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
bin/


================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) 2017 Åsmund Vikane

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
> **NOTE**: If you are having issues with skhd, or are looking for feature expansion and future development,  
you 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).  
(I am not personally involved in this re-write.)

**This repository is in maintenance mode; only critical issues that affect the core functionality of the software will be taken care of.** 

**skhd** is a simple hotkey daemon for macOS that focuses on responsiveness and performance.
Hotkeys 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.

**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
a 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
as `/tmp/skhd_$USER.pid` and so the user that is running **skhd** must have write permission to said path.
When running as a service (through launchd) log files can be found at `/tmp/skhd_$USER.out.log` and `/tmp/skhd_$USER.err.log`.

list of features

| feature                    | skhd |
|:--------------------------:|:----:|
| hotload config file        | [x]  |
| hotkey passthrough         | [x]  |
| modal hotkey-system        | [x]  |
| application specific hotkey| [x]  |
| blacklist applications     | [x]  |
| use media-keys as hotkey   | [x]  |
| synthesize a key-press     | [x]  |

### Install

The first time **skhd** is ran, it will request access to the accessibility API.

After access has been granted, the application must be restarted.

*Secure Keyboard Entry* must be disabled for **skhd** to receive key-events.

**Homebrew**:

Requires xcode-8 command-line tools.

      brew install asmvik/formulae/skhd
      skhd --start-service

**Source**:

Requires xcode-8 command-line tools.

      git clone https://github.com/asmvik/skhd
      make install      # release version
      make              # debug version

### Usage

```
--install-service: Install launchd service file into ~/Library/LaunchAgents/com.asmvik.skhd.plist
    skhd --install-service

--uninstall-service: Remove launchd service file ~/Library/LaunchAgents/com.asmvik.skhd.plist
    skhd --uninstall-service

--start-service: Run skhd as a service through launchd
    skhd --start-service

--restart-service: Restart skhd service
    skhd --restart-service

--stop-service: Stop skhd service from running
    skhd --stop-service

-V | --verbose: Output debug information
    skhd -V

-P | --profile: Output profiling information
    skhd -P

-v | --version: Print version number to stdout
    skhd -v

-c | --config: Specify location of config file
    skhd -c ~/.skhdrc

-o | --observe: Output keycode and modifiers of event. Ctrl+C to quit
    skhd -o

-r | --reload: Signal a running instance of skhd to reload its config file
    skhd -r

-h | --no-hotload: Disable system for hotloading config file
    skhd -h

-k | --key: Synthesize a keypress (same syntax as when defining a hotkey)
    skhd -k "shift + alt - 7"

-t | --text: Synthesize a line of text
    skhd -t "hello, worldシ"
```

### Configuration

The default configuration file is located at one of the following places (in order):

 - `$XDG_CONFIG_HOME/skhd/skhdrc`
 - `$HOME/.config/skhd/skhdrc`
 - `$HOME/.skhdrc`

A different location can be specified with the *--config | -c* argument.

A sample config is available [here](https://github.com/asmvik/skhd/blob/master/examples/skhdrc)

A list of all built-in modifier and literal keywords can be found [here](https://github.com/asmvik/skhd/issues/1)

A hotkey is written according to the following rules:
```
hotkey       = <mode> '<' <action> | <action>

mode         = 'name of mode' | <mode> ',' <mode>

action       = <keysym> '[' <proc_map_lst> ']' | <keysym> '->' '[' <proc_map_lst> ']'
               <keysym> ':' <command>          | <keysym> '->' ':' <command>
               <keysym> ';' <mode>             | <keysym> '->' ';' <mode>

keysym       = <mod> '-' <key> | <key>

mod          = 'modifier keyword' | <mod> '+' <mod>

key          = <literal> | <keycode>

literal      = 'single letter or built-in keyword'

keycode      = 'apple keyboard kVK_<Key> values (0x3C)'

proc_map_lst = * <proc_map>

proc_map     = <string> ':' <command> | <string>     '~' |
               '*'      ':' <command> | '*'          '~'

string       = '"' 'sequence of characters' '"'

command      = command is executed through '$SHELL -c' and
               follows valid shell syntax. if the $SHELL environment
               variable is not set, it will default to '/bin/bash'.
               when bash is used, the ';' delimeter can be specified
               to chain commands.

               to allow a command to extend into multiple lines,
               prepend '\' at the end of the previous line.

               an EOL character signifies the end of the bind.

->           = keypress is not consumed by skhd

*            = matches every application not specified in <proc_map_lst>

~            = application is unbound and keypress is forwarded per usual, when specified in a <proc_map>
```

A mode is declared according to the following rules:
```

mode_decl = '::' <name> '@' ':' <command> | '::' <name> ':' <command> |
            '::' <name> '@'               | '::' <name>

name      = desired name for this mode,

@         = capture keypresses regardless of being bound to an action

command  = command is executed through '$SHELL -c' and
           follows valid shell syntax. if the $SHELL environment
           variable is not set, it will default to '/bin/bash'.
           when bash is used, the ';' delimeter can be specified
           to chain commands.

           to allow a command to extend into multiple lines,
           prepend '\' at the end of the previous line.

           an EOL character signifies the end of the bind.
```

General options that configure the behaviour of **skhd**:
```
# specify a file that should be included as an additional config-file.
# treated as an absolutepath if the filename begins with '/' otherwise
# the file is relative to the path of the config-file it was loaded from.

.load "/Users/Koe/.config/partial_skhdrc"
.load "partial_skhdrc"

# prevents skhd from monitoring events for listed processes.

.blacklist [
    "terminal"
    "qutebrowser"
    "kitty"
    "google chrome"
]
```


================================================
FILE: examples/skhdrc
================================================
#  NOTE(asmvik): A list of all built-in modifier and literal keywords can
#                     be found at https://github.com/asmvik/skhd/issues/1
#
#                     A hotkey is written according to the following rules:
#
#                       hotkey       = <mode> '<' <action> | <action>
#
#                       mode         = 'name of mode' | <mode> ',' <mode>
#
#                       action       = <keysym> '[' <proc_map_lst> ']' | <keysym> '->' '[' <proc_map_lst> ']'
#                                      <keysym> ':' <command>          | <keysym> '->' ':' <command>
#                                      <keysym> ';' <mode>             | <keysym> '->' ';' <mode>
#
#                       keysym       = <mod> '-' <key> | <key>
#
#                       mod          = 'modifier keyword' | <mod> '+' <mod>
#
#                       key          = <literal> | <keycode>
#
#                       literal      = 'single letter or built-in keyword'
#
#                       keycode      = 'apple keyboard kVK_<Key> values (0x3C)'
#
#                       proc_map_lst = * <proc_map>
#
#                       proc_map     = <string> ':' <command> | <string>     '~' |
#                                      '*'      ':' <command> | '*'          '~'
#
#                       string       = '"' 'sequence of characters' '"'
#
#                       command      = command is executed through '$SHELL -c' and
#                                      follows valid shell syntax. if the $SHELL environment
#                                      variable is not set, it will default to '/bin/bash'.
#                                      when bash is used, the ';' delimeter can be specified
#                                      to chain commands.
#
#                                      to allow a command to extend into multiple lines,
#                                      prepend '\' at the end of the previous line.
#
#                                      an EOL character signifies the end of the bind.
#
#                       ->           = keypress is not consumed by skhd
#
#                       *            = matches every application not specified in <proc_map_lst>
#
#                       ~            = application is unbound and keypress is forwarded per usual, when specified in a <proc_map>
#
#  NOTE(asmvik): A mode is declared according to the following rules:
#
#                       mode_decl = '::' <name> '@' ':' <command> | '::' <name> ':' <command> |
#                                   '::' <name> '@'               | '::' <name>
#
#                       name      = desired name for this mode,
#
#                       @         = capture keypresses regardless of being bound to an action
#
#                       command   = command is executed through '$SHELL -c' and
#                                   follows valid shell syntax. if the $SHELL environment
#                                   variable is not set, it will default to '/bin/bash'.
#                                   when bash is used, the ';' delimeter can be specified
#                                   to chain commands.
#
#                                   to allow a command to extend into multiple lines,
#                                   prepend '\' at the end of the previous line.
#
#                                   an EOL character signifies the end of the bind.

# add an on_enter command to the default mode
# :: default : yabai -m config active_window_border_color 0xff775759
#
# defines a new mode 'test' with an on_enter command, that captures keypresses
# :: test @ : yabai -m config active_window_border_color 0xff24ccaa
#
# from 'default' mode, activate mode 'test'
# cmd - x ; test
#
# from 'test' mode, activate mode 'default'
# test < cmd - x ; default
#
# launch a new terminal instance when in either 'default' or 'test' mode
# default, test < cmd - return : open -na /Applications/Terminal.app

# application specific bindings
#
# cmd - n [
#     "kitty"       : echo "hello kitty"
#     *             : echo "hello everyone"
#     "qutebrowser" : echo "hello qutebrowser"
#     "terminal"    ~
#     "finder"      : false
# ]

# specify a file that should be included as an additional config-file.
# treated as an absolutepath if the filename begins with '/' otherwise
# the file is relative to the path of the config-file it was loaded from.
#
# .load "/Users/Koe/.config/partial_skhdrc"
# .load "partial_skhdrc"

# prevent skhd from monitoring events for specific applications.
#
# .blacklist [
#    "kitty"
#    "terminal"
#    "qutebrowser"
# ]

# open terminal, blazingly fast compared to iTerm/Hyper
cmd - return : /Applications/kitty.app/Contents/MacOS/kitty --single-instance -d ~

# open qutebrowser
cmd + shift - return : ~/Scripts/qtb.sh

# open mpv
cmd - m : open -na /Applications/mpv.app $(pbpaste)


================================================
FILE: makefile
================================================
FRAMEWORKS     = -framework Cocoa -framework Carbon -framework CoreServices
BUILD_PATH     = ./bin
BUILD_FLAGS    = -std=c99 -Wall -g -O0
SKHD_SRC       = ./src/skhd.c
BINS           = $(BUILD_PATH)/skhd

.PHONY: all clean install

all: clean $(BINS)

install: BUILD_FLAGS=-std=c99 -Wall -O2
install: clean $(BINS)

clean:
	rm -rf $(BUILD_PATH)

$(BUILD_PATH)/skhd: $(SKHD_SRC)
	mkdir -p $(BUILD_PATH)
	clang $^ $(BUILD_FLAGS) $(FRAMEWORKS) -o $@


================================================
FILE: src/carbon.c
================================================
#include "carbon.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
static inline char *
find_process_name_for_psn(ProcessSerialNumber *psn)
{
    CFStringRef process_name_ref;
    if (CopyProcessName(psn, &process_name_ref) == noErr) {
        char *process_name = copy_cfstring(process_name_ref);
        for (char *s = process_name; *s; ++s) *s = tolower(*s);
        CFRelease(process_name_ref);
        return process_name;
    }
    return NULL;
}

inline char *
find_process_name_for_pid(pid_t pid)
{
    ProcessSerialNumber psn;
    GetProcessForPID(pid, &psn);
    return find_process_name_for_psn(&psn);
}

static inline char *
find_active_process_name(void)
{
    ProcessSerialNumber psn;
    GetFrontProcess(&psn);
    return find_process_name_for_psn(&psn);
}
#pragma clang diagnostic pop

static OSStatus
carbon_event_handler(EventHandlerCallRef ref, EventRef event, void *context)
{
    struct carbon_event *carbon = (struct carbon_event *) context;

    ProcessSerialNumber psn;
    if (GetEventParameter(event,
                          kEventParamProcessID,
                          typeProcessSerialNumber,
                          NULL,
                          sizeof(psn),
                          NULL,
                          &psn) != noErr) {
        return -1;
    }

    if (carbon->process_name) {
        free(carbon->process_name);
        carbon->process_name = NULL;
    }

    carbon->process_name = find_process_name_for_psn(&psn);

    return noErr;
}

bool carbon_event_init(struct carbon_event *carbon)
{
    carbon->target = GetApplicationEventTarget();
    carbon->handler = NewEventHandlerUPP(carbon_event_handler);
    carbon->type.eventClass = kEventClassApplication;
    carbon->type.eventKind = kEventAppFrontSwitched;
    carbon->process_name = find_active_process_name();

    return InstallEventHandler(carbon->target,
                               carbon->handler,
                               1,
                               &carbon->type,
                               carbon,
                               &carbon->handler_ref) == noErr;
}


================================================
FILE: src/carbon.h
================================================
#ifndef SKHD_CARBON_H
#define SKHD_CARBON_H

#include <Carbon/Carbon.h>

struct carbon_event
{
    EventTargetRef target;
    EventHandlerUPP handler;
    EventTypeSpec type;
    EventHandlerRef handler_ref;
    char * volatile process_name;
};

char *find_process_name_for_pid(pid_t pid);
bool carbon_event_init(struct carbon_event *carbon);

#endif


================================================
FILE: src/event_tap.c
================================================
#include "event_tap.h"

bool event_tap_enabled(struct event_tap *event_tap)
{
    bool result = (event_tap->handle && CGEventTapIsEnabled(event_tap->handle));
    return result;
}

bool event_tap_begin(struct event_tap *event_tap, event_tap_callback *callback)
{
    event_tap->handle = CGEventTapCreate(kCGSessionEventTap,
                                         kCGHeadInsertEventTap,
                                         kCGEventTapOptionDefault,
                                         event_tap->mask,
                                         callback,
                                         event_tap);

    bool result = event_tap_enabled(event_tap);
    if (result) {
        event_tap->runloop_source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
                                                                  event_tap->handle,
                                                                  0);
        CFRunLoopAddSource(CFRunLoopGetMain(), event_tap->runloop_source, kCFRunLoopCommonModes);
    }

    return result;
}

void event_tap_end(struct event_tap *event_tap)
{
    if (event_tap_enabled(event_tap)) {
        CGEventTapEnable(event_tap->handle, false);
        CFMachPortInvalidate(event_tap->handle);
        CFRunLoopRemoveSource(CFRunLoopGetMain(), event_tap->runloop_source, kCFRunLoopCommonModes);
        CFRelease(event_tap->runloop_source);
        CFRelease(event_tap->handle);
        event_tap->handle = NULL;
    }
}


================================================
FILE: src/event_tap.h
================================================
#ifndef SKHD_EVENT_TAP_H
#define SKHD_EVENT_TAP_H

#include <stdbool.h>
#include <Carbon/Carbon.h>

struct event_tap
{
    CFMachPortRef handle;
    CFRunLoopSourceRef runloop_source;
    CGEventMask mask;
};

#define EVENT_TAP_CALLBACK(name) \
    CGEventRef name(CGEventTapProxy proxy, \
                    CGEventType type, \
                    CGEventRef event, \
                    void *reference)
typedef EVENT_TAP_CALLBACK(event_tap_callback);

bool event_tap_enabled(struct event_tap *event_tap);
bool event_tap_begin(struct event_tap *event_tap, event_tap_callback *callback);
void event_tap_end(struct event_tap *event_tap);

#endif


================================================
FILE: src/hashtable.h
================================================
#ifndef HASHTABLE_H
#define HASHTABLE_H

typedef unsigned long (*table_hash_func)(void *key);
typedef int (*table_compare_func)(void *key_a, void *key_b);

struct bucket
{
    void *key;
    void *value;
    struct bucket *next;
};
struct table
{
    int count;
    int capacity;
    table_hash_func hash;
    table_compare_func compare;
    struct bucket **buckets;
};

void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func compare);
void table_free(struct table *table);

void *table_find(struct table *table, void *key);
void table_add(struct table *table, void *key, void *value);
void *table_remove(struct table *table, void *key);
void *table_reset(struct table *table, int *count);

#endif

#ifdef HASHTABLE_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>

static struct bucket *
table_new_bucket(void *key, void *value)
{
    struct bucket *bucket = malloc(sizeof(struct bucket));
    bucket->key = key;
    bucket->value = value;
    bucket->next = NULL;
    return bucket;
}

static struct bucket **
table_get_bucket(struct table *table, void *key)
{
    struct bucket **bucket = table->buckets + (table->hash(key) % table->capacity);
    while (*bucket) {
        if (table->compare((*bucket)->key, key)) {
            break;
        }
        bucket = &(*bucket)->next;
    }
    return bucket;
}

void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func compare)
{
    table->count = 0;
    table->capacity = capacity;
    table->hash = hash;
    table->compare = compare;
    table->buckets = malloc(sizeof(struct bucket *) * capacity);
    memset(table->buckets, 0, sizeof(struct bucket *) * capacity);
}

void table_free(struct table *table)
{
    for (int i = 0; i < table->capacity; ++i) {
        struct bucket *next, *bucket = table->buckets[i];
        while (bucket) {
            next = bucket->next;
            free(bucket);
            bucket = next;
        }
    }
    if (table->buckets) {
        free(table->buckets);
        table->buckets = NULL;
    }
}

void *table_find(struct table *table, void *key)
{
    struct bucket *bucket = *table_get_bucket(table, key);
    return bucket ? bucket->value : NULL;
}

void table_add(struct table *table, void *key, void *value)
{
    struct bucket **bucket = table_get_bucket(table, key);
    if (*bucket) {
        if (!(*bucket)->value) {
            (*bucket)->value = value;
        }
    } else {
        *bucket = table_new_bucket(key, value);
        ++table->count;
    }
}

void *table_remove(struct table *table, void *key)
{
    void *result = NULL;
    struct bucket *next, **bucket = table_get_bucket(table, key);
    if (*bucket) {
        result = (*bucket)->value;
        next = (*bucket)->next;
        free(*bucket);
        *bucket = next;
        --table->count;
    }
    return result;
}

void *table_reset(struct table *table, int *count)
{
    void **values;
    int capacity;
    int index;
    int item;

    capacity = table->capacity;
    *count = table->count;
    values = malloc(sizeof(void *) * table->count);
    item = 0;

    for (index = 0; index < capacity; ++index) {
        struct bucket *next, **bucket = table->buckets + index;
        while (*bucket) {
            values[item++] = (*bucket)->value;
            next = (*bucket)->next;
            free(*bucket);
            *bucket = next;
            --table->count;
        }
    }

    return values;
}

#undef HASHTABLE_IMPLEMENTATION
#endif


================================================
FILE: src/hotkey.c
================================================
#include "hotkey.h"

#define HOTKEY_FOUND           ((1) << 0)
#define MODE_CAPTURE(a)        ((a) << 1)
#define HOTKEY_PASSTHROUGH(a)  ((a) << 2)

#define LRMOD_ALT   0
#define LRMOD_CMD   6
#define LRMOD_CTRL  9
#define LRMOD_SHIFT 3
#define LMOD_OFFS   1
#define RMOD_OFFS   2

static char arg[] = "-c";
static char *shell = NULL;

static uint32_t cgevent_lrmod_flag[] =
{
    Event_Mask_Alt,     Event_Mask_LAlt,     Event_Mask_RAlt,
    Event_Mask_Shift,   Event_Mask_LShift,   Event_Mask_RShift,
    Event_Mask_Cmd,     Event_Mask_LCmd,     Event_Mask_RCmd,
    Event_Mask_Control, Event_Mask_LControl, Event_Mask_RControl,
};

static uint32_t hotkey_lrmod_flag[] =
{
    Hotkey_Flag_Alt,     Hotkey_Flag_LAlt,     Hotkey_Flag_RAlt,
    Hotkey_Flag_Shift,   Hotkey_Flag_LShift,   Hotkey_Flag_RShift,
    Hotkey_Flag_Cmd,     Hotkey_Flag_LCmd,     Hotkey_Flag_RCmd,
    Hotkey_Flag_Control, Hotkey_Flag_LControl, Hotkey_Flag_RControl,
};

static bool
compare_lr_mod(struct hotkey *a, struct hotkey *b, int mod)
{
    bool result = has_flags(a, hotkey_lrmod_flag[mod])
                ? has_flags(b, hotkey_lrmod_flag[mod + LMOD_OFFS]) ||
                  has_flags(b, hotkey_lrmod_flag[mod + RMOD_OFFS]) ||
                  has_flags(b, hotkey_lrmod_flag[mod])
                : has_flags(a, hotkey_lrmod_flag[mod + LMOD_OFFS]) == has_flags(b, hotkey_lrmod_flag[mod + LMOD_OFFS]) &&
                  has_flags(a, hotkey_lrmod_flag[mod + RMOD_OFFS]) == has_flags(b, hotkey_lrmod_flag[mod + RMOD_OFFS]) &&
                  has_flags(a, hotkey_lrmod_flag[mod])             == has_flags(b, hotkey_lrmod_flag[mod]);
    return result;
}

static bool
compare_fn(struct hotkey *a, struct hotkey *b)
{
    return has_flags(a, Hotkey_Flag_Fn) == has_flags(b, Hotkey_Flag_Fn);
}

static bool
compare_nx(struct hotkey *a, struct hotkey *b)
{
    return has_flags(a, Hotkey_Flag_NX) == has_flags(b, Hotkey_Flag_NX);
}

bool same_hotkey(struct hotkey *a, struct hotkey *b)
{
    return compare_lr_mod(a, b, LRMOD_ALT)   &&
           compare_lr_mod(a, b, LRMOD_CMD)   &&
           compare_lr_mod(a, b, LRMOD_CTRL)  &&
           compare_lr_mod(a, b, LRMOD_SHIFT) &&
           compare_fn(a, b) &&
           compare_nx(a, b) &&
           a->key == b->key;
}

unsigned long hash_hotkey(struct hotkey *a)
{
    return a->key;
}

bool compare_string(char *a, char *b)
{
    while (*a && *b && *a == *b) {
        ++a;
        ++b;
    }
    return *a == '\0' && *b == '\0';
}

unsigned long hash_string(char *key)
{
    unsigned long hash = 0, high;
    while(*key) {
        hash = (hash << 4) + *key++;
        high = hash & 0xF0000000;
        if(high) {
            hash ^= (high >> 24);
        }
        hash &= ~high;
    }
    return hash;
}

static inline void
fork_and_exec(char *command)
{
    int cpid = fork();
    if (cpid == 0) {
        setsid();
        char *exec[] = { shell, arg, command, NULL};
        int status_code = execvp(exec[0], exec);
        exit(status_code);
    }
}

static inline void
passthrough(struct hotkey *hotkey, uint32_t *capture)
{
    *capture |= HOTKEY_PASSTHROUGH((int)has_flags(hotkey, Hotkey_Flag_Passthrough));
}

static inline struct hotkey *
find_hotkey(struct mode *mode, struct hotkey *hotkey, uint32_t *capture)
{
    struct hotkey *result = table_find(&mode->hotkey_map, hotkey);
    if (result) *capture |= HOTKEY_FOUND;
    return result;
}

static inline bool
should_capture_hotkey(uint32_t capture)
{
    if ((capture & HOTKEY_FOUND)) {
        if (!(capture & MODE_CAPTURE(1)) &&
            !(capture & HOTKEY_PASSTHROUGH(1))) {
            return true;
        }

        if (!(capture & HOTKEY_PASSTHROUGH(1)) &&
             (capture & MODE_CAPTURE(1))) {
            return true;
        }

        return false;
    }

    return (capture & MODE_CAPTURE(1));
}

static inline char *
find_process_command_mapping(struct hotkey *hotkey, uint32_t *capture, struct carbon_event *carbon)
{
    char *result = NULL;
    bool found = false;

    for (int i = 0; i < buf_len(hotkey->process_name); ++i) {
        if (same_string(carbon->process_name, hotkey->process_name[i])) {
            result = hotkey->command[i];
            found = true;
            break;
        }
    }

    if (!found) result = hotkey->wildcard_command;
    if (!result) *capture &= ~HOTKEY_FOUND;

    return result;
}

bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m, struct carbon_event *carbon)
{
    uint32_t c = MODE_CAPTURE((int)(*m)->capture);
    for (struct hotkey *h = find_hotkey(*m, k, &c); h; passthrough(h, &c), h = 0) {
        char *cmd = h->command[0];
        if (has_flags(h, Hotkey_Flag_Activate)) {
            *m = table_find(t, cmd);
            cmd = (*m)->command;
        } else if (buf_len(h->process_name) > 0) {
            cmd = find_process_command_mapping(h, &c, carbon);
        }
        if (cmd) fork_and_exec(cmd);
    }
    return should_capture_hotkey(c);
}

void free_mode_map(struct table *mode_map)
{
    struct hotkey **freed_pointers = NULL;

    int mode_count;
    void **modes = table_reset(mode_map, &mode_count);
    for (int mode_index = 0; mode_index < mode_count; ++mode_index) {
        struct mode *mode = (struct mode *) modes[mode_index];

        int hk_count;
        void **hotkeys = table_reset(&mode->hotkey_map, &hk_count);
        for (int hk_index = 0; hk_index < hk_count; ++hk_index) {
            struct hotkey *hotkey = (struct hotkey *) hotkeys[hk_index];

            for (int i = 0; i < buf_len(freed_pointers); ++i) {
                if (freed_pointers[i] == hotkey) {
                    goto next;
                }
            }

            buf_push(freed_pointers, hotkey);
            buf_free(hotkey->mode_list);

            for (int i = 0; i < buf_len(hotkey->process_name); ++i) {
                free(hotkey->process_name[i]);
            }
            buf_free(hotkey->process_name);

            for (int i = 0; i < buf_len(hotkey->command); ++i) {
                free(hotkey->command[i]);
            }
            buf_free(hotkey->command);

            free(hotkey);
next:;
        }

        if (hk_count) free(hotkeys);
        if (mode->command) free(mode->command);
        if (mode->name) free(mode->name);
        free(mode);
    }

    if (mode_count) {
        free(modes);
        buf_free(freed_pointers);
    }
}

void free_blacklist(struct table *blacklst)
{
    int count;
    void **items = table_reset(blacklst, &count);
    for (int index = 0; index < count; ++index) {
        char *item = (char *) items[index];
        free(item);
    }
}

static void
cgevent_lrmod_flag_to_hotkey_lrmod_flag(CGEventFlags eventflags, uint32_t *flags, int mod)
{
    enum osx_event_mask mask  = cgevent_lrmod_flag[mod];
    enum osx_event_mask lmask = cgevent_lrmod_flag[mod + LMOD_OFFS];
    enum osx_event_mask rmask = cgevent_lrmod_flag[mod + RMOD_OFFS];

    if ((eventflags & mask) == mask) {
        bool left  = (eventflags & lmask) == lmask;
        bool right = (eventflags & rmask) == rmask;

        if (left)            *flags |= hotkey_lrmod_flag[mod + LMOD_OFFS];
        if (right)           *flags |= hotkey_lrmod_flag[mod + RMOD_OFFS];
        if (!left && !right) *flags |= hotkey_lrmod_flag[mod];
    }
}

static uint32_t
cgevent_flags_to_hotkey_flags(uint32_t eventflags)
{
    uint32_t flags = 0;

    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_ALT);
    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_CMD);
    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_CTRL);
    cgevent_lrmod_flag_to_hotkey_lrmod_flag(eventflags, &flags, LRMOD_SHIFT);

    if ((eventflags & Event_Mask_Fn) == Event_Mask_Fn) {
        flags |= Hotkey_Flag_Fn;
    }

    return flags;
}

struct hotkey create_eventkey(CGEventRef event)
{
    struct hotkey eventkey = {
        .key = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode),
        .flags = cgevent_flags_to_hotkey_flags(CGEventGetFlags(event))
    };
    return eventkey;
}

bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey)
{
    CFDataRef event_data = CGEventCreateData(kCFAllocatorDefault, event);
    const uint8_t *data  = CFDataGetBytePtr(event_data);
    uint8_t key_code  = data[129];
    uint8_t key_state = data[130];
    uint8_t key_stype = data[123];
    CFRelease(event_data);

    bool result = ((key_state == NX_KEYDOWN) &&
                   (key_stype == NX_SUBTYPE_AUX_CONTROL_BUTTONS));

    if (result) {
        eventkey->key = key_code;
        eventkey->flags = cgevent_flags_to_hotkey_flags(CGEventGetFlags(event)) | Hotkey_Flag_NX;
    }

    return result;
}

void init_shell(void)
{
    if (!shell) {
        char *env_shell = getenv("SHELL");
        shell = env_shell ? env_shell : "/bin/bash";
    }
}


================================================
FILE: src/hotkey.h
================================================
#ifndef SKHD_HOTKEY_H
#define SKHD_HOTKEY_H

#include <Carbon/Carbon.h>
#include <stdint.h>
#include <stdbool.h>

#define Modifier_Keycode_Alt     0x3A
#define Modifier_Keycode_Shift   0x38
#define Modifier_Keycode_Cmd     0x37
#define Modifier_Keycode_Ctrl    0x3B
#define Modifier_Keycode_Fn      0x3F

enum osx_event_mask
{
    Event_Mask_Alt      = 0x00080000,
    Event_Mask_LAlt     = 0x00000020,
    Event_Mask_RAlt     = 0x00000040,
    Event_Mask_Shift    = 0x00020000,
    Event_Mask_LShift   = 0x00000002,
    Event_Mask_RShift   = 0x00000004,
    Event_Mask_Cmd      = 0x00100000,
    Event_Mask_LCmd     = 0x00000008,
    Event_Mask_RCmd     = 0x00000010,
    Event_Mask_Control  = 0x00040000,
    Event_Mask_LControl = 0x00000001,
    Event_Mask_RControl = 0x00002000,
    Event_Mask_Fn       = kCGEventFlagMaskSecondaryFn,
};

enum hotkey_flag
{
    Hotkey_Flag_Alt         = (1 <<  0),
    Hotkey_Flag_LAlt        = (1 <<  1),
    Hotkey_Flag_RAlt        = (1 <<  2),
    Hotkey_Flag_Shift       = (1 <<  3),
    Hotkey_Flag_LShift      = (1 <<  4),
    Hotkey_Flag_RShift      = (1 <<  5),
    Hotkey_Flag_Cmd         = (1 <<  6),
    Hotkey_Flag_LCmd        = (1 <<  7),
    Hotkey_Flag_RCmd        = (1 <<  8),
    Hotkey_Flag_Control     = (1 <<  9),
    Hotkey_Flag_LControl    = (1 << 10),
    Hotkey_Flag_RControl    = (1 << 11),
    Hotkey_Flag_Fn          = (1 << 12),
    Hotkey_Flag_Passthrough = (1 << 13),
    Hotkey_Flag_Activate    = (1 << 14),
    Hotkey_Flag_NX          = (1 << 15),
    Hotkey_Flag_Hyper       = (Hotkey_Flag_Cmd |
                               Hotkey_Flag_Alt |
                               Hotkey_Flag_Shift |
                               Hotkey_Flag_Control),
    Hotkey_Flag_Meh         = (Hotkey_Flag_Control |
                               Hotkey_Flag_Shift |
                               Hotkey_Flag_Alt)
};

#include "hashtable.h"

struct carbon_event;

struct mode
{
    char *name;
    char *command;
    bool capture;
    bool initialized;
    struct table hotkey_map;
};

struct hotkey
{
    uint32_t flags;
    uint32_t key;
    char **process_name;
    char **command;
    char *wildcard_command;
    struct mode **mode_list;
};

static inline void
add_flags(struct hotkey *hotkey, uint32_t flag)
{
    hotkey->flags |= flag;
}

static inline bool
has_flags(struct hotkey *hotkey, uint32_t flag)
{
    bool result = hotkey->flags & flag;
    return result;
}

static inline void
clear_flags(struct hotkey *hotkey, uint32_t flag)
{
    hotkey->flags &= ~flag;
}

bool compare_string(char *a, char *b);
unsigned long hash_string(char *key);

bool same_hotkey(struct hotkey *a, struct hotkey *b);
unsigned long hash_hotkey(struct hotkey *a);

struct hotkey create_eventkey(CGEventRef event);
bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey);

bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m, struct carbon_event *carbon);
void free_mode_map(struct table *mode_map);
void free_blacklist(struct table *blacklst);

void init_shell(void);

#endif


================================================
FILE: src/hotload.c
================================================
#include "hotload.h"
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FSEVENT_CALLBACK(name) void name(ConstFSEventStreamRef stream,\
                                         void *context,\
                                         size_t file_count,\
                                         void *file_paths,\
                                         const FSEventStreamEventFlags *flags,\
                                         const FSEventStreamEventId *ids)

enum watch_kind
{
    WATCH_KIND_INVALID,
    WATCH_KIND_CATALOG,
    WATCH_KIND_FILE
};

struct watched_catalog
{
    char *directory;
    char *extension;
};

struct watched_file
{
    char *absolutepath;
    char *directory;
    char *filename;
};

struct watched_entry
{
    enum watch_kind kind;
    union {
        struct watched_file file_info;
        struct watched_catalog catalog_info;
    };
};

static inline bool
same_string(const char *a, const char *b)
{
    bool result = a && b && strcmp(a, b) == 0;
    return result;
}

static char *
copy_string(const char *s)
{
    unsigned length = strlen(s);
    char *result = (char *) malloc(length + 1);
    memcpy(result, s, length);
    result[length] = '\0';
    return result;
}

static char *
file_directory(char *file)
{
    char *last_slash = strrchr(file, '/');
    *last_slash = '\0';
    char *directory = copy_string(file);
    *last_slash = '/';
    return directory;
}

static char *
file_name(char *file)
{
    char *last_slash = strrchr(file, '/');
    char *name = copy_string(last_slash + 1);
    return name;
}

static char *
resolve_symlink(const char *file)
{
    struct stat buffer;
    if (lstat(file, &buffer) != 0) {
        return NULL;
    }

    if (S_ISDIR(buffer.st_mode)) {
        return copy_string(file);
    }

    if (!S_ISLNK(buffer.st_mode)) {
        return copy_string(file);
    }

    char *result = realpath(file, NULL);
    return result;
}

static enum watch_kind
resolve_watch_kind(char *file)
{
    struct stat buffer;
    if (lstat(file, &buffer) != 0) {
        return WATCH_KIND_INVALID;
    }

    if (S_ISDIR(buffer.st_mode)) {
        return WATCH_KIND_CATALOG;
    }

    if (!S_ISLNK(buffer.st_mode)) {
        return WATCH_KIND_FILE;
    }

    return WATCH_KIND_INVALID;
}

static char *
same_catalog(char *absolutepath, struct watched_catalog *catalog_info)
{
    char *last_slash = strrchr(absolutepath, '/');
    if (!last_slash) return NULL;

    char *filename = NULL;

    // NOTE(koekeisihya): null terminate '/' to cut off filename
    *last_slash = '\0';

    if (same_string(absolutepath, catalog_info->directory)) {
        filename = !catalog_info->extension
                 ? last_slash + 1
                 : same_string(catalog_info->extension, strrchr(last_slash + 1, '.'))
                 ? last_slash + 1
                 : NULL;
    }

    // NOTE(koekeisihya): revert '/' to restore filename
    *last_slash = '/';

    return filename;
}

static inline bool
same_file(char *absolutepath, struct watched_file *file_info)
{
    bool result = same_string(absolutepath, file_info->absolutepath);
    return result;
}

static FSEVENT_CALLBACK(hotloader_handler)
{
    /* NOTE(asmvik): We sometimes get two events upon file save. */
    struct hotloader *hotloader = (struct hotloader *) context;
    char **files = (char **) file_paths;

    for (unsigned file_index = 0; file_index < file_count; ++file_index) {
        for (unsigned watch_index = 0; watch_index < hotloader->watch_count; ++watch_index) {
            struct watched_entry *watch_info = hotloader->watch_list + watch_index;
            if (watch_info->kind == WATCH_KIND_CATALOG) {
                char *filename = same_catalog(files[file_index], &watch_info->catalog_info);
                if (!filename) continue;

                hotloader->callback(files[file_index],
                                    watch_info->catalog_info.directory,
                                    filename);
                break;
            } else if (watch_info->kind == WATCH_KIND_FILE) {
                bool match = same_file(files[file_index], &watch_info->file_info);
                if (!match) continue;

                hotloader->callback(watch_info->file_info.absolutepath,
                                    watch_info->file_info.directory,
                                    watch_info->file_info.filename);
                break;
            }
        }
    }
}

static inline void
hotloader_add_watched_entry(struct hotloader *hotloader, struct watched_entry entry)
{
    if (!hotloader->watch_list) {
        hotloader->watch_capacity = 32;
        hotloader->watch_list = (struct watched_entry *) malloc(hotloader->watch_capacity * sizeof(struct watched_entry));
    }

    if (hotloader->watch_count >= hotloader->watch_capacity) {
        hotloader->watch_capacity = (unsigned) ceil(hotloader->watch_capacity * 1.5f);
        hotloader->watch_list = (struct watched_entry *) realloc(hotloader->watch_list, hotloader->watch_capacity * sizeof(struct watched_entry));
    }

    hotloader->watch_list[hotloader->watch_count++] = entry;
}

bool hotloader_add_catalog(struct hotloader *hotloader, const char *directory, const char *extension)
{
    if (hotloader->enabled) return false;

    char *real_path = resolve_symlink(directory);
    if (!real_path) return false;

    enum watch_kind kind = resolve_watch_kind(real_path);
    if (kind != WATCH_KIND_CATALOG) return false;

    hotloader_add_watched_entry(hotloader, (struct watched_entry) {
        .kind = WATCH_KIND_CATALOG,
        .catalog_info = {
            .directory = real_path,
            .extension = extension
                       ? copy_string(extension)
                       : NULL
        }
    });

    return true;
}

bool hotloader_add_file(struct hotloader *hotloader, const char *file)
{
    if (hotloader->enabled) return false;

    char *real_path = resolve_symlink(file);
    if (!real_path) return false;

    enum watch_kind kind = resolve_watch_kind(real_path);
    if (kind != WATCH_KIND_FILE) return false;

    hotloader_add_watched_entry(hotloader, (struct watched_entry) {
        .kind = WATCH_KIND_FILE,
        .file_info = {
            .absolutepath = real_path,
            .directory = file_directory(real_path),
            .filename = file_name(real_path)
        }
    });

    return true;
}

bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback)
{
    if (hotloader->enabled || !hotloader->watch_count) return false;

    CFStringRef string_refs[hotloader->watch_count];
    for (unsigned index = 0; index < hotloader->watch_count; ++index) {
        struct watched_entry *watch_info = hotloader->watch_list + index;
        char *directory = watch_info->kind == WATCH_KIND_FILE
                        ? watch_info->file_info.directory
                        : watch_info->catalog_info.directory;
        string_refs[index] = CFStringCreateWithCString(kCFAllocatorDefault,
                                                       directory,
                                                       kCFStringEncodingUTF8);
    }

    FSEventStreamContext context = {
        .info = hotloader
    };

    hotloader->path = (CFArrayRef) CFArrayCreate(NULL,
                                                 (const void **) string_refs,
                                                 hotloader->watch_count,
                                                 &kCFTypeArrayCallBacks);

    hotloader->flags = kFSEventStreamCreateFlagNoDefer |
                       kFSEventStreamCreateFlagFileEvents;

    hotloader->stream = FSEventStreamCreate(NULL,
                                            hotloader_handler,
                                            &context,
                                            hotloader->path,
                                            kFSEventStreamEventIdSinceNow,
                                            0.5,
                                            hotloader->flags);

    FSEventStreamScheduleWithRunLoop(hotloader->stream,
                                     CFRunLoopGetMain(),
                                     kCFRunLoopDefaultMode);

    hotloader->callback = callback;
    FSEventStreamStart(hotloader->stream);
    hotloader->enabled = true;

    return true;
}

void hotloader_end(struct hotloader *hotloader)
{
    if (!hotloader->enabled) return;

    FSEventStreamStop(hotloader->stream);
    FSEventStreamInvalidate(hotloader->stream);
    FSEventStreamRelease(hotloader->stream);

    CFIndex count = CFArrayGetCount(hotloader->path);
    for (unsigned index = 0; index < count; ++index) {
        struct watched_entry *watch_info = hotloader->watch_list + index;
        if (watch_info->kind == WATCH_KIND_FILE) {
            free(watch_info->file_info.absolutepath);
            free(watch_info->file_info.directory);
            free(watch_info->file_info.filename);
        } else if (watch_info->kind == WATCH_KIND_CATALOG) {
            free(watch_info->catalog_info.directory);
            if (watch_info->catalog_info.extension) {
                free(watch_info->catalog_info.extension);
            }
        }
        CFRelease(CFArrayGetValueAtIndex(hotloader->path, index));
    }

    CFRelease(hotloader->path);
    free(hotloader->watch_list);
    memset(hotloader, 0, sizeof(struct hotloader));
}


================================================
FILE: src/hotload.h
================================================
#ifndef SKHD_HOTLOAD_H
#define SKHD_HOTLOAD_H

#ifndef __cplusplus
#include <stdbool.h>
#endif

#include <Carbon/Carbon.h>

#define HOTLOADER_CALLBACK(name) void name(char *absolutepath, char *directory, char *filename)
typedef HOTLOADER_CALLBACK(hotloader_callback);

struct watched_entry;
struct hotloader
{
    FSEventStreamEventFlags flags;
    FSEventStreamRef stream;
    CFArrayRef path;
    bool enabled;

    hotloader_callback *callback;
    struct watched_entry *watch_list;
    unsigned watch_capacity;
    unsigned watch_count;
};

bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback);
void hotloader_end(struct hotloader *hotloader);
bool hotloader_add_catalog(struct hotloader *hotloader, const char *directory, const char *extension);
bool hotloader_add_file(struct hotloader *hotloader, const char *file);

#endif


================================================
FILE: src/locale.c
================================================
#include "locale.h"
#include "hashtable.h"
#include "sbuffer.h"
#include <Carbon/Carbon.h>
#include <IOKit/hidsystem/ev_keymap.h>

#define array_count(a) (sizeof((a)) / sizeof(*(a)))

static struct table keymap_table;
static char **keymap_keys;

static char *copy_cfstring(CFStringRef string)
{
    CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
    char *result = malloc(num_bytes + 1);

    // NOTE(asmvik): Boolean: typedef -> unsigned char; false = 0, true != 0
    if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) {
        free(result);
        result = NULL;
    }

    return result;
}

static int hash_keymap(const char *a)
{
    unsigned long hash = 0, high;
    while (*a) {
        hash = (hash << 4) + *a++;
        high = hash & 0xF0000000;
        if (high) {
            hash ^= (high >> 24);
        }
        hash &= ~high;
    }
    return hash;
}

static bool same_keymap(const char *a, const char *b)
{
    while (*a && *b && *a == *b) {
        ++a;
        ++b;
    }
    return *a == '\0' && *b == '\0';
}

static void free_keycode_map(void)
{
    for (int i = 0; i < buf_len(keymap_keys); ++i) {
        free(keymap_keys[i]);
    }

    buf_free(keymap_keys);
    keymap_keys = NULL;
}

static uint32_t layout_dependent_keycodes[] =
{
    kVK_ANSI_A,            kVK_ANSI_B,           kVK_ANSI_C,
    kVK_ANSI_D,            kVK_ANSI_E,           kVK_ANSI_F,
    kVK_ANSI_G,            kVK_ANSI_H,           kVK_ANSI_I,
    kVK_ANSI_J,            kVK_ANSI_K,           kVK_ANSI_L,
    kVK_ANSI_M,            kVK_ANSI_N,           kVK_ANSI_O,
    kVK_ANSI_P,            kVK_ANSI_Q,           kVK_ANSI_R,
    kVK_ANSI_S,            kVK_ANSI_T,           kVK_ANSI_U,
    kVK_ANSI_V,            kVK_ANSI_W,           kVK_ANSI_X,
    kVK_ANSI_Y,            kVK_ANSI_Z,           kVK_ANSI_0,
    kVK_ANSI_1,            kVK_ANSI_2,           kVK_ANSI_3,
    kVK_ANSI_4,            kVK_ANSI_5,           kVK_ANSI_6,
    kVK_ANSI_7,            kVK_ANSI_8,           kVK_ANSI_9,
    kVK_ANSI_Grave,        kVK_ANSI_Equal,       kVK_ANSI_Minus,
    kVK_ANSI_RightBracket, kVK_ANSI_LeftBracket, kVK_ANSI_Quote,
    kVK_ANSI_Semicolon,    kVK_ANSI_Backslash,   kVK_ANSI_Comma,
    kVK_ANSI_Slash,        kVK_ANSI_Period,      kVK_ISO_Section
};

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"
bool initialize_keycode_map(void)
{
    UniChar chars[255];
    UniCharCount len;
    UInt32 state;

    TISInputSourceRef keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
    CFDataRef uchr = (CFDataRef) TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
    CFRelease(keyboard);

    UCKeyboardLayout *keyboard_layout = (UCKeyboardLayout *) CFDataGetBytePtr(uchr);
    if (!keyboard_layout) return false;

    free_keycode_map();
    table_free(&keymap_table);
    table_init(&keymap_table,
               61,
               (table_hash_func) hash_keymap,
               (table_compare_func) same_keymap);

    for (int i = 0; i < array_count(layout_dependent_keycodes); ++i) {
        if (UCKeyTranslate(keyboard_layout,
                           layout_dependent_keycodes[i],
                           kUCKeyActionDown,
                           0,
                           LMGetKbdType(),
                           kUCKeyTranslateNoDeadKeysMask,
                           &state,
                           array_count(chars),
                           &len,
                           chars) == noErr && len > 0) {
            CFStringRef key_cfstring = CFStringCreateWithCharacters(NULL, chars, len);
            char *key_cstring = copy_cfstring(key_cfstring);
            CFRelease(key_cfstring);

            if (key_cstring) {
                table_add(&keymap_table, key_cstring, (void *)layout_dependent_keycodes[i]);
                buf_push(keymap_keys, key_cstring);
            }
        }
    }

    return true;
}
#pragma clang diagnostic pop

uint32_t keycode_from_char(char key)
{
    char lookup_key[] = { key, '\0' };
    uint32_t keycode = (uint32_t) (uintptr_t) table_find(&keymap_table, &lookup_key);
    return keycode;
}


================================================
FILE: src/locale.h
================================================
#ifndef SKHD_LOCALE_H
#define SKHD_LOCALE_H

#include <stdint.h>

#define CF_NOTIFICATION_CALLBACK(name) \
    void name(CFNotificationCenterRef center, \
              void *observer, \
              CFNotificationName name, \
              const void *object, \
              CFDictionaryRef userInfo)
typedef CF_NOTIFICATION_CALLBACK(cf_notification_callback);

bool initialize_keycode_map(void);
uint32_t keycode_from_char(char key);

#endif


================================================
FILE: src/log.h
================================================
#ifndef SKHD_LOG_H
#define SKHD_LOG_H

static bool verbose;

static inline void
debug(const char *format, ...)
{
    if (!verbose) return;

    va_list args;
    va_start(args, format);
    vfprintf(stdout, format, args);
    va_end(args);
}

static inline void
warn(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
}

static inline void
error(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(EXIT_FAILURE);
}

static inline void
require(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(EXIT_SUCCESS);
}

#endif


================================================
FILE: src/notify.c
================================================
void notify_init(void)
{
    class_replaceMethod(objc_getClass("NSBundle"),
                        sel_registerName("bundleIdentifier"),
                        method_getImplementation((void *)^{return CFSTR("com.asmvik.skhd");}),
                        NULL);
}

void notify(const char *subtitle, const char *format, ...)
{
    va_list args;
    va_start(args, format);

    CFStringRef format_ref = CFStringCreateWithCString(NULL, format, kCFStringEncodingUTF8);
    CFStringRef subtitle_ref = CFStringCreateWithCString(NULL, subtitle, kCFStringEncodingUTF8);
    CFStringRef message_ref = CFStringCreateWithFormatAndArguments(NULL, NULL, format_ref, args);

    void *center = ((void * (*)(void *, SEL))objc_msgSend)((void *) objc_getClass("NSUserNotificationCenter"), sel_registerName("defaultUserNotificationCenter"));
    void *notification = ((void * (*)(void *, SEL, SEL))objc_msgSend)((void *) objc_getClass("NSUserNotification"), sel_registerName("alloc"), sel_registerName("init"));

    ((void (*)(void *, SEL, CFStringRef))objc_msgSend)(notification, sel_registerName("setTitle:"), CFSTR("skhd"));
    ((void (*)(void *, SEL, CFStringRef))objc_msgSend)(notification, sel_registerName("setSubtitle:"), subtitle_ref);
    ((void (*)(void *, SEL, CFStringRef))objc_msgSend)(notification, sel_registerName("setInformativeText:"), message_ref);
    ((void (*)(void *, SEL, void *))objc_msgSend)(center, sel_registerName("deliverNotification:"), notification);

    CFRelease(message_ref);
    CFRelease(subtitle_ref);
    CFRelease(format_ref);

    va_end(args);
}


================================================
FILE: src/parse.c
================================================
#include "parse.h"
#include "tokenize.h"
#include "locale.h"
#include "hotkey.h"
#include "hashtable.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>

static struct mode *
find_or_init_default_mode(struct parser *parser)
{
    struct mode *default_mode;

    if ((default_mode = table_find(parser->mode_map, "default"))) {
        return default_mode;
    }

    default_mode = malloc(sizeof(struct mode));
    default_mode->name = copy_string("default");
    default_mode->initialized = false;

    table_init(&default_mode->hotkey_map, 131,
               (table_hash_func) hash_hotkey,
               (table_compare_func) same_hotkey);

    default_mode->capture = false;
    default_mode->command = NULL;
    table_add(parser->mode_map, default_mode->name, default_mode);

    return default_mode;
}

static char *
read_file(const char *file)
{
    unsigned length;
    char *buffer = NULL;
    FILE *handle = fopen(file, "r");

    if (handle) {
        fseek(handle, 0, SEEK_END);
        length = ftell(handle);
        fseek(handle, 0, SEEK_SET);
        buffer = malloc(length + 1);
        fread(buffer, length, 1, handle);
        buffer[length] = '\0';
        fclose(handle);
    }

    return buffer;
}

static char *
copy_string_count(char *s, int length)
{
    char *result = malloc(length + 1);
    memcpy(result, s, length);
    result[length] = '\0';
    return result;
}

static uint32_t
keycode_from_hex(char *hex)
{
    uint32_t result;
    sscanf(hex, "%x", &result);
    return result;
}

static void
parse_command(struct parser *parser, struct hotkey *hotkey)
{
    struct token command = parser_previous(parser);
    char *result = copy_string_count(command.text, command.length);
    debug("\tcmd: '%s'\n", result);
    buf_push(hotkey->command, result);
}

static void
parse_process_command_list(struct parser *parser, struct hotkey *hotkey)
{
    if (parser_match(parser, Token_String)) {
        struct token name_token = parser_previous(parser);
        char *name = copy_string_count(name_token.text, name_token.length);
        for (char *s = name; *s; ++s) *s = tolower(*s);
        buf_push(hotkey->process_name, name);
        if (parser_match(parser, Token_Command)) {
            parse_command(parser, hotkey);
            parse_process_command_list(parser, hotkey);
        } else if (parser_match(parser, Token_Unbound)) {
            buf_push(hotkey->command, NULL);
            parse_process_command_list(parser, hotkey);
        } else {
            parser_report_error(parser, parser_peek(parser), "expected '~' or ':' followed by command\n");
        }
    } else if (parser_match(parser, Token_Wildcard)) {
        if (parser_match(parser, Token_Command)) {
            struct token command = parser_previous(parser);
            char *result = copy_string_count(command.text, command.length);
            debug("\tcmd: '%s'\n", result);
            hotkey->wildcard_command = result;
            parse_process_command_list(parser, hotkey);
        } else if (parser_match(parser, Token_Unbound)) {
            hotkey->wildcard_command = NULL;
            parse_process_command_list(parser, hotkey);
        } else {
            parser_report_error(parser, parser_peek(parser), "expected '~' or ':' followed by command\n");
        }
    } else if (parser_match(parser, Token_EndList)) {
        if (!buf_len(hotkey->process_name)) {
            parser_report_error(parser, parser_previous(parser), "list must contain at least one value\n");
        }
    } else {
        parser_report_error(parser, parser_peek(parser), "expected process command mapping or ']'\n");
    }
}

static void
parse_activate(struct parser *parser, struct hotkey *hotkey)
{
    parse_command(parser, hotkey);
    hotkey->flags |= Hotkey_Flag_Activate;

    if (!table_find(parser->mode_map, hotkey->command[0])) {
        parser_report_error(parser, parser_previous(parser), "undeclared identifier\n");
    }
}

static uint32_t
parse_key_hex(struct parser *parser)
{
    struct token key = parser_previous(parser);
    char *hex = copy_string_count(key.text, key.length);
    uint32_t keycode = keycode_from_hex(hex);
    free(hex);
    debug("\tkey: '%.*s' (0x%02x)\n", key.length, key.text, keycode);
    return keycode;
}

static uint32_t
parse_key(struct parser *parser)
{
    uint32_t keycode;
    struct token key = parser_previous(parser);
    keycode = keycode_from_char(*key.text);
    debug("\tkey: '%c' (0x%02x)\n", *key.text, keycode);
    return keycode;
}

#define KEY_HAS_IMPLICIT_FN_MOD  4
#define KEY_HAS_IMPLICIT_NX_MOD  35
static uint32_t literal_keycode_value[] =
{
    kVK_Return,     kVK_Tab,           kVK_Space,
    kVK_Delete,     kVK_Escape,        kVK_ForwardDelete,
    kVK_Home,       kVK_End,           kVK_PageUp,
    kVK_PageDown,   kVK_Help,          kVK_LeftArrow,
    kVK_RightArrow, kVK_UpArrow,       kVK_DownArrow,
    kVK_F1,         kVK_F2,            kVK_F3,
    kVK_F4,         kVK_F5,            kVK_F6,
    kVK_F7,         kVK_F8,            kVK_F9,
    kVK_F10,        kVK_F11,           kVK_F12,
    kVK_F13,        kVK_F14,           kVK_F15,
    kVK_F16,        kVK_F17,           kVK_F18,
    kVK_F19,        kVK_F20,

    NX_KEYTYPE_SOUND_UP,        NX_KEYTYPE_SOUND_DOWN,      NX_KEYTYPE_MUTE,
    NX_KEYTYPE_PLAY,            NX_KEYTYPE_PREVIOUS,        NX_KEYTYPE_NEXT,
    NX_KEYTYPE_REWIND,          NX_KEYTYPE_FAST,            NX_KEYTYPE_BRIGHTNESS_UP,
    NX_KEYTYPE_BRIGHTNESS_DOWN, NX_KEYTYPE_ILLUMINATION_UP, NX_KEYTYPE_ILLUMINATION_DOWN
};

static inline void
handle_implicit_literal_flags(struct hotkey *hotkey, int literal_index)
{
    if ((literal_index > KEY_HAS_IMPLICIT_FN_MOD) &&
        (literal_index < KEY_HAS_IMPLICIT_NX_MOD)) {
        hotkey->flags |= Hotkey_Flag_Fn;
    } else if (literal_index >= KEY_HAS_IMPLICIT_NX_MOD) {
        hotkey->flags |= Hotkey_Flag_NX;
    }
}

static void
parse_key_literal(struct parser *parser, struct hotkey *hotkey)
{
    struct token key = parser_previous(parser);
    for (int i = 0; i < array_count(literal_keycode_str); ++i) {
        if (token_equals(key, literal_keycode_str[i])) {
            handle_implicit_literal_flags(hotkey, i);
            hotkey->key = literal_keycode_value[i];
            debug("\tkey: '%.*s' (0x%02x)\n", key.length, key.text, hotkey->key);
            break;
        }
    }
}

static enum hotkey_flag modifier_flags_value[] =
{
    Hotkey_Flag_Alt,        Hotkey_Flag_LAlt,       Hotkey_Flag_RAlt,
    Hotkey_Flag_Shift,      Hotkey_Flag_LShift,     Hotkey_Flag_RShift,
    Hotkey_Flag_Cmd,        Hotkey_Flag_LCmd,       Hotkey_Flag_RCmd,
    Hotkey_Flag_Control,    Hotkey_Flag_LControl,   Hotkey_Flag_RControl,
    Hotkey_Flag_Fn,         Hotkey_Flag_Hyper,      Hotkey_Flag_Meh,
};

static uint32_t
parse_modifier(struct parser *parser)
{
    struct token modifier = parser_previous(parser);
    uint32_t flags = 0;

    for (int i = 0; i < array_count(modifier_flags_str); ++i) {
        if (token_equals(modifier, modifier_flags_str[i])) {
            flags |= modifier_flags_value[i];
            debug("\tmod: '%s'\n", modifier_flags_str[i]);
            break;
        }
    }

    if (parser_match(parser, Token_Plus)) {
        if (parser_match(parser, Token_Modifier)) {
            flags |= parse_modifier(parser);
        } else {
            parser_report_error(parser, parser_peek(parser), "expected modifier\n");
        }
    }

    return flags;
}

static void
parse_mode(struct parser *parser, struct hotkey *hotkey)
{
    struct token identifier = parser_previous(parser);

    char *name = copy_string_count(identifier.text, identifier.length);
    struct mode *mode = table_find(parser->mode_map, name);
    free(name);

    if (!mode && token_equals(identifier, "default")) {
        mode = find_or_init_default_mode(parser);
    } else if (!mode) {
        parser_report_error(parser, identifier, "undeclared identifier\n");
        return;
    }

    buf_push(hotkey->mode_list, mode);
    debug("\tmode: '%s'\n", mode->name);

    if (parser_match(parser, Token_Comma)) {
        if (parser_match(parser, Token_Identifier)) {
            parse_mode(parser, hotkey);
        } else {
            parser_report_error(parser, parser_peek(parser), "expected identifier\n");
        }
    }
}

static struct hotkey *
parse_hotkey(struct parser *parser)
{
    struct hotkey *hotkey = malloc(sizeof(struct hotkey));
    memset(hotkey, 0, sizeof(struct hotkey));
    bool found_modifier;

    debug("hotkey :: #%d {\n", parser->current_token.line);

    if (parser_match(parser, Token_Identifier)) {
        parse_mode(parser, hotkey);
        if (parser->error) {
            goto err;
        }
    }

    if (buf_len(hotkey->mode_list) > 0) {
        if (!parser_match(parser, Token_Insert)) {
            parser_report_error(parser, parser_peek(parser), "expected '<'\n");
            goto err;
        }
    } else {
        buf_push(hotkey->mode_list, find_or_init_default_mode(parser));
    }

    if ((found_modifier = parser_match(parser, Token_Modifier))) {
        hotkey->flags = parse_modifier(parser);
        if (parser->error) {
            goto err;
        }
    }

    if (found_modifier) {
        if (!parser_match(parser, Token_Dash)) {
            parser_report_error(parser, parser_peek(parser), "expected '-'\n");
            goto err;
        }
    }

    if (parser_match(parser, Token_Key)) {
        hotkey->key = parse_key(parser);
    } else if (parser_match(parser, Token_Key_Hex)) {
        hotkey->key = parse_key_hex(parser);
    } else if (parser_match(parser, Token_Literal)) {
        parse_key_literal(parser, hotkey);
    } else {
        parser_report_error(parser, parser_peek(parser), "expected key-literal\n");
        goto err;
    }

    if (parser_match(parser, Token_Arrow)) {
        hotkey->flags |= Hotkey_Flag_Passthrough;
    }

    if (parser_match(parser, Token_Command)) {
        parse_command(parser, hotkey);
    } else if (parser_match(parser, Token_BeginList)) {
        parse_process_command_list(parser, hotkey);
        if (parser->error) {
            goto err;
        }
    } else if (parser_match(parser, Token_Activate)) {
        parse_activate(parser, hotkey);
        if (parser->error) {
            goto err;
        }
    } else {
        parser_report_error(parser, parser_peek(parser), "expected ':' followed by command or ';' followed by mode\n");
        goto err;
    }

    debug("}\n");
    return hotkey;

err:
    free(hotkey);
    return NULL;
}

static struct mode *
parse_mode_decl(struct parser *parser)
{
    struct mode *mode = malloc(sizeof(struct mode));
    struct token identifier = parser_previous(parser);

    mode->name = copy_string_count(identifier.text, identifier.length);
    mode->initialized = true;

    table_init(&mode->hotkey_map, 131,
               (table_hash_func) hash_hotkey,
               (table_compare_func) same_hotkey);

    if (parser_match(parser, Token_Capture)) {
        mode->capture = true;
    } else {
        mode->capture = false;
    }

    if (parser_match(parser, Token_Command)) {
        mode->command = copy_string_count(parser->previous_token.text, parser->previous_token.length);
    } else {
        mode->command = NULL;
    }

    return mode;
}

void parse_declaration(struct parser *parser)
{
    parser_match(parser, Token_Decl);
    if (parser_match(parser, Token_Identifier)) {
        struct token identifier = parser_previous(parser);
        struct mode *mode = parse_mode_decl(parser);

        struct mode *existing_mode = table_find(parser->mode_map, mode->name);
        if  (existing_mode) {
            if (same_string(existing_mode->name, "default") && !existing_mode->initialized) {
                existing_mode->initialized = true;
                existing_mode->capture = mode->capture;
                existing_mode->command = mode->command;
            } else {
                parser_report_error(parser, identifier, "duplicate declaration '%s'\n", mode->name);
                if (mode->command) free(mode->command);
            }

            free(mode->name);
            free(mode);
        } else {
            table_add(parser->mode_map, mode->name, mode);
        }
    } else {
        parser_report_error(parser, parser_peek(parser), "expected identifier\n");
    }
}

void parse_option_blacklist(struct parser *parser)
{
    if (parser_match(parser, Token_String)) {
        struct token name_token = parser_previous(parser);
        char *name = copy_string_count(name_token.text, name_token.length);
        for (char *s = name; *s; ++s) *s = tolower(*s);
        debug("\t%s\n", name);
        table_add(parser->blacklst, name, name);
        parse_option_blacklist(parser);
    } else if (parser_match(parser, Token_EndList)) {
        if (parser->blacklst->count == 0) {
            parser_report_error(parser, parser_previous(parser), "list must contain at least one value\n");
        }
    } else {
        parser_report_error(parser, parser_peek(parser), "expected process name or ']'\n");
    }
}

void parse_option_load(struct parser *parser, struct token option)
{
    struct token filename_token = parser_previous(parser);
    char *filename = copy_string_count(filename_token.text, filename_token.length);
    debug("\t%s\n", filename);

    if (*filename != '/') {
        char *directory = file_directory(parser->file);

        size_t directory_length = strlen(directory);
        size_t filename_length  = strlen(filename);
        size_t total_length     = directory_length + filename_length + 2;

        char *absolutepath = malloc(total_length * sizeof(char));
        snprintf(absolutepath, total_length, "%s/%s", directory, filename);
        free(filename);

        filename = absolutepath;
    }

    buf_push(parser->load_directives, ((struct load_directive) {
        .file  = filename,
        .option = option
    }));
}

void parse_option(struct parser *parser)
{
    parser_match(parser, Token_Option);
    struct token option = parser_previous(parser);
    if (token_equals(option, "blacklist")) {
        if (parser_match(parser, Token_BeginList)) {
            debug("blacklist :: #%d {\n", option.line);
            parse_option_blacklist(parser);
            debug("}\n");
        } else {
            parser_report_error(parser, option, "expected '[' followed by list of process names\n");
        }
    } else if (token_equals(option, "load")) {
        if (parser_match(parser, Token_String)) {
            debug("load :: #%d {\n", option.line);
            parse_option_load(parser, option);
            debug("}\n");
        } else {
            parser_report_error(parser, option, "expected filename\n");
        }
    } else {
        parser_report_error(parser, option, "invalid option specified\n");
    }
}

bool parse_config(struct parser *parser)
{
    struct mode *mode;
    struct hotkey *hotkey;

    while (!parser_eof(parser)) {
        if (parser->error) break;

        if ((parser_check(parser, Token_Identifier)) ||
            (parser_check(parser, Token_Modifier)) ||
            (parser_check(parser, Token_Literal)) ||
            (parser_check(parser, Token_Key_Hex)) ||
            (parser_check(parser, Token_Key))) {
            if ((hotkey = parse_hotkey(parser))) {
                for (int i = 0; i < buf_len(hotkey->mode_list); ++i) {
                    mode = hotkey->mode_list[i];
                    table_add(&mode->hotkey_map, hotkey, hotkey);
                }
            }
        } else if (parser_check(parser, Token_Decl)) {
            parse_declaration(parser);
        } else if (parser_check(parser, Token_Option)) {
            parse_option(parser);
        } else {
            parser_report_error(parser, parser_peek(parser), "expected decl, modifier or key-literal\n");
        }
    }

    if (parser->error) {
        free_mode_map(parser->mode_map);
        free_blacklist(parser->blacklst);
        return false;
    }

    return true;
}

struct hotkey *
parse_keypress(struct parser *parser)
{
    if ((parser_check(parser, Token_Modifier)) ||
        (parser_check(parser, Token_Literal)) ||
        (parser_check(parser, Token_Key_Hex)) ||
        (parser_check(parser, Token_Key))) {
        struct hotkey *hotkey = malloc(sizeof(struct hotkey));
        memset(hotkey, 0, sizeof(struct hotkey));
        bool found_modifier;

        if ((found_modifier = parser_match(parser, Token_Modifier))) {
            hotkey->flags = parse_modifier(parser);
            if (parser->error) {
                goto err;
            }
        }

        if (found_modifier) {
            if (!parser_match(parser, Token_Dash)) {
                goto err;
            }
        }

        if (parser_match(parser, Token_Key)) {
            hotkey->key = parse_key(parser);
        } else if (parser_match(parser, Token_Key_Hex)) {
            hotkey->key = parse_key_hex(parser);
        } else if (parser_match(parser, Token_Literal)) {
            parse_key_literal(parser, hotkey);
        } else {
            goto err;
        }

        return hotkey;

    err:
        free(hotkey);
        return NULL;
    }

    return NULL;
}

struct token
parser_peek(struct parser *parser)
{
    return parser->current_token;
}

struct token
parser_previous(struct parser *parser)
{
    return parser->previous_token;
}

bool parser_eof(struct parser *parser)
{
    struct token token = parser_peek(parser);
    return token.type == Token_EndOfStream;
}

struct token
parser_advance(struct parser *parser)
{
    if (!parser_eof(parser)) {
        parser->previous_token = parser->current_token;
        parser->current_token = get_token(&parser->tokenizer);
    }
    return parser_previous(parser);
}

bool parser_check(struct parser *parser, enum token_type type)
{
    if (parser_eof(parser)) return false;
    struct token token = parser_peek(parser);
    return token.type == type;
}

bool parser_match(struct parser *parser, enum token_type type)
{
    if (parser_check(parser, type)) {
        parser_advance(parser);
        return true;
    }
    return false;
}

void parser_report_error(struct parser *parser, struct token token, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    fprintf(stderr, "#%d:%d ", token.line, token.cursor);
    vfprintf(stderr, format, args);
    va_end(args);
    parser->error = true;
}

void parser_do_directives(struct parser *parser, struct hotloader *hotloader, bool thwart_hotloader)
{
    bool error = false;

    for (int i = 0; i < buf_len(parser->load_directives); ++i) {
        struct load_directive load = parser->load_directives[i];

        struct parser directive_parser;
        if (parser_init(&directive_parser, parser->mode_map, parser->blacklst, load.file)) {
            if (!thwart_hotloader) {
                hotloader_add_file(hotloader, load.file);
            }

            if (parse_config(&directive_parser)) {
                parser_do_directives(&directive_parser, hotloader, thwart_hotloader);
            } else {
                error = true;
            }

            parser_destroy(&directive_parser);
        } else {
            warn("skhd: could not open file '%s' from load directive #%d:%d\n", load.file, load.option.line, load.option.cursor);
        }

        free(load.file);
    }
    buf_free(parser->load_directives);

    if (error) {
        free_mode_map(parser->mode_map);
        free_blacklist(parser->blacklst);
    }
}

bool parser_init(struct parser *parser, struct table *mode_map, struct table *blacklst, char *file)
{
    memset(parser, 0, sizeof(struct parser));
    char *buffer = read_file(file);
    if (buffer) {
        parser->file     = file;
        parser->mode_map = mode_map;
        parser->blacklst = blacklst;
        tokenizer_init(&parser->tokenizer, buffer);
        parser_advance(parser);
        return true;
    }
    return false;
}

bool parser_init_text(struct parser *parser, char *text)
{
    memset(parser, 0, sizeof(struct parser));
    tokenizer_init(&parser->tokenizer, text);
    parser_advance(parser);
    return true;
}

void parser_destroy(struct parser *parser)
{
    free(parser->tokenizer.buffer);
}


================================================
FILE: src/parse.h
================================================
#ifndef SKHD_PARSE_H
#define SKHD_PARSE_H

#include "tokenize.h"
#include <stdbool.h>

struct load_directive
{
    char *file;
    struct token option;
};

struct table;
struct parser
{
    char *file;
    struct token previous_token;
    struct token current_token;
    struct tokenizer tokenizer;
    struct table *mode_map;
    struct table *blacklst;
    struct load_directive *load_directives;
    bool error;
};

bool parse_config(struct parser *parser);
struct hotkey *parse_keypress(struct parser *parser);

struct token parser_peek(struct parser *parser);
struct token parser_previous(struct parser *parser);
bool parser_eof(struct parser *parser);
struct token parser_advance(struct parser *parser);
bool parser_check(struct parser *parser, enum token_type type);
bool parser_match(struct parser *parser, enum token_type type);
bool parser_init(struct parser *parser, struct table *mode_map, struct table *blacklst, char *file);
bool parser_init_text(struct parser *parser, char *text);
void parser_destroy(struct parser *parser);
void parser_report_error(struct parser *parser, struct token token, const char *format, ...);

#endif


================================================
FILE: src/sbuffer.h
================================================
#ifndef SBUFFER_H
#define SBUFFER_H

#include <stdlib.h>
#include <stdint.h>

struct buf_hdr
{
    size_t len;
    size_t cap;
    char buf[0];
};

#define buf_MAX(a, b) ((a) > (b) ? (a) : (b))
#define buf_OFFSETOF(t, f) (size_t)((char *)&(((t *)0)->f) - (char *)0)

#define buf__hdr(b) ((struct buf_hdr *)((char *)(b) - buf_OFFSETOF(struct buf_hdr, buf)))
#define buf__should_grow(b, n) (buf_len(b) + (n) >= buf_cap(b))
#define buf__fit(b, n) (buf__should_grow(b, n) ? ((b) = buf__grow_f(b, buf_len(b) + (n), sizeof(*(b)))) : 0)

#define buf_len(b) ((b) ? buf__hdr(b)->len : 0)
#define buf_cap(b) ((b) ? buf__hdr(b)->cap : 0)
#define buf_push(b, x) (buf__fit(b, 1), (b)[buf_len(b)] = (x), buf__hdr(b)->len++)
#define buf_last(b) ((b)[buf_len(b)-1])
#define buf_free(b) ((b) ? free(buf__hdr(b)) : 0)

static void *buf__grow_f(const void *buf, size_t new_len, size_t elem_size)
{
    size_t new_cap = buf_MAX(1 + 2*buf_cap(buf), new_len);
    size_t new_size = buf_OFFSETOF(struct buf_hdr, buf) + new_cap*elem_size;
    struct buf_hdr *new_hdr = realloc(buf ? buf__hdr(buf) : 0, new_size);
    new_hdr->cap = new_cap;
    if (!buf) {
        new_hdr->len = 0;
    }
    return new_hdr->buf;
}

#endif


================================================
FILE: src/service.h
================================================
#ifndef SERVICE_H
#define SERVICE_H

#include <spawn.h>
#include <sys/stat.h>

#define MAXLEN 512

#define _PATH_LAUNCHCTL   "/bin/launchctl"
#define _NAME_SKHD_PLIST "com.asmvik.skhd"
#define _PATH_SKHD_PLIST "%s/Library/LaunchAgents/"_NAME_SKHD_PLIST".plist"

#define _SKHD_PLIST \
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
    "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" \
    "<plist version=\"1.0\">\n" \
    "<dict>\n" \
    "    <key>Label</key>\n" \
    "    <string>"_NAME_SKHD_PLIST"</string>\n" \
    "    <key>ProgramArguments</key>\n" \
    "    <array>\n" \
    "        <string>%s</string>\n" \
    "    </array>\n" \
    "    <key>EnvironmentVariables</key>\n" \
    "    <dict>\n" \
    "        <key>PATH</key>\n" \
    "        <string>%s</string>\n" \
    "    </dict>\n" \
    "    <key>RunAtLoad</key>\n" \
    "    <true/>\n" \
    "    <key>KeepAlive</key>\n" \
    "    <dict>\n" \
    "        <key>SuccessfulExit</key>\n" \
    " 	     <false/>\n" \
    " 	     <key>Crashed</key>\n" \
    " 	     <true/>\n" \
    "    </dict>\n" \
    "    <key>StandardOutPath</key>\n" \
    "    <string>/tmp/skhd_%s.out.log</string>\n" \
    "    <key>StandardErrorPath</key>\n" \
    "    <string>/tmp/skhd_%s.err.log</string>\n" \
    "    <key>ProcessType</key>\n" \
    "    <string>Interactive</string>\n" \
    "    <key>Nice</key>\n" \
    "    <integer>-20</integer>\n" \
    "</dict>\n" \
    "</plist>"

//
// NOTE(asmvik): A launchd service has the following states:
//
//          1. Installed / Uninstalled
//          2. Active (Enable / Disable)
//          3. Bootstrapped (Load / Unload)
//          4. Running (Start / Stop)
//

static int safe_exec(char *const argv[], bool suppress_output)
{
    pid_t pid;
    posix_spawn_file_actions_t actions;
    posix_spawn_file_actions_init(&actions);

    if (suppress_output) {
        posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0);
        posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0);
    }

    int status = posix_spawn(&pid, argv[0], &actions, NULL, argv, NULL);
    if (status) return 1;

    while ((waitpid(pid, &status, 0) == -1) && (errno == EINTR)) {
        usleep(1000);
    }

    if (WIFSIGNALED(status)) {
        return 1;
    } else if (WIFSTOPPED(status)) {
        return 1;
    } else {
        return WEXITSTATUS(status);
    }
}

static inline char *cfstring_copy(CFStringRef string)
{
    CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
    char *result = malloc(num_bytes + 1);
    if (!result) return NULL;

    if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) {
        free(result);
        result = NULL;
    }

    return result;
}

extern CFURLRef CFCopyHomeDirectoryURLForUser(void *user);
static char *populate_plist_path(void)
{
    CFURLRef homeurl_ref = CFCopyHomeDirectoryURLForUser(NULL);
    CFStringRef home_ref = homeurl_ref ? CFURLCopyFileSystemPath(homeurl_ref, kCFURLPOSIXPathStyle) : NULL;
    char *home = home_ref ? cfstring_copy(home_ref) : NULL;

    if (!home) {
        error("skhd: unable to retrieve home directory! abort..\n");
    }

    int size = strlen(_PATH_SKHD_PLIST)-2 + strlen(home) + 1;
    char *result = malloc(size);
    if (!result) {
        error("skhd: could not allocate memory for plist path! abort..\n");
    }

    memset(result, 0, size);
    snprintf(result, size, _PATH_SKHD_PLIST, home);

    return result;
}

static char *populate_plist(int *length)
{
    char *user = getenv("USER");
    if (!user) {
        error("skhd: 'env USER' not set! abort..\n");
    }

    char *path_env = getenv("PATH");
    if (!path_env) {
        error("skhd: 'env PATH' not set! abort..\n");
    }

    char exe_path[4096];
    unsigned int exe_path_size = sizeof(exe_path);
    if (_NSGetExecutablePath(exe_path, &exe_path_size) < 0) {
        error("skhd: unable to retrieve path of executable! abort..\n");
    }

    int size = strlen(_SKHD_PLIST)-8 + strlen(exe_path) + strlen(path_env) + (2*strlen(user)) + 1;
    char *result = malloc(size);
    if (!result) {
        error("skhd: could not allocate memory for plist contents! abort..\n");
    }

    memset(result, 0, size);
    snprintf(result, size, _SKHD_PLIST, exe_path, path_env, user, user);
    *length = size-1;

    return result;
}


static inline bool directory_exists(char *filename)
{
    struct stat buffer;

    if (stat(filename, &buffer) != 0) {
        return false;
    }

    return S_ISDIR(buffer.st_mode);
}

static inline void ensure_directory_exists(char *skhd_plist_path)
{
    //
    // NOTE(asmvik): Temporarily remove filename.
    // We know the filepath will contain a slash, as
    // it is controlled by us, so don't bother checking
    // the result..
    //

    char *last_slash = strrchr(skhd_plist_path, '/');
    *last_slash = '\0';

    if (!directory_exists(skhd_plist_path)) {
        mkdir(skhd_plist_path, 0755);
    }

    //
    // NOTE(asmvik): Restore original filename.
    //

    *last_slash = '/';
}

static int service_install_internal(char *skhd_plist_path)
{
    int skhd_plist_length;
    char *skhd_plist = populate_plist(&skhd_plist_length);
    ensure_directory_exists(skhd_plist_path);

    FILE *handle = fopen(skhd_plist_path, "w");
    if (!handle) return 1;

    size_t bytes = fwrite(skhd_plist, skhd_plist_length, 1, handle);
    int result = bytes == 1 ? 0 : 1;
    fclose(handle);

    return result;
}

static bool file_exists(char *filename);

static int service_install(void)
{
    char *skhd_plist_path = populate_plist_path();

    if (file_exists(skhd_plist_path)) {
        error("skhd: service file '%s' is already installed! abort..\n", skhd_plist_path);
    }

    return service_install_internal(skhd_plist_path);
}

static int service_uninstall(void)
{
    char *skhd_plist_path = populate_plist_path();

    if (!file_exists(skhd_plist_path)) {
        error("skhd: service file '%s' is not installed! abort..\n", skhd_plist_path);
    }

    return unlink(skhd_plist_path) == 0 ? 0 : 1;
}

static int service_start(void)
{
    char *skhd_plist_path = populate_plist_path();
    if (!file_exists(skhd_plist_path)) {
        warn("skhd: service file '%s' is not installed! attempting installation..\n", skhd_plist_path);

        int result = service_install_internal(skhd_plist_path);
        if (result) {
            error("skhd: service file '%s' could not be installed! abort..\n", skhd_plist_path);
        }
    }

    char service_target[MAXLEN];
    snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST);

    char domain_target[MAXLEN];
    snprintf(domain_target, sizeof(domain_target), "gui/%d", getuid());

    //
    // NOTE(asmvik): Check if service is bootstrapped
    //

    const char *const args[] = { _PATH_LAUNCHCTL, "print", service_target, NULL };
    int is_bootstrapped = safe_exec((char *const*)args, true);

    if (is_bootstrapped != 0) {

        //
        // NOTE(asmvik): Service is not bootstrapped and could be disabled.
        // There is no way to query if the service is disabled, and we cannot
        // bootstrap a disabled service. Try to enable the service. This will be
        // a no-op if the service is already enabled.
        //

        const char *const args[] = { _PATH_LAUNCHCTL, "enable", service_target, NULL };
        safe_exec((char *const*)args, false);

        //
        // NOTE(asmvik): Bootstrap service into the target domain.
        // This will also start the program **iff* RunAtLoad is set to true.
        //

        const char *const args2[] = { _PATH_LAUNCHCTL, "bootstrap", domain_target, skhd_plist_path, NULL };
        return safe_exec((char *const*)args2, false);
    } else {

        //
        // NOTE(asmvik): The service has already been bootstrapped.
        // Tell the bootstrapped service to launch immediately; it is an
        // error to bootstrap a service that has already been bootstrapped.
        //

        const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", service_target, NULL };
        return safe_exec((char *const*)args, false);
    }
}

static int service_restart(void)
{
    char *skhd_plist_path = populate_plist_path();
    if (!file_exists(skhd_plist_path)) {
        error("skhd: service file '%s' is not installed! abort..\n", skhd_plist_path);
    }

    char service_target[MAXLEN];
    snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST);

    const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", "-k", service_target, NULL };
    return safe_exec((char *const*)args, false);
}

static int service_stop(void)
{
    char *skhd_plist_path = populate_plist_path();
    if (!file_exists(skhd_plist_path)) {
        error("skhd: service file '%s' is not installed! abort..\n", skhd_plist_path);
    }

    char service_target[MAXLEN];
    snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST);

    char domain_target[MAXLEN];
    snprintf(domain_target, sizeof(domain_target), "gui/%d", getuid());

    //
    // NOTE(asmvik): Check if service is bootstrapped
    //

    const char *const args[] = { _PATH_LAUNCHCTL, "print", service_target, NULL };
    int is_bootstrapped = safe_exec((char *const*)args, true);

    if (is_bootstrapped != 0) {

        //
        // NOTE(asmvik): Service is not bootstrapped, but the program
        // could still be running an instance that was started **while the service
        // was bootstrapped**, so we tell it to stop said service.
        //

        const char *const args[] = { _PATH_LAUNCHCTL, "kill", "SIGTERM", service_target, NULL };
        return safe_exec((char *const*)args, false);
    } else {

        //
        // NOTE(asmvik): Service is bootstrapped; we stop a potentially
        // running instance of the program and unload the service, making it
        // not trigger automatically in the future.
        //
        // This is NOT the same as disabling the service, which will prevent
        // it from being boostrapped in the future (without explicitly re-enabling
        // it first).
        //

        const char *const args[] = { _PATH_LAUNCHCTL, "bootout", domain_target, skhd_plist_path, NULL };
        return safe_exec((char *const*)args, false);
    }
}

#endif


================================================
FILE: src/skhd.c
================================================
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <objc/objc-runtime.h>

#include "timing.h"
#include "log.h"
#define HASHTABLE_IMPLEMENTATION
#include "hashtable.h"
#include "sbuffer.h"
#include "hotload.h"
#include "event_tap.h"
#include "locale.h"
#include "carbon.h"
#include "tokenize.h"
#include "parse.h"
#include "hotkey.h"
#include "synthesize.h"
#include "service.h"

#include "hotload.c"
#include "event_tap.c"
#include "locale.c"
#include "carbon.c"
#include "tokenize.c"
#include "parse.c"
#include "hotkey.c"
#include "synthesize.c"
#include "notify.c"

extern void NSApplicationLoad(void);
extern CFDictionaryRef CGSCopyCurrentSessionDictionary(void);
extern bool CGSIsSecureEventInputSet(void);
#define secure_keyboard_entry_enabled CGSIsSecureEventInputSet

#define GLOBAL_CONNECTION_CALLBACK(name) void name(uint32_t type, void *data, size_t data_length, void *context)
typedef GLOBAL_CONNECTION_CALLBACK(global_connection_callback);
extern CGError CGSRegisterNotifyProc(void *handler, uint32_t type, void *context);

#define SKHD_CONFIG_FILE ".skhdrc"
#define SKHD_PIDFILE_FMT "/tmp/skhd_%s.pid"

#define VERSION_OPT_LONG        "--version"
#define VERSION_OPT_SHRT        "-v"

#define SERVICE_INSTALL_OPT     "--install-service"
#define SERVICE_UNINSTALL_OPT   "--uninstall-service"
#define SERVICE_START_OPT       "--start-service"
#define SERVICE_RESTART_OPT     "--restart-service"
#define SERVICE_STOP_OPT        "--stop-service"

#define MAJOR  0
#define MINOR  3
#define PATCH  9

static struct carbon_event carbon;
static struct event_tap event_tap;
static struct hotloader hotloader;
static struct mode *current_mode;
static struct table mode_map;
static struct table blacklst;
static bool thwart_hotloader;
static char config_file[4096];

static HOTLOADER_CALLBACK(config_handler);

static void
parse_config_helper(char *absolutepath)
{
    struct parser parser;
    if (parser_init(&parser, &mode_map, &blacklst, absolutepath)) {
        if (!thwart_hotloader) {
            hotloader_end(&hotloader);
            hotloader_add_file(&hotloader, absolutepath);
        }

        if (parse_config(&parser)) {
            parser_do_directives(&parser, &hotloader, thwart_hotloader);
        }
        parser_destroy(&parser);

        if (!thwart_hotloader) {
            if (hotloader_begin(&hotloader, config_handler)) {
                debug("skhd: watching files for changes:\n", absolutepath);
                for (int i = 0; i < hotloader.watch_count; ++i) {
                    debug("\t%s\n", hotloader.watch_list[i].file_info.absolutepath);
                }
            } else {
                warn("skhd: could not start watcher.. hotloading is not enabled\n");
            }
        }
    } else {
        warn("skhd: could not open file '%s'\n", absolutepath);
    }

    current_mode = table_find(&mode_map, "default");
}

static HOTLOADER_CALLBACK(config_handler)
{
    BEGIN_TIMED_BLOCK("hotload_config");
    debug("skhd: config-file has been modified.. reloading config\n");
    free_mode_map(&mode_map);
    free_blacklist(&blacklst);
    parse_config_helper(config_file);
    END_TIMED_BLOCK();
}

static CF_NOTIFICATION_CALLBACK(keymap_handler)
{
    BEGIN_TIMED_BLOCK("keymap_changed");
    if (initialize_keycode_map()) {
        debug("skhd: input source changed.. reloading config\n");
        free_mode_map(&mode_map);
        free_blacklist(&blacklst);
        parse_config_helper(config_file);
    }
    END_TIMED_BLOCK();
}

static EVENT_TAP_CALLBACK(key_observer_handler)
{
    switch (type) {
    case kCGEventTapDisabledByTimeout:
    case kCGEventTapDisabledByUserInput: {
        debug("skhd: restarting event-tap\n");
        struct event_tap *event_tap = (struct event_tap *) reference;
        CGEventTapEnable(event_tap->handle, 1);
    } break;
    case kCGEventKeyDown:
    case kCGEventFlagsChanged: {
        uint32_t flags = CGEventGetFlags(event);
        uint32_t keycode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

        if (keycode == kVK_ANSI_C && flags & 0x40000) {
            exit(0);
        }

        printf("\rkeycode: 0x%.2X\tflags: ", keycode);
        for (int i = 31; i >= 0; --i) {
            printf("%c", (flags & (1 << i)) ? '1' : '0');
        }
        fflush(stdout);

        return NULL;
    } break;
    }
    return event;
}

static EVENT_TAP_CALLBACK(key_handler)
{
    switch (type) {
    case kCGEventTapDisabledByTimeout:
    case kCGEventTapDisabledByUserInput: {
        debug("skhd: restarting event-tap\n");
        struct event_tap *event_tap = (struct event_tap *) reference;
        CGEventTapEnable(event_tap->handle, 1);
    } break;
    case kCGEventKeyDown: {
        if (table_find(&blacklst, carbon.process_name)) return event;
        if (!current_mode) return event;

        BEGIN_TIMED_BLOCK("handle_keypress");
        struct hotkey eventkey = create_eventkey(event);
        bool result = find_and_exec_hotkey(&eventkey, &mode_map, &current_mode, &carbon);
        END_TIMED_BLOCK();

        if (result) return NULL;
    } break;
    case NX_SYSDEFINED: {
        if (table_find(&blacklst, carbon.process_name)) return event;
        if (!current_mode) return event;

        struct hotkey eventkey;
        if (intercept_systemkey(event, &eventkey)) {
            bool result = find_and_exec_hotkey(&eventkey, &mode_map, &current_mode, &carbon);
            if (result) return NULL;
        }
    } break;
    }
    return event;
}

static void sigusr1_handler(int signal)
{
    BEGIN_TIMED_BLOCK("sigusr1");
    debug("skhd: SIGUSR1 received.. reloading config\n");
    free_mode_map(&mode_map);
    free_blacklist(&blacklst);
    parse_config_helper(config_file);
    END_TIMED_BLOCK();
}

static pid_t read_pid_file(void)
{
    char pid_file[255] = {};
    pid_t pid = 0;

    char *user = getenv("USER");
    if (user) {
        snprintf(pid_file, sizeof(pid_file), SKHD_PIDFILE_FMT, user);
    } else {
        error("skhd: could not create path to pid-file because 'env USER' was not set! abort..\n");
    }

    int handle = open(pid_file, O_RDWR);
    if (handle == -1) {
        error("skhd: could not open pid-file..\n");
    }

    if (flock(handle, LOCK_EX | LOCK_NB) == 0) {
        error("skhd: could not locate existing instance..\n");
    } else if (read(handle, &pid, sizeof(pid_t)) == -1) {
        error("skhd: could not read pid-file..\n");
    }

    close(handle);
    return pid;
}

static void create_pid_file(void)
{
    char pid_file[255] = {};
    pid_t pid = getpid();

    char *user = getenv("USER");
    if (user) {
        snprintf(pid_file, sizeof(pid_file), SKHD_PIDFILE_FMT, user);
    } else {
        error("skhd: could not create path to pid-file because 'env USER' was not set! abort..\n");
    }

    int handle = open(pid_file, O_CREAT | O_RDWR, 0644);
    if (handle == -1) {
        error("skhd: could not create pid-file! abort..\n");
    }

    struct flock lockfd = {
        .l_start  = 0,
        .l_len    = 0,
        .l_pid    = pid,
        .l_type   = F_WRLCK,
        .l_whence = SEEK_SET
    };

    if (fcntl(handle, F_SETLK, &lockfd) == -1) {
        error("skhd: could not lock pid-file! abort..\n");
    } else if (write(handle, &pid, sizeof(pid_t)) == -1) {
        error("skhd: could not write pid-file! abort..\n");
    }

    // NOTE(asmvik): we intentionally leave the handle open,
    // as calling close(..) will release the lock we just acquired.

    debug("skhd: successfully created pid-file..\n");
}

static inline bool string_equals(const char *a, const char *b)
{
    return a && b && strcmp(a, b) == 0;
}

static bool parse_arguments(int argc, char **argv)
{
    if ((string_equals(argv[1], VERSION_OPT_LONG)) ||
        (string_equals(argv[1], VERSION_OPT_SHRT))) {
        fprintf(stdout, "skhd-v%d.%d.%d\n", MAJOR, MINOR, PATCH);
        exit(EXIT_SUCCESS);
    }

    if (string_equals(argv[1], SERVICE_INSTALL_OPT)) {
        exit(service_install());
    }

    if (string_equals(argv[1], SERVICE_UNINSTALL_OPT)) {
        exit(service_uninstall());
    }

    if (string_equals(argv[1], SERVICE_START_OPT)) {
        exit(service_start());
    }

    if (string_equals(argv[1], SERVICE_RESTART_OPT)) {
        exit(service_restart());
    }

    if (string_equals(argv[1], SERVICE_STOP_OPT)) {
        exit(service_stop());
    }

    int option;
    const char *short_option = "VPvc:k:t:rho";
    struct option long_option[] = {
        { "verbose", no_argument, NULL, 'V' },
        { "profile", no_argument, NULL, 'P' },
        { "config", required_argument, NULL, 'c' },
        { "no-hotload", no_argument, NULL, 'h' },
        { "key", required_argument, NULL, 'k' },
        { "text", required_argument, NULL, 't' },
        { "reload", no_argument, NULL, 'r' },
        { "observe", no_argument, NULL, 'o' },
        { NULL, 0, NULL, 0 }
    };

    while ((option = getopt_long(argc, argv, short_option, long_option, NULL)) != -1) {
        switch (option) {
        case 'V': {
            verbose = true;
        } break;
        case 'P': {
            profile = true;
        } break;
        case 'c': {
            snprintf(config_file, sizeof(config_file), "%s", optarg);
        } break;
        case 'h': {
            thwart_hotloader = true;
        } break;
        case 'k': {
            synthesize_key(optarg);
            return true;
        } break;
        case 't': {
            synthesize_text(optarg);
            return true;
        } break;
        case 'r': {
            pid_t pid = read_pid_file();
            if (pid) kill(pid, SIGUSR1);
            return true;
        } break;
        case 'o': {
            event_tap.mask = (1 << kCGEventKeyDown) |
                             (1 << kCGEventFlagsChanged);
            event_tap_begin(&event_tap, key_observer_handler);
            CFRunLoopRun();
        } break;
        }
    }

    return false;
}

static bool check_privileges(void)
{
    bool result;
    const void *keys[] = { kAXTrustedCheckOptionPrompt };
    const void *values[] = { kCFBooleanTrue };

    CFDictionaryRef options;
    options = CFDictionaryCreate(kCFAllocatorDefault,
                                 keys, values, sizeof(keys) / sizeof(*keys),
                                 &kCFCopyStringDictionaryKeyCallBacks,
                                 &kCFTypeDictionaryValueCallBacks);

    result = AXIsProcessTrustedWithOptions(options);
    CFRelease(options);

    return result;
}

static inline bool file_exists(char *filename)
{
    struct stat buffer;

    if (stat(filename, &buffer) != 0) {
        return false;
    }

    if (buffer.st_mode & S_IFDIR) {
        return false;
    }

    return true;
}

static bool get_config_file(char *restrict filename, char *restrict buffer, int buffer_size)
{
    char *xdg_home = getenv("XDG_CONFIG_HOME");
    if (xdg_home && *xdg_home) {
        snprintf(buffer, buffer_size, "%s/skhd/%s", xdg_home, filename);
        if (file_exists(buffer)) return true;
    }

    char *home = getenv("HOME");
    if (!home) return false;

    snprintf(buffer, buffer_size, "%s/.config/skhd/%s", home, filename);
    if (file_exists(buffer)) return true;

    snprintf(buffer, buffer_size, "%s/.%s", home, filename);
    return file_exists(buffer);
}

static char *secure_keyboard_entry_process_info(pid_t *pid)
{
    char *process_name = NULL;

    CFDictionaryRef session = CGSCopyCurrentSessionDictionary();
    if (!session) return NULL;

    CFNumberRef pid_ref = (CFNumberRef) CFDictionaryGetValue(session, CFSTR("kCGSSessionSecureInputPID"));
    if (pid_ref) {
        CFNumberGetValue(pid_ref, CFNumberGetType(pid_ref), pid);
        process_name = find_process_name_for_pid(*pid);
    }

    CFRelease(session);
    return process_name;
}

static void dump_secure_keyboard_entry_process_info(void)
{
    pid_t pid;
    char *process_name = secure_keyboard_entry_process_info(&pid);
    if (process_name) {
        error("skhd: secure keyboard entry is enabled by (%lld) '%s'! abort..\n", pid, process_name);
    } else {
        error("skhd: secure keyboard entry is enabled! abort..\n");
    }
}

static GLOBAL_CONNECTION_CALLBACK(connection_handler)
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        pid_t pid;
        char *process_name = secure_keyboard_entry_process_info(&pid);

        if (type == 752) {
            if (process_name) {
                notify("Secure Keyboard Entry", "Enabled by '%s' (%d)", process_name, pid);
            } else {
                notify("Secure Keyboard Entry", "Enabled by unknown application..");
            }
        } else if (type == 753) {
            if (process_name) {
                notify("Secure Keyboard Entry", "Disabled by '%s' (%d)", process_name, pid);
            } else {
                notify("Secure Keyboard Entry", "Disabled by unknown application..");
            }
        }

        if (process_name) free(process_name);
    });
}

int main(int argc, char **argv)
{
    if (getuid() == 0 || geteuid() == 0) {
        require("skhd: running as root is not allowed! abort..\n");
    }

    if (parse_arguments(argc, argv)) {
        return EXIT_SUCCESS;
    }

    BEGIN_SCOPED_TIMED_BLOCK("total_time");
    BEGIN_SCOPED_TIMED_BLOCK("init");
    create_pid_file();

    if (secure_keyboard_entry_enabled()) {
        dump_secure_keyboard_entry_process_info();
    }

    if (!check_privileges()) {
        require("skhd: must be run with accessibility access! abort..\n");
    }

    if (!initialize_keycode_map()) {
        error("skhd: could not initialize keycode map! abort..\n");
    }

    if (!carbon_event_init(&carbon)) {
        error("skhd: could not initialize carbon events! abort..\n");
    }

    if (config_file[0] == 0) {
        get_config_file("skhdrc", config_file, sizeof(config_file));
    }

    CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(),
                                    NULL,
                                    &keymap_handler,
                                    kTISNotifySelectedKeyboardInputSourceChanged,
                                    NULL,
                                    CFNotificationSuspensionBehaviorCoalesce);

    signal(SIGCHLD, SIG_IGN);
    signal(SIGUSR1, sigusr1_handler);

    init_shell();
    table_init(&mode_map, 13, (table_hash_func) hash_string, (table_compare_func) compare_string);
    table_init(&blacklst, 13, (table_hash_func) hash_string, (table_compare_func) compare_string);
    END_SCOPED_TIMED_BLOCK();

    BEGIN_SCOPED_TIMED_BLOCK("parse_config");
    debug("skhd: using config '%s'\n", config_file);
    parse_config_helper(config_file);
    END_SCOPED_TIMED_BLOCK();

    BEGIN_SCOPED_TIMED_BLOCK("begin_eventtap");
    event_tap.mask = (1 << kCGEventKeyDown) | (1 << NX_SYSDEFINED);
    event_tap_begin(&event_tap, key_handler);
    END_SCOPED_TIMED_BLOCK();
    END_SCOPED_TIMED_BLOCK();

    NSApplicationLoad();
    notify_init();
    // CGSRegisterNotifyProc((void*)connection_handler, 752, NULL);
    // CGSRegisterNotifyProc((void*)connection_handler, 753, NULL);

    CFRunLoopRun();
    return EXIT_SUCCESS;
}


================================================
FILE: src/synthesize.c
================================================
#include <Carbon/Carbon.h>

#include "synthesize.h"
#include "locale.h"
#include "parse.h"
#include "hotkey.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"

static inline void
create_and_post_keyevent(uint16_t key, bool pressed)
{
    CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)key, pressed);
}

static inline void
synthesize_modifiers(struct hotkey *hotkey, bool pressed)
{
    if (has_flags(hotkey, Hotkey_Flag_Alt)) {
        create_and_post_keyevent(Modifier_Keycode_Alt, pressed);
    }

    if (has_flags(hotkey, Hotkey_Flag_Shift)) {
        create_and_post_keyevent(Modifier_Keycode_Shift, pressed);
    }

    if (has_flags(hotkey, Hotkey_Flag_Cmd)) {
        create_and_post_keyevent(Modifier_Keycode_Cmd, pressed);
    }

    if (has_flags(hotkey, Hotkey_Flag_Control)) {
        create_and_post_keyevent(Modifier_Keycode_Ctrl, pressed);
    }

    if (has_flags(hotkey, Hotkey_Flag_Fn)) {
        create_and_post_keyevent(Modifier_Keycode_Fn, pressed);
    }
}

void synthesize_key(char *key_string)
{
    if (!initialize_keycode_map()) return;

    struct parser parser;
    parser_init_text(&parser, key_string);

    close(1);
    close(2);

    struct hotkey *hotkey = parse_keypress(&parser);
    if (!hotkey) return;

    CGSetLocalEventsSuppressionInterval(0.0f);
    CGEnableEventStateCombining(false);

    synthesize_modifiers(hotkey, true);
    create_and_post_keyevent(hotkey->key, true);

    create_and_post_keyevent(hotkey->key, false);
    synthesize_modifiers(hotkey, false);
}

void synthesize_text(char *text)
{
    CFStringRef text_ref = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
    CFIndex text_length = CFStringGetLength(text_ref);

    CGEventRef de = CGEventCreateKeyboardEvent(NULL, 0, true);
    CGEventRef ue = CGEventCreateKeyboardEvent(NULL, 0, false);

    CGEventSetFlags(de, 0);
    CGEventSetFlags(ue, 0);

    UniChar c;
    for (CFIndex i = 0; i < text_length; ++i)
    {
        c = CFStringGetCharacterAtIndex(text_ref, i);
        CGEventKeyboardSetUnicodeString(de, 1, &c);
        CGEventPost(kCGAnnotatedSessionEventTap, de);
        usleep(1000);
        CGEventKeyboardSetUnicodeString(ue, 1, &c);
        CGEventPost(kCGAnnotatedSessionEventTap, ue);
    }

    CFRelease(ue);
    CFRelease(de);
    CFRelease(text_ref);
}

#pragma clang diagnostic pop


================================================
FILE: src/synthesize.h
================================================
#ifndef SKHD_SYNTHESIZE_H
#define SKHD_SYNTHESIZE_H

void synthesize_key(char *key_string);
void synthesize_text(char *text);

#endif


================================================
FILE: src/timing.h
================================================
#ifndef MACOS_TIMING_H
#define MACOS_TIMING_H

#include <stdint.h>
#include <CoreServices/CoreServices.h>
#include <mach/mach_time.h>

#define BEGIN_SCOPED_TIMED_BLOCK(note) \
    do { \
        struct timing_info timing; \
        if (profile) begin_timing(&timing, note)
#define END_SCOPED_TIMED_BLOCK() \
        if (profile) end_timing(&timing); \
    } while (0)

#define BEGIN_TIMED_BLOCK(note) \
    struct timing_info timing; \
    if (profile) begin_timing(&timing, note)
#define END_TIMED_BLOCK() \
    if (profile) end_timing(&timing)

static bool profile;

struct timing_info
{
    char *note;
    uint64_t start;
    uint64_t end;
    double ms;
};

void begin_timing(struct timing_info *timing, char *note);
void end_timing(struct timing_info *timing);

static inline uint64_t
macos_get_wall_clock(void)
{
    return mach_absolute_time();
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
static inline uint64_t macos_get_nanoseconds_elapsed(uint64_t start, uint64_t end)
{
    uint64_t elapsed = end - start;
    Nanoseconds nano = AbsoluteToNanoseconds(*(AbsoluteTime *) &elapsed);
    return *(uint64_t *) &nano;
}
#pragma clang diagnostic pop

static inline double
macos_get_milliseconds_elapsed(uint64_t start, uint64_t end)
{
    uint64_t ns = macos_get_nanoseconds_elapsed(start, end);
    return (double)(ns / 1000000.0);
}

static inline double
macos_get_seconds_elapsed(uint64_t start, uint64_t end)
{
    uint64_t ns = macos_get_nanoseconds_elapsed(start, end);
    return (double)(ns / 1000000000.0);
}

void begin_timing(struct timing_info *timing, char *note) {
    timing->note = note;
    timing->start = macos_get_wall_clock();
}

void end_timing(struct timing_info *timing) {
    timing->end = macos_get_wall_clock();
    timing->ms  = macos_get_milliseconds_elapsed(timing->start, timing->end);
    if (timing->note) {
        printf("%6.4fms (%s)\n", timing->ms, timing->note);
    } else {
        printf("%6.4fms\n", timing->ms);
    }
}

#endif


================================================
FILE: src/tokenize.c
================================================
#include "tokenize.h"
#include <ctype.h>

#define array_count(a) (sizeof((a)) / sizeof(*(a)))

int token_equals(struct token token, const char *match)
{
    const char *at = match;
    for (int i = 0; i < token.length; ++i, ++at) {
        if ((*at == 0) || (token.text[i] != *at)) {
            return false;
        }
    }
    return (*at == 0);
}

static void
advance(struct tokenizer *tokenizer)
{
    if (*tokenizer->at == '\n') {
        tokenizer->cursor = 0;
        ++tokenizer->line;
    }
    ++tokenizer->cursor;
    ++tokenizer->at;
}

static void
eat_whitespace(struct tokenizer *tokenizer)
{
    while (*tokenizer->at && isspace(*tokenizer->at)) {
        advance(tokenizer);
    }
}

static void
eat_comment(struct tokenizer *tokenizer)
{
    while (*tokenizer->at && *tokenizer->at != '\n') {
        advance(tokenizer);
    }
}

static void
eat_command(struct tokenizer *tokenizer)
{
    while (*tokenizer->at && *tokenizer->at != '\n') {
        if (*tokenizer->at == '\\') {
            advance(tokenizer);
        }
        advance(tokenizer);
    }
}

static void
eat_hex(struct tokenizer *tokenizer)
{
    while ((*tokenizer->at) &&
           ((isdigit(*tokenizer->at)) ||
            (*tokenizer->at >= 'A' && *tokenizer->at <= 'F'))) {
        advance(tokenizer);
    }
}

static void
eat_string(struct tokenizer *tokenizer)
{
    /*
     * NOTE(asmvik): This is NOT proper string parsing code, as we do
     * not check for escaped '"' here. At the time of writing, this is only
     * supposed to be used for parsing names of processes, and such names
     * should not contain escaped quotes at all. We are lazy and simply do
     * the most basic implementation that fulfills our current requirement.
     */

    while (*tokenizer->at && *tokenizer->at != '"') {
        advance(tokenizer);
    }
}

static void
eat_option(struct tokenizer *tokenizer)
{
    while (*tokenizer->at && !isspace(*tokenizer->at)) {
        advance(tokenizer);
    }
}

static inline bool
isidentifier(char c)
{
    return isalpha(c) || c == '_';
}

static void
eat_identifier(struct tokenizer *tokenizer)
{
    while ((*tokenizer->at) && isidentifier(*tokenizer->at)) {
        advance(tokenizer);
    }

    while ((*tokenizer->at) && isdigit(*tokenizer->at)) {
        advance(tokenizer);
    }
}

static enum token_type
resolve_identifier_type(struct token token)
{
    if (token.length == 1) {
        return Token_Key;
    }

    for (int i = 0; i < array_count(modifier_flags_str); ++i) {
        if (token_equals(token, modifier_flags_str[i])) {
            return Token_Modifier;
        }
    }

    for (int i = 0; i < array_count(literal_keycode_str); ++i) {
        if (token_equals(token, literal_keycode_str[i])) {
            return Token_Literal;
        }
    }

    return Token_Identifier;
}

struct token
peek_token(struct tokenizer tokenizer)
{
    return get_token(&tokenizer);
}

struct token
get_token(struct tokenizer *tokenizer)
{
    struct token token;
    char c;

    eat_whitespace(tokenizer);

    token.length = 1;
    token.text = tokenizer->at;
    token.line = tokenizer->line;
    token.cursor = tokenizer->cursor;
    c = *token.text;
    advance(tokenizer);

    switch (c) {
    case '\0':{ token.type = Token_EndOfStream; } break;
    case '+': { token.type = Token_Plus;        } break;
    case ',': { token.type = Token_Comma;       } break;
    case '<': { token.type = Token_Insert;      } break;
    case '@': { token.type = Token_Capture;     } break;
    case '~': { token.type = Token_Unbound;     } break;
    case '*': { token.type = Token_Wildcard;    } break;
    case '[': { token.type = Token_BeginList;   } break;
    case ']': { token.type = Token_EndList;     } break;
    case '.': {
        token.text = tokenizer->at;
        token.line = tokenizer->line;
        token.cursor = tokenizer->cursor;

        eat_option(tokenizer);
        token.length = tokenizer->at - token.text;
        token.type = Token_Option;
    } break;
    case '"': {
        token.text = tokenizer->at;
        token.line = tokenizer->line;
        token.cursor = tokenizer->cursor;

        eat_string(tokenizer);
        token.length = tokenizer->at - token.text;
        token.type = Token_String;

        advance(tokenizer);
    } break;
    case '#': {
        eat_comment(tokenizer);
        token = get_token(tokenizer);
    } break;
    case '-': {
        if (*tokenizer->at && *tokenizer->at == '>') {
            advance(tokenizer);
            token.length = tokenizer->at - token.text;
            token.type = Token_Arrow;
        } else {
            token.type = Token_Dash;
        }
    } break;
    case ';': {
        eat_whitespace(tokenizer);

        token.text = tokenizer->at;
        token.line = tokenizer->line;
        token.cursor = tokenizer->cursor;

        eat_identifier(tokenizer);
        token.length = tokenizer->at - token.text;
        token.type = Token_Activate;
    } break;
    case ':': {
        if (*tokenizer->at && *tokenizer->at == ':') {
            advance(tokenizer);
            token.length = tokenizer->at - token.text;
            token.type = Token_Decl;
        } else {
            eat_whitespace(tokenizer);

            token.text = tokenizer->at;
            token.line = tokenizer->line;
            token.cursor = tokenizer->cursor;

            eat_command(tokenizer);
            token.length = tokenizer->at - token.text;
            token.type = Token_Command;
        }
    } break;
    default:  {
        if (c == '0' && *tokenizer->at == 'x') {
            advance(tokenizer);
            eat_hex(tokenizer);
            token.length = tokenizer->at - token.text;
            token.type = Token_Key_Hex;
        } else if (isdigit(c)) {
            token.type = Token_Key;
        } else if (isalpha(c)) {
            eat_identifier(tokenizer);
            token.length = tokenizer->at - token.text;
            token.type = resolve_identifier_type(token);
        } else {
            token.type = Token_Unknown;
        }
    } break;
    }

    return token;
}

void tokenizer_init(struct tokenizer *tokenizer, char *buffer)
{
    tokenizer->buffer = buffer;
    tokenizer->at = buffer;
    tokenizer->line = 1;
    tokenizer->cursor = 1;
}


================================================
FILE: src/tokenize.h
================================================
#ifndef SKHD_TOKENIZE_H
#define SKHD_TOKENIZE_H

static const char *modifier_flags_str[] =
{
    "alt",   "lalt",    "ralt",
    "shift", "lshift",  "rshift",
    "cmd",   "lcmd",    "rcmd",
    "ctrl",  "lctrl",   "rctrl",
    "fn",    "hyper",   "meh",
};

static const char *literal_keycode_str[] =
{
    "return",          "tab",             "space",
    "backspace",       "escape",          "delete",
    "home",            "end",             "pageup",
    "pagedown",        "insert",          "left",
    "right",           "up",              "down",
    "f1",              "f2",              "f3",
    "f4",              "f5",              "f6",
    "f7",              "f8",              "f9",
    "f10",             "f11",             "f12",
    "f13",             "f14",             "f15",
    "f16",             "f17",             "f18",
    "f19",             "f20",

    "sound_up",        "sound_down",      "mute",
    "play",            "previous",        "next",
    "rewind",          "fast",            "brightness_up",
    "brightness_down", "illumination_up", "illumination_down"
};

enum token_type
{
    Token_Identifier,
    Token_Activate,

    Token_Command,
    Token_Modifier,
    Token_Literal,
    Token_Key_Hex,
    Token_Key,

    Token_Decl,
    Token_Comma,
    Token_Insert,
    Token_Plus,
    Token_Dash,
    Token_Arrow,
    Token_Capture,
    Token_Unbound,
    Token_Wildcard,
    Token_String,
    Token_Option,

    Token_BeginList,
    Token_EndList,

    Token_Unknown,
    Token_EndOfStream,
};

struct token
{
    enum token_type type;
    char *text;
    unsigned length;

    unsigned line;
    unsigned cursor;
};

struct tokenizer
{
    char *buffer;
    char *at;
    unsigned line;
    unsigned cursor;
};

void tokenizer_init(struct tokenizer *tokenizer, char *buffer);
struct token get_token(struct tokenizer *tokenizer);
struct token peek_token(struct tokenizer tokenizer);
int token_equals(struct token token, const char *match);

#endif
Download .txt
gitextract_za2cgzfj/

├── .gitignore
├── LICENSE.txt
├── README.md
├── examples/
│   └── skhdrc
├── makefile
└── src/
    ├── carbon.c
    ├── carbon.h
    ├── event_tap.c
    ├── event_tap.h
    ├── hashtable.h
    ├── hotkey.c
    ├── hotkey.h
    ├── hotload.c
    ├── hotload.h
    ├── locale.c
    ├── locale.h
    ├── log.h
    ├── notify.c
    ├── parse.c
    ├── parse.h
    ├── sbuffer.h
    ├── service.h
    ├── skhd.c
    ├── synthesize.c
    ├── synthesize.h
    ├── timing.h
    ├── tokenize.c
    └── tokenize.h
Download .txt
SYMBOL INDEX (253 symbols across 22 files)

FILE: src/carbon.c
  function OSStatus (line 35) | static OSStatus
  function carbon_event_init (line 61) | bool carbon_event_init(struct carbon_event *carbon)

FILE: src/carbon.h
  type carbon_event (line 6) | struct carbon_event
  type carbon_event (line 16) | struct carbon_event

FILE: src/event_tap.c
  function event_tap_enabled (line 3) | bool event_tap_enabled(struct event_tap *event_tap)
  function event_tap_begin (line 9) | bool event_tap_begin(struct event_tap *event_tap, event_tap_callback *ca...
  function event_tap_end (line 29) | void event_tap_end(struct event_tap *event_tap)

FILE: src/event_tap.h
  type event_tap (line 7) | struct event_tap
  type EVENT_TAP_CALLBACK (line 19) | typedef EVENT_TAP_CALLBACK(event_tap_callback);
  type event_tap (line 21) | struct event_tap
  type event_tap (line 22) | struct event_tap
  type event_tap (line 23) | struct event_tap

FILE: src/hashtable.h
  type bucket (line 7) | struct bucket
  type table (line 13) | struct table
  type table (line 22) | struct table
  type table (line 23) | struct table
  type table (line 25) | struct table
  type table (line 26) | struct table
  type table (line 27) | struct table
  type table (line 28) | struct table
  type bucket (line 36) | struct bucket
  type bucket (line 39) | struct bucket
  type bucket (line 39) | struct bucket
  type bucket (line 46) | struct bucket
  type table (line 47) | struct table
  type bucket (line 49) | struct bucket
  function table_init (line 59) | void table_init(struct table *table, int capacity, table_hash_func hash,...
  function table_free (line 69) | void table_free(struct table *table)
  type table (line 85) | struct table
  type bucket (line 87) | struct bucket
  function table_add (line 91) | void table_add(struct table *table, void *key, void *value)
  type table (line 104) | struct table
  type bucket (line 107) | struct bucket
  type table (line 118) | struct table
  type bucket (line 131) | struct bucket

FILE: src/hotkey.c
  function compare_lr_mod (line 33) | static bool
  function compare_fn (line 46) | static bool
  function compare_nx (line 52) | static bool
  function same_hotkey (line 58) | bool same_hotkey(struct hotkey *a, struct hotkey *b)
  function hash_hotkey (line 69) | unsigned long hash_hotkey(struct hotkey *a)
  function compare_string (line 74) | bool compare_string(char *a, char *b)
  function hash_string (line 83) | unsigned long hash_string(char *key)
  function fork_and_exec (line 97) | static inline void
  function passthrough (line 109) | static inline void
  type hotkey (line 115) | struct hotkey
  type mode (line 116) | struct mode
  type hotkey (line 116) | struct hotkey
  type hotkey (line 118) | struct hotkey
  function should_capture_hotkey (line 123) | static inline bool
  type hotkey (line 144) | struct hotkey
  type carbon_event (line 144) | struct carbon_event
  function find_and_exec_hotkey (line 163) | bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode...
  function free_mode_map (line 179) | void free_mode_map(struct table *mode_map)
  function free_blacklist (line 228) | void free_blacklist(struct table *blacklst)
  function cgevent_lrmod_flag_to_hotkey_lrmod_flag (line 238) | static void
  function cgevent_flags_to_hotkey_flags (line 255) | static uint32_t
  function create_eventkey (line 272) | struct hotkey create_eventkey(CGEventRef event)
  function intercept_systemkey (line 281) | bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey)
  function init_shell (line 301) | void init_shell(void)

FILE: src/hotkey.h
  type osx_event_mask (line 14) | enum osx_event_mask
  type hotkey_flag (line 31) | enum hotkey_flag
  type carbon_event (line 60) | struct carbon_event
  type mode (line 62) | struct mode
  type hotkey (line 71) | struct hotkey
  function add_flags (line 81) | static inline void
  function has_flags (line 87) | static inline bool
  function clear_flags (line 94) | static inline void
  type hotkey (line 103) | struct hotkey
  type hotkey (line 103) | struct hotkey
  type hotkey (line 104) | struct hotkey
  type hotkey (line 106) | struct hotkey
  type hotkey (line 107) | struct hotkey
  type hotkey (line 109) | struct hotkey
  type table (line 109) | struct table
  type mode (line 109) | struct mode
  type carbon_event (line 109) | struct carbon_event
  type table (line 110) | struct table
  type table (line 111) | struct table

FILE: src/hotload.c
  type watch_kind (line 14) | enum watch_kind
  type watched_catalog (line 21) | struct watched_catalog
  type watched_file (line 27) | struct watched_file
  type watched_entry (line 34) | struct watched_entry
  function same_string (line 43) | static inline bool
  type stat (line 81) | struct stat
  function resolve_watch_kind (line 98) | static enum watch_kind
  type watched_catalog (line 118) | struct watched_catalog
  function same_file (line 142) | static inline bool
  function FSEVENT_CALLBACK (line 149) | static FSEVENT_CALLBACK(hotloader_handler)
  function hotloader_add_watched_entry (line 179) | static inline void
  function hotloader_add_catalog (line 195) | bool hotloader_add_catalog(struct hotloader *hotloader, const char *dire...
  function hotloader_add_file (line 218) | bool hotloader_add_file(struct hotloader *hotloader, const char *file)
  function hotloader_begin (line 240) | bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *ca...
  function hotloader_end (line 286) | void hotloader_end(struct hotloader *hotloader)

FILE: src/hotload.h
  type HOTLOADER_CALLBACK (line 11) | typedef HOTLOADER_CALLBACK(hotloader_callback);
  type watched_entry (line 13) | struct watched_entry
  type hotloader (line 14) | struct hotloader
  type hotloader (line 27) | struct hotloader
  type hotloader (line 28) | struct hotloader
  type hotloader (line 29) | struct hotloader
  type hotloader (line 30) | struct hotloader

FILE: src/locale.c
  type table (line 9) | struct table
  function hash_keymap (line 26) | static int hash_keymap(const char *a)
  function same_keymap (line 40) | static bool same_keymap(const char *a, const char *b)
  function free_keycode_map (line 49) | static void free_keycode_map(void)
  function initialize_keycode_map (line 81) | bool initialize_keycode_map(void)
  function keycode_from_char (line 127) | uint32_t keycode_from_char(char key)

FILE: src/locale.h
  type CF_NOTIFICATION_CALLBACK (line 12) | typedef CF_NOTIFICATION_CALLBACK(cf_notification_callback);

FILE: src/log.h
  function debug (line 6) | static inline void
  function warn (line 17) | static inline void
  function error (line 26) | static inline void
  function require (line 36) | static inline void

FILE: src/notify.c
  function notify_init (line 1) | void notify_init(void)
  function notify (line 9) | void notify(const char *subtitle, const char *format, ...)

FILE: src/parse.c
  type mode (line 13) | struct mode
  type parser (line 14) | struct parser
  type mode (line 16) | struct mode
  type mode (line 22) | struct mode
  function keycode_from_hex (line 66) | static uint32_t
  function parse_command (line 74) | static void
  function parse_process_command_list (line 83) | static void
  function parse_activate (line 122) | static void
  function parse_key_hex (line 133) | static uint32_t
  function parse_key (line 144) | static uint32_t
  function handle_implicit_literal_flags (line 177) | static inline void
  function parse_key_literal (line 188) | static void
  type hotkey_flag (line 202) | enum hotkey_flag
  function parse_modifier (line 211) | static uint32_t
  function parse_mode (line 236) | static void
  type hotkey (line 264) | struct hotkey
  type parser (line 265) | struct parser
  type hotkey (line 267) | struct hotkey
  type hotkey (line 267) | struct hotkey
  type hotkey (line 268) | struct hotkey
  type mode (line 343) | struct mode
  type parser (line 344) | struct parser
  type mode (line 346) | struct mode
  type mode (line 346) | struct mode
  type token (line 347) | struct token
  function parse_declaration (line 371) | void parse_declaration(struct parser *parser)
  function parse_option_blacklist (line 399) | void parse_option_blacklist(struct parser *parser)
  function parse_option_load (line 417) | void parse_option_load(struct parser *parser, struct token option)
  function parse_option (line 443) | void parse_option(struct parser *parser)
  function parse_config (line 468) | bool parse_config(struct parser *parser)
  type hotkey (line 505) | struct hotkey
  type parser (line 506) | struct parser
  type hotkey (line 512) | struct hotkey
  type hotkey (line 512) | struct hotkey
  type hotkey (line 513) | struct hotkey
  function parser_peek (line 549) | struct token
  function parser_previous (line 555) | struct token
  function parser_eof (line 561) | bool parser_eof(struct parser *parser)
  function parser_advance (line 567) | struct token
  function parser_check (line 577) | bool parser_check(struct parser *parser, enum token_type type)
  function parser_match (line 584) | bool parser_match(struct parser *parser, enum token_type type)
  function parser_report_error (line 593) | void parser_report_error(struct parser *parser, struct token token, cons...
  function parser_do_directives (line 603) | void parser_do_directives(struct parser *parser, struct hotloader *hotlo...
  function parser_init (line 637) | bool parser_init(struct parser *parser, struct table *mode_map, struct t...
  function parser_init_text (line 652) | bool parser_init_text(struct parser *parser, char *text)
  function parser_destroy (line 660) | void parser_destroy(struct parser *parser)

FILE: src/parse.h
  type load_directive (line 7) | struct load_directive
  type table (line 13) | struct table
  type parser (line 14) | struct parser
  type parser (line 26) | struct parser
  type hotkey (line 27) | struct hotkey
  type parser (line 27) | struct parser
  type token (line 29) | struct token
  type parser (line 29) | struct parser
  type token (line 30) | struct token
  type parser (line 30) | struct parser
  type parser (line 31) | struct parser
  type token (line 32) | struct token
  type parser (line 32) | struct parser
  type parser (line 33) | struct parser
  type token_type (line 33) | enum token_type
  type parser (line 34) | struct parser
  type token_type (line 34) | enum token_type
  type parser (line 35) | struct parser
  type table (line 35) | struct table
  type table (line 35) | struct table
  type parser (line 36) | struct parser
  type parser (line 37) | struct parser
  type parser (line 38) | struct parser
  type token (line 38) | struct token

FILE: src/sbuffer.h
  type buf_hdr (line 7) | struct buf_hdr
  type buf_hdr (line 31) | struct buf_hdr

FILE: src/service.h
  function safe_exec (line 58) | static int safe_exec(char *const argv[], bool suppress_output)
  function directory_exists (line 154) | static inline bool directory_exists(char *filename)
  function ensure_directory_exists (line 165) | static inline void ensure_directory_exists(char *skhd_plist_path)
  function service_install_internal (line 188) | static int service_install_internal(char *skhd_plist_path)
  function service_install (line 206) | static int service_install(void)
  function service_uninstall (line 217) | static int service_uninstall(void)
  function service_start (line 228) | static int service_start(void)
  function service_restart (line 285) | static int service_restart(void)
  function service_stop (line 299) | static int service_stop(void)

FILE: src/skhd.c
  type GLOBAL_CONNECTION_CALLBACK (line 48) | typedef GLOBAL_CONNECTION_CALLBACK(global_connection_callback);
  type carbon_event (line 67) | struct carbon_event
  type event_tap (line 68) | struct event_tap
  type hotloader (line 69) | struct hotloader
  type mode (line 70) | struct mode
  type table (line 71) | struct table
  type table (line 72) | struct table
  function parse_config_helper (line 78) | static void
  function HOTLOADER_CALLBACK (line 110) | static HOTLOADER_CALLBACK(config_handler)
  function CF_NOTIFICATION_CALLBACK (line 120) | static CF_NOTIFICATION_CALLBACK(keymap_handler)
  function EVENT_TAP_CALLBACK (line 132) | static EVENT_TAP_CALLBACK(key_observer_handler)
  function EVENT_TAP_CALLBACK (line 162) | static EVENT_TAP_CALLBACK(key_handler)
  function sigusr1_handler (line 196) | static void sigusr1_handler(int signal)
  function pid_t (line 206) | static pid_t read_pid_file(void)
  function create_pid_file (line 233) | static void create_pid_file(void)
  function string_equals (line 270) | static inline bool string_equals(const char *a, const char *b)
  function parse_arguments (line 275) | static bool parse_arguments(int argc, char **argv)
  function check_privileges (line 356) | static bool check_privileges(void)
  function file_exists (line 374) | static inline bool file_exists(char *filename)
  function get_config_file (line 389) | static bool get_config_file(char *restrict filename, char *restrict buff...
  function dump_secure_keyboard_entry_process_info (line 424) | static void dump_secure_keyboard_entry_process_info(void)
  function GLOBAL_CONNECTION_CALLBACK (line 435) | static GLOBAL_CONNECTION_CALLBACK(connection_handler)
  function main (line 459) | int main(int argc, char **argv)

FILE: src/synthesize.c
  function create_and_post_keyevent (line 11) | static inline void
  function synthesize_modifiers (line 17) | static inline void
  function synthesize_key (line 41) | void synthesize_key(char *key_string)
  function synthesize_text (line 64) | void synthesize_text(char *text)

FILE: src/timing.h
  type timing_info (line 24) | struct timing_info
  type timing_info (line 32) | struct timing_info
  type timing_info (line 33) | struct timing_info
  function macos_get_wall_clock (line 35) | static inline uint64_t
  function macos_get_nanoseconds_elapsed (line 43) | static inline uint64_t macos_get_nanoseconds_elapsed(uint64_t start, uin...
  function macos_get_milliseconds_elapsed (line 51) | static inline double
  function macos_get_seconds_elapsed (line 58) | static inline double
  function begin_timing (line 65) | void begin_timing(struct timing_info *timing, char *note) {
  function end_timing (line 70) | void end_timing(struct timing_info *timing) {

FILE: src/tokenize.c
  function token_equals (line 6) | int token_equals(struct token token, const char *match)
  function advance (line 17) | static void
  function eat_whitespace (line 28) | static void
  function eat_comment (line 36) | static void
  function eat_command (line 44) | static void
  function eat_hex (line 55) | static void
  function eat_string (line 65) | static void
  function eat_option (line 81) | static void
  function isidentifier (line 89) | static inline bool
  function eat_identifier (line 95) | static void
  function resolve_identifier_type (line 107) | static enum token_type
  function peek_token (line 129) | struct token
  function get_token (line 135) | struct token
  function tokenizer_init (line 242) | void tokenizer_init(struct tokenizer *tokenizer, char *buffer)

FILE: src/tokenize.h
  type token_type (line 34) | enum token_type
  type token (line 64) | struct token
  type tokenizer (line 74) | struct tokenizer
  type tokenizer (line 82) | struct tokenizer
  type token (line 83) | struct token
  type tokenizer (line 83) | struct tokenizer
  type token (line 84) | struct token
  type tokenizer (line 84) | struct tokenizer
  type token (line 85) | struct token
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (118K chars).
[
  {
    "path": ".gitignore",
    "chars": 5,
    "preview": "bin/\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1080,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Åsmund Vikane\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "README.md",
    "chars": 6590,
    "preview": "> **NOTE**: If you are having issues with skhd, or are looking for feature expansion and future development,  \nyou may w"
  },
  {
    "path": "examples/skhdrc",
    "chars": 4872,
    "preview": "#  NOTE(asmvik): A list of all built-in modifier and literal keywords can\n#                     be found at https://gith"
  },
  {
    "path": "makefile",
    "chars": 447,
    "preview": "FRAMEWORKS     = -framework Cocoa -framework Carbon -framework CoreServices\nBUILD_PATH     = ./bin\nBUILD_FLAGS    = -std"
  },
  {
    "path": "src/carbon.c",
    "chars": 2135,
    "preview": "#include \"carbon.h\"\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\nstatic inline char *\n"
  },
  {
    "path": "src/carbon.h",
    "chars": 351,
    "preview": "#ifndef SKHD_CARBON_H\n#define SKHD_CARBON_H\n\n#include <Carbon/Carbon.h>\n\nstruct carbon_event\n{\n    EventTargetRef target"
  },
  {
    "path": "src/event_tap.c",
    "chars": 1471,
    "preview": "#include \"event_tap.h\"\n\nbool event_tap_enabled(struct event_tap *event_tap)\n{\n    bool result = (event_tap->handle && CG"
  },
  {
    "path": "src/event_tap.h",
    "chars": 647,
    "preview": "#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 "
  },
  {
    "path": "src/hashtable.h",
    "chars": 3497,
    "preview": "#ifndef HASHTABLE_H\n#define HASHTABLE_H\n\ntypedef unsigned long (*table_hash_func)(void *key);\ntypedef int (*table_compar"
  },
  {
    "path": "src/hotkey.c",
    "chars": 8831,
    "preview": "#include \"hotkey.h\"\n\n#define HOTKEY_FOUND           ((1) << 0)\n#define MODE_CAPTURE(a)        ((a) << 1)\n#define HOTKEY_"
  },
  {
    "path": "src/hotkey.h",
    "chars": 3061,
    "preview": "#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#defin"
  },
  {
    "path": "src/hotload.c",
    "chars": 9420,
    "preview": "#include \"hotload.h\"\n#include <sys/stat.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define FSEVENT_C"
  },
  {
    "path": "src/hotload.h",
    "chars": 858,
    "preview": "#ifndef SKHD_HOTLOAD_H\n#define SKHD_HOTLOAD_H\n\n#ifndef __cplusplus\n#include <stdbool.h>\n#endif\n\n#include <Carbon/Carbon."
  },
  {
    "path": "src/locale.c",
    "chars": 4230,
    "preview": "#include \"locale.h\"\n#include \"hashtable.h\"\n#include \"sbuffer.h\"\n#include <Carbon/Carbon.h>\n#include <IOKit/hidsystem/ev_"
  },
  {
    "path": "src/locale.h",
    "chars": 446,
    "preview": "#ifndef SKHD_LOCALE_H\n#define SKHD_LOCALE_H\n\n#include <stdint.h>\n\n#define CF_NOTIFICATION_CALLBACK(name) \\\n    void name"
  },
  {
    "path": "src/log.h",
    "chars": 764,
    "preview": "#ifndef SKHD_LOG_H\n#define SKHD_LOG_H\n\nstatic bool verbose;\n\nstatic inline void\ndebug(const char *format, ...)\n{\n    if "
  },
  {
    "path": "src/notify.c",
    "chars": 1577,
    "preview": "void notify_init(void)\n{\n    class_replaceMethod(objc_getClass(\"NSBundle\"),\n                        sel_registerName(\"bu"
  },
  {
    "path": "src/parse.c",
    "chars": 20271,
    "preview": "#include \"parse.h\"\n#include \"tokenize.h\"\n#include \"locale.h\"\n#include \"hotkey.h\"\n#include \"hashtable.h\"\n\n#include <stdli"
  },
  {
    "path": "src/parse.h",
    "chars": 1143,
    "preview": "#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 "
  },
  {
    "path": "src/sbuffer.h",
    "chars": 1200,
    "preview": "#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_"
  },
  {
    "path": "src/service.h",
    "chars": 10520,
    "preview": "#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_LAUNCHC"
  },
  {
    "path": "src/skhd.c",
    "chars": 15487,
    "preview": "#include <stdlib.h>\n#include <stdio.h>\n#include <stdbool.h>\n#include <stdarg.h>\n#include <getopt.h>\n#include <signal.h>\n"
  },
  {
    "path": "src/synthesize.c",
    "chars": 2367,
    "preview": "#include <Carbon/Carbon.h>\n\n#include \"synthesize.h\"\n#include \"locale.h\"\n#include \"parse.h\"\n#include \"hotkey.h\"\n\n#pragma "
  },
  {
    "path": "src/synthesize.h",
    "chars": 134,
    "preview": "#ifndef SKHD_SYNTHESIZE_H\n#define SKHD_SYNTHESIZE_H\n\nvoid synthesize_key(char *key_string);\nvoid synthesize_text(char *t"
  },
  {
    "path": "src/timing.h",
    "chars": 2026,
    "preview": "#ifndef MACOS_TIMING_H\n#define MACOS_TIMING_H\n\n#include <stdint.h>\n#include <CoreServices/CoreServices.h>\n#include <mach"
  },
  {
    "path": "src/tokenize.c",
    "chars": 6270,
    "preview": "#include \"tokenize.h\"\n#include <ctype.h>\n\n#define array_count(a) (sizeof((a)) / sizeof(*(a)))\n\nint token_equals(struct t"
  },
  {
    "path": "src/tokenize.h",
    "chars": 1995,
    "preview": "#ifndef SKHD_TOKENIZE_H\n#define SKHD_TOKENIZE_H\n\nstatic const char *modifier_flags_str[] =\n{\n    \"alt\",   \"lalt\",    \"ra"
  }
]

About this extraction

This page contains the full source code of the asmvik/skhd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (109.1 KB), approximately 28.7k tokens, and a symbol index with 253 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!