aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkoekeishiya <aasvi93@hotmail.com>2019-07-10 00:11:50 +0200
committerkoekeishiya <aasvi93@hotmail.com>2019-07-10 00:11:50 +0200
commit32f669eab3dea1908a83133e402411fa2e377ac3 (patch)
treef6ace98d121b9fac8598e6b52e266b082920671c
parentaae0e4f2a5d90a812343acf3d5b3f2f20f5c2dc0 (diff)
parent22071ddd738ad591f42500ad8dbe818ffd1eb93d (diff)
downloadskhd-mt.tar.gz
skhd-mt.zip
merge mastermt
-rw-r--r--README.md100
-rw-r--r--examples/skhdrc161
-rw-r--r--makefile5
-rw-r--r--src/carbon.c77
-rw-r--r--src/carbon.h18
-rw-r--r--src/hashtable.h13
-rw-r--r--src/hotkey.c142
-rw-r--r--src/hotkey.h44
-rw-r--r--src/hotload.c288
-rw-r--r--src/hotload.h16
-rw-r--r--src/locale.c112
-rw-r--r--src/locale.h10
-rw-r--r--src/log.h42
-rw-r--r--src/parse.c308
-rw-r--r--src/parse.h25
-rw-r--r--src/sbuffer.h6
-rw-r--r--src/skhd.c364
-rw-r--r--src/synthesize.c93
-rw-r--r--src/synthesize.h7
-rw-r--r--src/timing.h76
-rw-r--r--src/tokenize.c48
-rw-r--r--src/tokenize.h41
22 files changed, 1581 insertions, 415 deletions
diff --git a/README.md b/README.md
index 1446a28..f1647e5 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,13 @@
[![Build Status](https://travis-ci.org/koekeishiya/skhd.svg?branch=master)](https://travis-ci.org/koekeishiya/skhd)
-**skhd** is a simple hotkey daemon for macOS. It is a stripped version of [**khd**](https://github.com/koekeishiya/khd)
+**skhd** is a simple hotkey daemon for macOS. It is a stripped version of [**khd** (*no longer maintained*)](https://github.com/koekeishiya/khd)
(although rewritten from scratch), that sacrifices the more advanced features in favour of increased responsiveness and performance.
**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.
+
feature comparison between **skhd** and **khd**
| feature | skhd | khd |
@@ -11,8 +15,9 @@ feature comparison between **skhd** and **khd**
| hotload config file | [x] | [ ] |
| hotkey passthrough | [x] | [x] |
| modal hotkey-system | [x] | [x] |
+| application specific hotkey| [x] | [x] |
+| blacklist applications | [x] | [ ] |
| use media-keys as hotkey | [x] | [ ] |
-| application specific hotkey| [ ] | [x] |
| modifier only hotkey | [ ] | [x] |
| caps-lock as hotkey | [ ] | [x] |
| mouse-buttons as hotkey | [ ] | [x] |
@@ -32,9 +37,6 @@ Requires xcode-8 command-line tools.
brew install koekeishiya/formulae/skhd
brew services start skhd
- stdout -> /tmp/skhd.out
- stderr -> /tmp/skhd.err
-
**Source**:
Requires xcode-8 command-line tools.
@@ -46,11 +48,32 @@ Requires xcode-8 command-line tools.
### Usage
```
+-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
@@ -63,35 +86,47 @@ A list of all built-in modifier and literal keywords can be found [here](https:/
A hotkey is written according to the following rules:
```
-hotkey = <mode> '<' <action> | <action>
+hotkey = <mode> '<' <action> | <action>
-mode = 'name of mode' | <mode> ',' <mode>
+mode = 'name of mode' | <mode> ',' <mode>
-action = <keysym> ':' <command> | <keysym> '->' ':' <command>
- <keysym> ';' <mode> | <keysym> '->' ';' <mode>
+action = <keysym> '[' <proc_map_lst> ']' | <keysym> '->' '[' <proc_map_lst> ']'
+ <keysym> ':' <command> | <keysym> '->' ':' <command>
+ <keysym> ';' <mode> | <keysym> '->' ';' <mode>
-keysym = <mod> '-' <key> | <key>
+keysym = <mod> '-' <key> | <key>
-mod = 'modifier keyword' | <mod> '+' <mod>
+mod = 'modifier keyword' | <mod> '+' <mod>
-key = <literal> | <keycode>
+key = <literal> | <keycode>
-literal = 'single letter or built-in keyword'
+literal = 'single letter or built-in keyword'
-keycode = 'apple keyboard kVK_<Key> values (0x3C)'
+keycode = 'apple keyboard kVK_<Key> values (0x3C)'
--> = keypress is not consumed by skhd
+proc_map_lst = * <proc_map>
-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.
+proc_map = <string> ':' <command> | <string> '~' |
+ '*' ':' <command> | '*' '~'
- to allow a command to extend into multiple lines,
- prepend '\' at the end of the previous line.
+string = '"' 'sequence of characters' '"'
- an EOL character signifies the end of the bind.
+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:
@@ -115,3 +150,22 @@ command = command is executed through '$SHELL -c' and
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"
+]
+```
diff --git a/examples/skhdrc b/examples/skhdrc
index 2b0e6d8..3486c1f 100644
--- a/examples/skhdrc
+++ b/examples/skhdrc
@@ -3,36 +3,47 @@
#
# A hotkey is written according to the following rules:
#
-# hotkey = <mode> '<' <action> | <action>
+# hotkey = <mode> '<' <action> | <action>
#
-# mode = 'name of mode' | <mode> ',' <mode>
+# mode = 'name of mode' | <mode> ',' <mode>
#
-# action = <keysym> ':' <command> | <keysym> '->' ':' <command>
-# <keysym> ';' <mode> | <keysym> '->' ';' <mode>
+# action = <keysym> '[' <proc_map_lst> ']' | <keysym> '->' '[' <proc_map_lst> ']'
+# <keysym> ':' <command> | <keysym> '->' ':' <command>
+# <keysym> ';' <mode> | <keysym> '->' ';' <mode>
#
-# keysym = <mod> '-' <key> | <key>
+# keysym = <mod> '-' <key> | <key>
#
-# mod = 'built-in mod keyword' | <mod> '+' <mod>
+# mod = 'modifier keyword' | <mod> '+' <mod>
#
-# key = <literal> | <keycode>
+# key = <literal> | <keycode>
#
-# literal = 'single letter or built-in keyword'
+# literal = 'single letter or built-in keyword'
#
-# keycode = 'apple keyboard kVK_<Key> values (0x3C)'
+# keycode = 'apple keyboard kVK_<Key> values (0x3C)'
#
-# -> = keypress is not consumed by skhd
+# proc_map_lst = * <proc_map>
#
-# 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.
+# proc_map = <string> ':' <command> | <string> '~' |
+# '*' ':' <command> | '*' '~'
#
-# to allow a command to extend into multiple lines,
-# prepend '\' at the end of the previous line.
+# string = '"' 'sequence of characters' '"'
#
-# an EOL character signifies the end of the bind.
+# 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(koekeishiya): A mode is declared according to the following rules:
#
@@ -43,16 +54,16 @@
#
# @ = 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.
+# 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.
+# 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.
+# an EOL character signifies the end of the bind.
# add an on_enter command to the default mode
# :: default : chunkc border::color 0xff775759
@@ -69,12 +80,40 @@
# 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 : open -na /Applications/Kitty.app
+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)
+
# close focused window
alt - w : chunkc tiling::window --close
@@ -111,27 +150,48 @@ shift + alt - left : chunkc tiling::window --grid-layout 1:2:0:0:1:1
# make floating window fill right-half of screen
shift + alt - right : chunkc tiling::window --grid-layout 1:2:1:0:1:1
-# send window to desktop
-shift + alt - x : chunkc tiling::window --send-to-desktop $(chunkc get _last_active_desktop)
-shift + alt - z : chunkc tiling::window --send-to-desktop prev
-shift + alt - c : chunkc tiling::window --send-to-desktop next
-shift + alt - 1 : chunkc tiling::window --send-to-desktop 1
-shift + alt - 2 : chunkc tiling::window --send-to-desktop 2
-shift + alt - 3 : chunkc tiling::window --send-to-desktop 3
-shift + alt - 4 : chunkc tiling::window --send-to-desktop 4
-shift + alt - 5 : chunkc tiling::window --send-to-desktop 5
-shift + alt - 6 : chunkc tiling::window --send-to-desktop 6
+# create desktop, move window and follow focus
+shift + cmd - n : chunkc tiling::desktop --create;\
+ id=$(chunkc tiling::query --desktops-for-monitor $(chunkc tiling::query --monitor-for-desktop $(chunkc tiling::query --desktop id)));\
+ chunkc tiling::window --send-to-desktop $(echo ${id##* });\
+ chunkc tiling::desktop --focus $(echo ${id##* })
+
+# create desktop and follow focus
+cmd + alt - n : chunkc tiling::desktop --create;\
+ id=$(chunkc tiling::query --desktops-for-monitor $(chunkc tiling::query --monitor-for-desktop $(chunkc tiling::query --desktop id)));\
+ chunkc tiling::desktop --focus $(echo ${id##* })
+# destroy desktop
+cmd + alt - w : chunkc tiling::desktop --annihilate
+
+# fast focus desktop
+cmd + alt - x : chunkc tiling::desktop --focus $(chunkc get _last_active_desktop)
+cmd + alt - z : chunkc tiling::desktop --focus prev
+cmd + alt - c : chunkc tiling::desktop --focus next
+cmd + alt - 1 : chunkc tiling::desktop --focus 1
+cmd + alt - 2 : chunkc tiling::desktop --focus 2
+cmd + alt - 3 : chunkc tiling::desktop --focus 3
+cmd + alt - 4 : chunkc tiling::desktop --focus 4
+cmd + alt - 5 : chunkc tiling::desktop --focus 5
+cmd + alt - 6 : chunkc tiling::desktop --focus 6
+cmd + alt - 7 : chunkc tiling::desktop --focus 7
+cmd + alt - 8 : chunkc tiling::desktop --focus 8
+cmd + alt - 9 : chunkc tiling::desktop --focus 9
+cmd + alt - 0 : chunkc tiling::desktop --focus 10
# send window to desktop and follow focus
-shift + cmd - x : chunkc tiling::window --send-to-desktop $(chunkc get _last_active_desktop); qes -k "cmd + alt - $(chunkc get _last_active_desktop)"
-shift + cmd - z : chunkc tiling::window --send-to-desktop prev; qes -k "cmd + alt - z"
-shift + cmd - c : chunkc tiling::window --send-to-desktop next; qes -k "cmd + alt - c"
-shift + cmd - 1 : chunkc tiling::window --send-to-desktop 1; qes -k "cmd + alt - 1"
-shift + cmd - 2 : chunkc tiling::window --send-to-desktop 2; qes -k "cmd + alt - 2"
-shift + cmd - 3 : chunkc tiling::window --send-to-desktop 3; qes -k "cmd + alt - 3"
-shift + cmd - 4 : chunkc tiling::window --send-to-desktop 4; qes -k "cmd + alt - 4"
-shift + cmd - 5 : chunkc tiling::window --send-to-desktop 5; qes -k "cmd + alt - 5"
-shift + cmd - 6 : chunkc tiling::window --send-to-desktop 6; qes -k "cmd + alt - 6"
+shift + cmd - x : chunkc tiling::window --send-to-desktop $(chunkc get _last_active_desktop); chunkc tiling::desktop --focus $(chunkc get _last_active_desktop)
+shift + cmd - z : chunkc tiling::window --send-to-desktop prev; chunkc tiling::desktop --focus prev
+shift + cmd - c : chunkc tiling::window --send-to-desktop next; chunkc tiling::desktop --focus next
+shift + cmd - 1 : chunkc tiling::window --send-to-desktop 1; chunkc tiling::desktop --focus 1
+shift + cmd - 2 : chunkc tiling::window --send-to-desktop 2; chunkc tiling::desktop --focus 2
+shift + cmd - 3 : chunkc tiling::window --send-to-desktop 3; chunkc tiling::desktop --focus 3
+shift + cmd - 4 : chunkc tiling::window --send-to-desktop 4; chunkc tiling::desktop --focus 4
+shift + cmd - 5 : chunkc tiling::window --send-to-desktop 5; chunkc tiling::desktop --focus 5
+shift + cmd - 6 : chunkc tiling::window --send-to-desktop 6; chunkc tiling::desktop --focus 6
+shift + cmd - 7 : chunkc tiling::window --send-to-desktop 7; chunkc tiling::desktop --focus 7
+shift + cmd - 8 : chunkc tiling::window --send-to-desktop 8; chunkc tiling::desktop --focus 8
+shift + cmd - 9 : chunkc tiling::window --send-to-desktop 9; chunkc tiling::desktop --focus 9
+shift + cmd - 0 : chunkc tiling::window --send-to-desktop 10; chunkc tiling::desktop --focus 10
# focus monitor
ctrl + alt - z : chunkc tiling::monitor -f prev
@@ -197,8 +257,11 @@ alt - q : chunkc tiling::window --toggle fade
alt - t : chunkc tiling::window --toggle float;\
chunkc tiling::window --grid-layout 4:4:1:1:2:2
+# toggle sticky
+alt - s : chunkc tiling::window --toggle sticky
+
# toggle sticky, float and resize to picture-in-picture size
-alt - s : chunkc tiling::window --toggle sticky;\
+alt - p : chunkc tiling::window --toggle sticky;\
chunkc tiling::window --grid-layout 5:5:4:0:1:1
# float next window to be tiled
@@ -210,7 +273,3 @@ ctrl + alt - s : chunkc tiling::desktop --layout monocle
ctrl + alt - d : chunkc tiling::desktop --layout float
ctrl + alt - w : chunkc tiling::desktop --deserialize ~/.chunkwm_layouts/dev_1
-
-cmd - 7 : qes -k "shift + alt - 7"
-cmd - 8 : qes -k "shift + alt - 8"
-cmd - 9 : qes -k "shift + alt - 9"
diff --git a/makefile b/makefile
index 6d18463..3cee3cb 100644
--- a/makefile
+++ b/makefile
@@ -1,4 +1,4 @@
-FRAMEWORKS = -framework Carbon -F/System/Library/PrivateFrameworks -framework MultitouchSupport
+FRAMEWORKS = -framework Carbon -framework CoreAudio -F/System/Library/PrivateFrameworks -framework MultitouchSupport
BUILD_PATH = ./bin
BUILD_FLAGS = -std=c99 -Wall -g -O0
SKHD_SRC = ./src/skhd.c
@@ -11,9 +11,6 @@ all: clean $(BINS)
install: BUILD_FLAGS=-std=c99 -O3
install: clean $(BINS)
-segfault: BUILD_FLAGS=-O0 -g -Wall -std=c99 -D_FORTIFY_SOURCE=2 -fstack-protector-strong --param ssp-buffer-size=4 -fPIC -fno-strict-overflow -Wformat -Wformat-security -Werror=format-security
-segfault: clean $(BINS)
-
clean:
rm -rf $(BUILD_PATH)
diff --git a/src/carbon.c b/src/carbon.c
new file mode 100644
index 0000000..dcfc2d8
--- /dev/null
+++ b/src/carbon.c
@@ -0,0 +1,77 @@
+#include "carbon.h"
+
+#define internal static
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated"
+internal 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);
+}
+
+internal inline char *
+find_active_process_name(void)
+{
+ ProcessSerialNumber psn;
+ GetFrontProcess(&psn);
+ return find_process_name_for_psn(&psn);
+}
+#pragma clang diagnostic pop
+
+internal 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;
+}
diff --git a/src/carbon.h b/src/carbon.h
new file mode 100644
index 0000000..8ef9ed4
--- /dev/null
+++ b/src/carbon.h
@@ -0,0 +1,18 @@
+#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
diff --git a/src/hashtable.h b/src/hashtable.h
index 42d0166..8f73066 100644
--- a/src/hashtable.h
+++ b/src/hashtable.h
@@ -33,7 +33,9 @@ void *table_reset(struct table *table, int *count);
#include <stdlib.h>
#include <string.h>
-static struct bucket *
+#define internal static
+
+internal struct bucket *
table_new_bucket(void *key, void *value)
{
struct bucket *bucket = malloc(sizeof(struct bucket));
@@ -43,7 +45,7 @@ table_new_bucket(void *key, void *value)
return bucket;
}
-static struct bucket **
+internal struct bucket **
table_get_bucket(struct table *table, void *key)
{
struct bucket **bucket = table->buckets + (table->hash(key) % table->capacity);
@@ -56,6 +58,8 @@ table_get_bucket(struct table *table, void *key)
return bucket;
}
+#undef internal
+
void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func compare)
{
table->count = 0;
@@ -76,7 +80,10 @@ void table_free(struct table *table)
bucket = next;
}
}
- free(table->buckets);
+ if (table->buckets) {
+ free(table->buckets);
+ table->buckets = NULL;
+ }
}
void *table_find(struct table *table, void *key)
diff --git a/src/hotkey.c b/src/hotkey.c
index a2c383a..780aa3a 100644
--- a/src/hotkey.c
+++ b/src/hotkey.c
@@ -1,7 +1,11 @@
#include "hotkey.h"
#define internal static
-#define local_persist static
+#define global static
+
+#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
@@ -10,6 +14,9 @@
#define LMOD_OFFS 1
#define RMOD_OFFS 2
+global char arg[] = "-c";
+global char *shell = NULL;
+
internal uint32_t cgevent_lrmod_flag[] =
{
Event_Mask_Alt, Event_Mask_LAlt, Event_Mask_RAlt,
@@ -67,7 +74,7 @@ unsigned long hash_hotkey(struct hotkey *a)
return a->key;
}
-bool same_mode(char *a, char *b)
+bool compare_string(char *a, char *b)
{
while (*a && *b && *a == *b) {
++a;
@@ -76,7 +83,7 @@ bool same_mode(char *a, char *b)
return *a == '\0' && *b == '\0';
}
-unsigned long hash_mode(char *key)
+unsigned long hash_string(char *key)
{
unsigned long hash = 0, high;
while(*key) {
@@ -90,48 +97,86 @@ unsigned long hash_mode(char *key)
return hash;
}
-internal void
+internal inline void
fork_and_exec(char *command)
{
- local_persist char arg[] = "-c";
- local_persist char *shell = NULL;
- if (!shell) {
- char *env_shell = getenv("SHELL");
- shell = env_shell ? env_shell : "/bin/bash";
- }
-
int cpid = fork();
if (cpid == 0) {
+ setsid();
char *exec[] = { shell, arg, command, NULL};
int status_code = execvp(exec[0], exec);
exit(status_code);
}
}
-internal inline bool
-passthrough(struct hotkey *hotkey)
+internal inline void
+passthrough(struct hotkey *hotkey, uint32_t *capture)
{
- return !has_flags(hotkey, Hotkey_Flag_Passthrough);
+ *capture |= HOTKEY_PASSTHROUGH((int)has_flags(hotkey, Hotkey_Flag_Passthrough));
}
internal inline struct hotkey *
-find_hotkey(struct mode *mode, struct hotkey *hotkey)
+find_hotkey(struct mode *mode, struct hotkey *hotkey, uint32_t *capture)
{
- return table_find(&mode->hotkey_map, hotkey);
+ struct hotkey *result = table_find(&mode->hotkey_map, hotkey);
+ if (result) *capture |= HOTKEY_FOUND;
+ return result;
}
-bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m)
+internal inline bool
+should_capture_hotkey(uint32_t capture)
{
- bool c = (*m)->capture;
- for (struct hotkey *h = find_hotkey(*m, k); h; c |= passthrough(h), h = 0) {
- char *cmd = h->command;
+ 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));
+}
+
+internal 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 c;
+ return should_capture_hotkey(c);
}
void free_mode_map(struct table *mode_map)
@@ -156,7 +201,17 @@ void free_mode_map(struct table *mode_map)
buf_push(freed_pointers, hotkey);
buf_free(hotkey->mode_list);
- free(hotkey->command);
+
+ 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:;
}
@@ -173,6 +228,16 @@ next:;
}
}
+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);
+ }
+}
+
internal void
cgevent_lrmod_flag_to_hotkey_lrmod_flag(CGEventFlags eventflags, uint32_t *flags, int mod)
{
@@ -216,21 +281,30 @@ struct hotkey create_eventkey(CGEventRef event)
return eventkey;
}
-struct systemkey create_systemkey(CGEventRef event)
+bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey)
{
CFDataRef event_data = CGEventCreateData(kCFAllocatorDefault, event);
- const uint8_t *data = CFDataGetBytePtr(event_data);
- uint8_t event_subtype = data[123];
- uint8_t key_code = data[129];
+ 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);
- struct systemkey systemkey = {
- .eventkey = {
- .key = key_code,
- .flags = cgevent_flags_to_hotkey_flags(CGEventGetFlags(event)) | Hotkey_Flag_NX
- },
- .intercept = key_state == NX_KEYDOWN && event_subtype == NX_SUBTYPE_AUX_CONTROL_BUTTONS
- };
- return systemkey;
+ 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";
+ }
}
diff --git a/src/hotkey.h b/src/hotkey.h
index b0ca504..4611b09 100644
--- a/src/hotkey.h
+++ b/src/hotkey.h
@@ -5,6 +5,12 @@
#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,
@@ -43,15 +49,18 @@ enum hotkey_flag
Hotkey_Flag_Hyper = (Hotkey_Flag_Cmd |
Hotkey_Flag_Alt |
Hotkey_Flag_Shift |
- Hotkey_Flag_Control)
+ Hotkey_Flag_Control),
+ Hotkey_Flag_Meh = (Hotkey_Flag_Control |
+ Hotkey_Flag_Shift |
+ Hotkey_Flag_Alt)
};
#include "hashtable.h"
+struct carbon_event;
+
struct mode
{
- int line;
- int cursor;
char *name;
char *command;
bool capture;
@@ -62,45 +71,48 @@ struct hotkey
{
uint32_t flags;
uint32_t key;
- char *command;
+ char **process_name;
+ char **command;
+ char *wildcard_command;
struct mode **mode_list;
};
-struct systemkey
-{
- struct hotkey eventkey;
- bool intercept;
-};
+#define internal static
-static inline void
+internal inline void
add_flags(struct hotkey *hotkey, uint32_t flag)
{
hotkey->flags |= flag;
}
-static inline bool
+internal inline bool
has_flags(struct hotkey *hotkey, uint32_t flag)
{
bool result = hotkey->flags & flag;
return result;
}
-static inline void
+internal inline void
clear_flags(struct hotkey *hotkey, uint32_t flag)
{
hotkey->flags &= ~flag;
}
-bool same_mode(char *a, char *b);
-unsigned long hash_mode(char *key);
+#undef internal
+
+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);
-struct systemkey create_systemkey(CGEventRef event);
+bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey);
-bool find_and_exec_hotkey(struct hotkey *eventkey, struct table *mode_map, struct mode **current_mode);
+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
diff --git a/src/hotload.c b/src/hotload.c
index ad02d21..88d286a 100644
--- a/src/hotload.c
+++ b/src/hotload.c
@@ -8,23 +8,59 @@
#define FSEVENT_CALLBACK(name) void name(ConstFSEventStreamRef stream,\
void *context,\
- size_t count,\
- void *paths,\
+ 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;
+ };
+};
+
+internal inline bool
+same_string(const char *a, const char *b)
+{
+ bool result = a && b && strcmp(a, b) == 0;
+ return result;
+}
+
internal char *
copy_string(const char *s)
{
unsigned length = strlen(s);
- char *result = malloc(length + 1);
+ char *result = (char *) malloc(length + 1);
memcpy(result, s, length);
result[length] = '\0';
return result;
}
internal char *
-file_directory(const char *file)
+file_directory(char *file)
{
char *last_slash = strrchr(file, '/');
*last_slash = '\0';
@@ -34,7 +70,7 @@ file_directory(const char *file)
}
internal char *
-file_name(const char *file)
+file_name(char *file)
{
char *last_slash = strrchr(file, '/');
char *name = copy_string(last_slash + 1);
@@ -42,50 +78,73 @@ file_name(const char *file)
}
internal char *
-resolve_symlink(char *file)
+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 file;
+ return copy_string(file);
}
- ssize_t size = buffer.st_size + 1;
- char *result = malloc(size);
- ssize_t read = readlink(file, result, size);
+ char *result = realpath(file, NULL);
+ return result;
+}
- if (read != -1) {
- result[read] = '\0';
- return result;
+internal enum watch_kind
+resolve_watch_kind(char *file)
+{
+ struct stat buffer;
+ if (lstat(file, &buffer) != 0) {
+ return WATCH_KIND_INVALID;
}
- free(result);
- return NULL;
+ if (S_ISDIR(buffer.st_mode)) {
+ return WATCH_KIND_CATALOG;
+ }
+
+ if (!S_ISLNK(buffer.st_mode)) {
+ return WATCH_KIND_FILE;
+ }
+
+ return WATCH_KIND_INVALID;
}
-internal struct watched_file *
-hotloader_watched_file(struct hotloader *hotloader, char *absolutepath)
+internal char *
+same_catalog(char *absolutepath, struct watched_catalog *catalog_info)
{
- struct watched_file *result = NULL;
- for (unsigned index = 0; result == NULL && index < hotloader->watch_count; ++index) {
- struct watched_file *watch_info = hotloader->watch_list + index;
+ char *last_slash = strrchr(absolutepath, '/');
+ if (!last_slash) return NULL;
- char *directory = file_directory(absolutepath);
- char *filename = file_name(absolutepath);
+ char *filename = NULL;
- if (strcmp(watch_info->directory, directory) == 0) {
- if (strcmp(watch_info->filename, filename) == 0) {
- result = watch_info;
- }
- }
+ // NOTE(koekeisihya): null terminate '/' to cut off filename
+ *last_slash = '\0';
- free(filename);
- free(directory);
+ 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;
+}
+
+internal inline bool
+same_file(char *absolutepath, struct watched_file *file_info)
+{
+ bool result = same_string(absolutepath, file_info->absolutepath);
return result;
}
@@ -93,59 +152,120 @@ internal FSEVENT_CALLBACK(hotloader_handler)
{
/* NOTE(koekeishiya): We sometimes get two events upon file save. */
struct hotloader *hotloader = (struct hotloader *) context;
- char **files = (char **) paths;
+ char **files = (char **) file_paths;
- struct watched_file *watch_info;
- for (unsigned index = 0; index < count; ++index) {
- char *absolutepath = files[index];
- if ((watch_info = hotloader_watched_file(hotloader, absolutepath))) {
- hotloader->callback(absolutepath, watch_info->directory, watch_info->filename);
+ 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;
+ }
}
}
}
-void hotloader_add_file(struct hotloader *hotloader, char *file)
+internal inline void
+hotloader_add_watched_entry(struct hotloader *hotloader, struct watched_entry entry)
{
- if (!hotloader->enabled) {
- char *real_path = resolve_symlink(file);
- if (real_path) {
- struct watched_file watch_info;
- watch_info.directory = file_directory(real_path);
- watch_info.filename = file_name(real_path);
+ if (!hotloader->watch_list) {
+ hotloader->watch_capacity = 32;
+ hotloader->watch_list = (struct watched_entry *) malloc(hotloader->watch_capacity * sizeof(struct watched_entry));
+ }
- if (real_path != file) {
- free(real_path);
- }
+ 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;
+}
- hotloader->watch_list[hotloader->watch_count++] = watch_info;
- printf("hotload: watching file '%s' in directory '%s'\n", watch_info.filename, watch_info.directory);
- } else {
- fprintf(stderr, "hotload: could not watch file '%s'\n", file);
+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;
- }
+ 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,
- hotloader->watch_list[index].directory,
+ directory,
kCFStringEncodingUTF8);
}
- FSEventStreamContext context = {};
- context.info = (void *) hotloader;
+ FSEventStreamContext context = {
+ .info = hotloader
+ };
+
+ hotloader->path = (CFArrayRef) CFArrayCreate(NULL,
+ (const void **) string_refs,
+ hotloader->watch_count,
+ &kCFTypeArrayCallBacks);
+
+ hotloader->flags = kFSEventStreamCreateFlagNoDefer |
+ kFSEventStreamCreateFlagFileEvents;
- hotloader->enabled = true;
- hotloader->callback = callback;
- hotloader->path = (CFArrayRef) CFArrayCreate(NULL, (const void **) string_refs, hotloader->watch_count, &kCFTypeArrayCallBacks);
- hotloader->flags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents;
hotloader->stream = FSEventStreamCreate(NULL,
hotloader_handler,
&context,
@@ -153,27 +273,43 @@ bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback)
kFSEventStreamEventIdSinceNow,
0.5,
hotloader->flags);
- FSEventStreamScheduleWithRunLoop(hotloader->stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
+
+ 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) {
- FSEventStreamStop(hotloader->stream);
- FSEventStreamInvalidate(hotloader->stream);
- FSEventStreamRelease(hotloader->stream);
-
- CFIndex count = CFArrayGetCount(hotloader->path);
- for (unsigned index = 0; index < count; ++index) {
- CFStringRef string_ref = (CFStringRef) CFArrayGetValueAtIndex(hotloader->path, index);
- free(hotloader->watch_list[index].directory);
- free(hotloader->watch_list[index].filename);
- CFRelease(string_ref);
- }
+ if (!hotloader->enabled) return;
+
+ FSEventStreamStop(hotloader->stream);
+ FSEventStreamInvalidate(hotloader->stream);
+ FSEventStreamRelease(hotloader->stream);
- CFRelease(hotloader->path);
- memset(hotloader, 0, sizeof(struct hotloader));
+ 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));
}
diff --git a/src/hotload.h b/src/hotload.h
index ad782fb..1d3921b 100644
--- a/src/hotload.h
+++ b/src/hotload.h
@@ -1,18 +1,16 @@
#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_file
-{
- char *directory;
- char *filename;
-};
-
+struct watched_entry;
struct hotloader
{
FSEventStreamEventFlags flags;
@@ -21,12 +19,14 @@ struct hotloader
bool enabled;
hotloader_callback *callback;
- struct watched_file watch_list[32];
+ 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);
-void hotloader_add_file(struct hotloader *hotloader, char *file);
+bool hotloader_add_catalog(struct hotloader *hotloader, const char *directory, const char *extension);
+bool hotloader_add_file(struct hotloader *hotloader, const char *file);
#endif
diff --git a/src/locale.c b/src/locale.c
index 464c3ba..64f8b04 100644
--- a/src/locale.c
+++ b/src/locale.c
@@ -1,24 +1,25 @@
#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)))
+
#define internal static
-#define local_persist static
+#define global static
-internal struct table keymap_table;
+global struct table keymap_table;
+global char **keymap_keys;
internal char *
-copy_cf_string_to_c(CFStringRef string)
+copy_cfstring(CFStringRef string)
{
- CFStringEncoding encoding = kCFStringEncodingUTF8;
- CFIndex length = CFStringGetLength(string);
- CFIndex bytes = CFStringGetMaximumSizeForEncoding(length, encoding);
- char *result = malloc(bytes + 1);
+ CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
+ char *result = malloc(num_bytes + 1);
// NOTE(koekeishiya): Boolean: typedef -> unsigned char; false = 0, true != 0
- Boolean success = CFStringGetCString(string, result, bytes + 1, encoding);
- if (!success) {
+ if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) {
free(result);
result = NULL;
}
@@ -51,43 +52,45 @@ same_keymap(const char *a, const char *b)
return *a == '\0' && *b == '\0';
}
-internal CFStringRef
-cfstring_from_keycode(UCKeyboardLayout *keyboard_layout, CGKeyCode keycode)
+internal void
+free_keycode_map(void)
{
- UInt32 dead_key_state = 0;
- UniCharCount max_string_length = 255;
- UniCharCount string_length = 0;
- UniChar unicode_string[max_string_length];
-
- OSStatus status = UCKeyTranslate(keyboard_layout, keycode,
- kUCKeyActionDown, 0,
- LMGetKbdType(), 0,
- &dead_key_state,
- max_string_length,
- &string_length,
- unicode_string);
-
- if (string_length == 0 && dead_key_state) {
- status = UCKeyTranslate(keyboard_layout, kVK_Space,
- kUCKeyActionDown, 0,
- LMGetKbdType(), 0,
- &dead_key_state,
- max_string_length,
- &string_length,
- unicode_string);
- }
-
- if (string_length > 0 && status == noErr) {
- return CFStringCreateWithCharacters(NULL, unicode_string, string_length);
+ for (int i = 0; i < buf_len(keymap_keys); ++i) {
+ free(keymap_keys[i]);
}
- return NULL;
+ buf_free(keymap_keys);
+ keymap_keys = NULL;
}
+internal 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()
+bool initialize_keycode_map(void)
{
+ UniChar chars[255];
+ UniCharCount len;
+ UInt32 state;
+
TISInputSourceRef keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
CFDataRef uchr = (CFDataRef) TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
CFRelease(keyboard);
@@ -95,20 +98,33 @@ bool initialize_keycode_map()
UCKeyboardLayout *keyboard_layout = (UCKeyboardLayout *) CFDataGetBytePtr(uchr);
if (!keyboard_layout) return false;
+ free_keycode_map();
+ table_free(&keymap_table);
table_init(&keymap_table,
- 131,
+ 61,
(table_hash_func) hash_keymap,
(table_compare_func) same_keymap);
- for (unsigned index = 0; index < 128; ++index) {
- CFStringRef key_string = cfstring_from_keycode(keyboard_layout, index);
- if (!key_string) continue;
-
- char *c_key_string = copy_cf_string_to_c(key_string);
- CFRelease(key_string);
- if (!c_key_string) continue;
-
- table_add(&keymap_table, c_key_string, (void *)index);
+ 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;
diff --git a/src/locale.h b/src/locale.h
index 653bcfd..7a1ec6e 100644
--- a/src/locale.h
+++ b/src/locale.h
@@ -3,7 +3,15 @@
#include <stdint.h>
-bool initialize_keycode_map();
+#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
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..88c12a2
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,42 @@
+#ifndef SKHD_LOG_H
+#define SKHD_LOG_H
+
+#define internal static
+#define global static
+
+global bool verbose;
+
+internal inline void
+debug(const char *format, ...)
+{
+ if (!verbose) return;
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+}
+
+internal inline void
+warn(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+internal inline void
+error(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ exit(EXIT_FAILURE);
+}
+
+#undef internal
+#undef global
+
+#endif
diff --git a/src/parse.c b/src/parse.c
index 79f4aba..e2b4c4d 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -22,8 +22,6 @@ find_or_init_default_mode(struct parser *parser)
}
default_mode = malloc(sizeof(struct mode));
- default_mode->line = -1;
- default_mode->cursor = -1;
default_mode->name = copy_string("default");
table_init(&default_mode->hotkey_map, 131,
@@ -74,13 +72,63 @@ keycode_from_hex(char *hex)
return result;
}
-internal char *
-parse_command(struct parser *parser)
+internal void
+parse_command(struct parser *parser, struct hotkey *hotkey)
{
struct token command = parser_previous(parser);
char *result = copy_string_count(command.text, command.length);
- printf("\tcmd: '%s'\n", result);
- return result;
+ debug("\tcmd: '%s'\n", result);
+ buf_push(hotkey->command, result);
+}
+
+internal 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");
+ }
+}
+
+internal 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");
+ }
}
internal uint32_t
@@ -90,7 +138,7 @@ parse_key_hex(struct parser *parser)
char *hex = copy_string_count(key.text, key.length);
uint32_t keycode = keycode_from_hex(hex);
free(hex);
- printf("\tkey: '%.*s' (0x%02x)\n", key.length, key.text, keycode);
+ debug("\tkey: '%.*s' (0x%02x)\n", key.length, key.text, keycode);
return keycode;
}
@@ -100,7 +148,7 @@ parse_key(struct parser *parser)
uint32_t keycode;
struct token key = parser_previous(parser);
keycode = keycode_from_char(*key.text);
- printf("\tkey: '%c' (0x%02x)\n", *key.text, keycode);
+ debug("\tkey: '%c' (0x%02x)\n", *key.text, keycode);
return keycode;
}
@@ -146,7 +194,7 @@ parse_key_literal(struct parser *parser, struct hotkey *hotkey)
if (token_equals(key, literal_keycode_str[i])) {
handle_implicit_literal_flags(hotkey, i);
hotkey->key = literal_keycode_value[i];
- printf("\tkey: '%.*s' (0x%02x)\n", key.length, key.text, hotkey->key);
+ debug("\tkey: '%.*s' (0x%02x)\n", key.length, key.text, hotkey->key);
break;
}
}
@@ -158,7 +206,7 @@ internal enum hotkey_flag modifier_flags_value[] =
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_Fn, Hotkey_Flag_Hyper, Hotkey_Flag_Meh,
};
internal uint32_t
@@ -170,7 +218,7 @@ parse_modifier(struct parser *parser)
for (int i = 0; i < array_count(modifier_flags_str); ++i) {
if (token_equals(modifier, modifier_flags_str[i])) {
flags |= modifier_flags_value[i];
- printf("\tmod: '%s'\n", modifier_flags_str[i]);
+ debug("\tmod: '%s'\n", modifier_flags_str[i]);
break;
}
}
@@ -179,7 +227,7 @@ parse_modifier(struct parser *parser)
if (parser_match(parser, Token_Modifier)) {
flags |= parse_modifier(parser);
} else {
- parser_report_error(parser, Error_Unexpected_Token, "expected modifier");
+ parser_report_error(parser, parser_peek(parser), "expected modifier\n");
}
}
@@ -198,18 +246,18 @@ parse_mode(struct parser *parser, struct hotkey *hotkey)
if (!mode && token_equals(identifier, "default")) {
mode = find_or_init_default_mode(parser);
} else if (!mode) {
- parser_report_error(parser, Error_Undeclared_Ident, "undeclared identifier");
+ parser_report_error(parser, identifier, "undeclared identifier\n");
return;
}
buf_push(hotkey->mode_list, mode);
- printf("\tmode: '%s'\n", mode->name);
+ 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, Error_Unexpected_Token, "expected identifier");
+ parser_report_error(parser, parser_peek(parser), "expected identifier\n");
}
}
}
@@ -221,7 +269,7 @@ parse_hotkey(struct parser *parser)
memset(hotkey, 0, sizeof(struct hotkey));
bool found_modifier;
- printf("hotkey :: #%d {\n", parser->current_token.line);
+ debug("hotkey :: #%d {\n", parser->current_token.line);
if (parser_match(parser, Token_Identifier)) {
parse_mode(parser, hotkey);
@@ -232,7 +280,7 @@ parse_hotkey(struct parser *parser)
if (buf_len(hotkey->mode_list) > 0) {
if (!parser_match(parser, Token_Insert)) {
- parser_report_error(parser, Error_Unexpected_Token, "expected '<'");
+ parser_report_error(parser, parser_peek(parser), "expected '<'\n");
goto err;
}
} else {
@@ -248,7 +296,7 @@ parse_hotkey(struct parser *parser)
if (found_modifier) {
if (!parser_match(parser, Token_Dash)) {
- parser_report_error(parser, Error_Unexpected_Token, "expected '-'");
+ parser_report_error(parser, parser_peek(parser), "expected '-'\n");
goto err;
}
}
@@ -260,7 +308,7 @@ parse_hotkey(struct parser *parser)
} else if (parser_match(parser, Token_Literal)) {
parse_key_literal(parser, hotkey);
} else {
- parser_report_error(parser, Error_Unexpected_Token, "expected key-literal");
+ parser_report_error(parser, parser_peek(parser), "expected key-literal\n");
goto err;
}
@@ -269,20 +317,23 @@ parse_hotkey(struct parser *parser)
}
if (parser_match(parser, Token_Command)) {
- hotkey->command = parse_command(parser);
+ 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)) {
- hotkey->flags |= Hotkey_Flag_Activate;
- hotkey->command = parse_command(parser);
- if (!table_find(parser->mode_map, hotkey->command)) {
- parser_report_error(parser, Error_Undeclared_Ident, "undeclared identifier");
+ parse_activate(parser, hotkey);
+ if (parser->error) {
goto err;
}
} else {
- parser_report_error(parser, Error_Unexpected_Token, "expected ':' followed by command or ';' followed by mode");
+ parser_report_error(parser, parser_peek(parser), "expected ':' followed by command or ';' followed by mode\n");
goto err;
}
- printf("}\n");
+ debug("}\n");
return hotkey;
err:
@@ -296,8 +347,6 @@ parse_mode_decl(struct parser *parser)
struct mode *mode = malloc(sizeof(struct mode));
struct token identifier = parser_previous(parser);
- mode->line = identifier.line;
- mode->cursor = identifier.cursor;
mode->name = copy_string_count(identifier.text, identifier.length);
table_init(&mode->hotkey_map, 131,
@@ -321,32 +370,96 @@ parse_mode_decl(struct parser *parser)
void parse_declaration(struct parser *parser)
{
- struct mode *mode;
parser_match(parser, Token_Decl);
if (parser_match(parser, Token_Identifier)) {
- mode = parse_mode_decl(parser);
+ struct token identifier = parser_previous(parser);
+ struct mode *mode = parse_mode_decl(parser);
if (table_find(parser->mode_map, mode->name)) {
- parser_report_error(parser, Error_Duplicate_Ident,
- "#%d:%d duplicate declaration '%s'\n",
- mode->line, mode->cursor, mode->name);
+ parser_report_error(parser, identifier, "duplicate declaration '%s'\n", mode->name);
} else {
table_add(parser->mode_map, mode->name, mode);
}
} else {
- parser_report_error(parser, Error_Unexpected_Token, "expected identifier");
+ 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_config(struct parser *parser)
+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) {
- free_mode_map(parser->mode_map);
- return;
- }
+ if (parser->error) break;
if ((parser_check(parser, Token_Identifier)) ||
(parser_check(parser, Token_Modifier)) ||
@@ -361,10 +474,64 @@ void parse_config(struct parser *parser)
}
} 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, Error_Unexpected_Token, "expected decl, modifier or key-literal");
+ 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
@@ -411,33 +578,58 @@ bool parser_match(struct parser *parser, enum token_type type)
return false;
}
-void parser_report_error(struct parser *parser, enum parse_error_type error_type, const char *format, ...)
+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);
+ }
- if (error_type == Error_Unexpected_Token) {
- fprintf(stderr, "#%d:%d ", parser->current_token.line, parser->current_token.cursor);
- vfprintf(stderr, format, args);
- fprintf(stderr, ", but got '%.*s'\n", parser->current_token.length, parser->current_token.text);
- } else if (error_type == Error_Undeclared_Ident) {
- fprintf(stderr, "#%d:%d ", parser->previous_token.line, parser->previous_token.cursor);
- vfprintf(stderr, format, args);
- fprintf(stderr, " '%.*s'\n", parser->previous_token.length, parser->previous_token.text);
- } else if (error_type == Error_Duplicate_Ident) {
- vfprintf(stderr, format, args);
+ free(load.file);
}
+ buf_free(parser->load_directives);
- va_end(args);
- parser->error = true;
+ if (error) {
+ free_mode_map(parser->mode_map);
+ free_blacklist(parser->blacklst);
+ }
}
-bool parser_init(struct parser *parser, struct table *mode_map, char *file)
+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;
@@ -445,6 +637,14 @@ bool parser_init(struct parser *parser, struct table *mode_map, char *file)
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);
diff --git a/src/parse.h b/src/parse.h
index d3543b9..6576f7a 100644
--- a/src/parse.h
+++ b/src/parse.h
@@ -4,25 +4,27 @@
#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;
};
-enum parse_error_type
-{
- Error_Unexpected_Token,
- Error_Undeclared_Ident,
- Error_Duplicate_Ident,
-};
-
-
-void parse_config(struct parser *parser);
+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);
@@ -30,8 +32,9 @@ 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, char *file);
+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, enum parse_error_type error_type, const char *format, ...);
+void parser_report_error(struct parser *parser, struct token token, const char *format, ...);
#endif
diff --git a/src/sbuffer.h b/src/sbuffer.h
index 92ac811..3558bbc 100644
--- a/src/sbuffer.h
+++ b/src/sbuffer.h
@@ -24,7 +24,9 @@ struct buf_hdr
#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)
+#define internal static
+
+internal void *buf__grow_f(const void *buf, size_t new_len, size_t elem_size)
{
size_t new_cap = MAX(1 + 2*buf_cap(buf), new_len);
size_t new_size = OFFSETOF(struct buf_hdr, buf) + new_cap*elem_size;
@@ -36,4 +38,6 @@ static void *buf__grow_f(const void *buf, size_t new_len, size_t elem_size)
return new_hdr->buf;
}
+#undef internal
+
#endif
diff --git a/src/skhd.c b/src/skhd.c
index 75fecd1..536ca03 100644
--- a/src/skhd.c
+++ b/src/skhd.c
@@ -5,88 +5,145 @@
#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 "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 "mtouch.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 "mtouch.c"
-#define internal static
-extern bool CGSIsSecureEventInputSet();
+extern CFDictionaryRef CGSCopyCurrentSessionDictionary(void);
+extern bool CGSIsSecureEventInputSet(void);
#define secure_keyboard_entry_enabled CGSIsSecureEventInputSet
-#if 0
-#define BEGIN_TIMED_BLOCK() \
- clock_t timed_block_begin = clock()
-#define END_TIMED_BLOCK() \
- clock_t timed_block_end = clock(); \
- double timed_block_elapsed = ((timed_block_end - timed_block_begin) / (double)CLOCKS_PER_SEC) * 1000.0f; \
- printf("elapsed time: %.4fms\n", timed_block_elapsed)
-#else
-#define BEGIN_TIMED_BLOCK()
-#define END_TIMED_BLOCK()
-#endif
-
-internal unsigned major_version = 0;
-internal unsigned minor_version = 2;
-internal unsigned patch_version = 1;
-
-internal struct mode *current_mode;
-internal struct table mode_map;
-internal char *config_file;
+#define internal static
+#define global static
-internal void
-error(const char *format, ...)
-{
- va_list args;
- va_start(args, format);
- vfprintf(stderr, format, args);
- va_end(args);
- exit(EXIT_FAILURE);
-}
+#define SKHD_CONFIG_FILE ".skhdrc"
+#define SKHD_PIDFILE_FMT "/tmp/skhd_%s.pid"
-internal void
-warn(const char *format, ...)
-{
- va_list args;
- va_start(args, format);
- vfprintf(stderr, format, args);
- va_end(args);
-}
+global unsigned major_version = 0;
+global unsigned minor_version = 3;
+global unsigned patch_version = 4;
+
+global struct carbon_event carbon;
+global struct event_tap event_tap;
+global struct hotloader hotloader;
+global struct mode *current_mode;
+global struct table mode_map;
+global struct table blacklst;
+global bool thwart_hotloader;
+global char *config_file;
+
+internal HOTLOADER_CALLBACK(config_handler);
internal void
parse_config_helper(char *absolutepath)
{
struct parser parser;
- if (parser_init(&parser, &mode_map, absolutepath)) {
- parse_config(&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");
}
internal HOTLOADER_CALLBACK(config_handler)
{
+ BEGIN_TIMED_BLOCK("hotload_config");
+ debug("skhd: config-file has been modified.. reloading config\n");
free_mode_map(&mode_map);
- parse_config_helper(absolutepath);
+ free_blacklist(&blacklst);
+ parse_config_helper(config_file);
+ END_TIMED_BLOCK();
+}
+
+internal 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();
+}
+
+internal 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;
}
internal EVENT_TAP_CALLBACK(key_handler)
@@ -94,23 +151,28 @@ internal EVENT_TAP_CALLBACK(key_handler)
switch (type) {
case kCGEventTapDisabledByTimeout:
case kCGEventTapDisabledByUserInput: {
- printf("skhd: restarting event-tap\n");
+ 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);
+ 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 systemkey systemkey = create_systemkey(event);
- if (systemkey.intercept) {
- bool result = find_and_exec_hotkey(&systemkey.eventkey, &mode_map, &current_mode);
+ 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;
@@ -146,25 +208,137 @@ internal MULTITOUCH_CALLBACK(touch_handler)
return 0;
}
+internal 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();
+}
+
+internal 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;
+}
+
+internal 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(koekeishiya): we intentionally leave the handle open,
+ // as calling close(..) will release the lock we just acquired.
+
+ debug("skhd: successfully created pid-file..\n");
+}
+
internal bool
parse_arguments(int argc, char **argv)
{
int option;
- const char *short_option = "vc:";
+ const char *short_option = "VPvc:k:t:rho";
struct option long_option[] = {
+ { "verbose", no_argument, NULL, 'V' },
+ { "profile", no_argument, NULL, 'P' },
{ "version", no_argument, NULL, 'v' },
{ "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 'v': {
printf("skhd version %d.%d.%d\n", major_version, minor_version, patch_version);
return true;
} break;
case 'c': {
- config_file = strdup(optarg);
+ config_file = copy_string(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;
}
}
@@ -173,7 +347,7 @@ parse_arguments(int argc, char **argv)
}
internal bool
-check_privileges()
+check_privileges(void)
{
bool result;
const void *keys[] = { kAXTrustedCheckOptionPrompt };
@@ -192,31 +366,63 @@ check_privileges()
}
internal void
-set_config_path()
+use_default_config_path(void)
{
char *home = getenv("HOME");
- if (home) {
- int length = strlen(home) + strlen("/.skhdrc");
- config_file = (char *) malloc(length + 1);
- strcpy(config_file, home);
- strcat(config_file, "/.skhdrc");
- } else {
- config_file = strdup(".skhdrc");
+ if (!home) {
+ error("skhd: could not locate config because 'env HOME' was not set! abort..\n");
}
+
+ int home_len = strlen(home);
+ int config_len = strlen("/"SKHD_CONFIG_FILE);
+ int length = home_len + config_len;
+ config_file = malloc(length + 1);
+ memcpy(config_file, home, home_len);
+ memcpy(config_file + home_len, "/"SKHD_CONFIG_FILE, config_len);
+ config_file[length] = '\0';
}
-int main(int argc, char **argv)
+internal void
+dump_secure_keyboard_entry_process_info(void)
{
- if (parse_arguments(argc, argv)) {
- return EXIT_SUCCESS;
+ CFDictionaryRef session;
+ CFNumberRef pid_ref;
+ char *process_name;
+ pid_t pid;
+
+ session = CGSCopyCurrentSessionDictionary();
+ if (!session) goto err;
+
+ pid_ref = (CFNumberRef) CFDictionaryGetValue(session, CFSTR("kCGSSessionSecureInputPID"));
+ if (!pid_ref) goto err;
+
+ CFNumberGetValue(pid_ref, CFNumberGetType(pid_ref), &pid);
+ process_name = find_process_name_for_pid(pid);
+
+ if (process_name) {
+ error("skhd: secure keyboard entry is enabled by (%lld) '%s'! abort..\n", pid, process_name);
}
+err:
+ error("skhd: secure keyboard entry is enabled! abort..\n");
+}
+
+int main(int argc, char **argv)
+{
if (getuid() == 0 || geteuid() == 0) {
error("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()) {
- error("skhd: secure keyboard entry is enabled! abort..\n");
+ dump_secure_keyboard_entry_process_info();
}
if (!check_privileges()) {
@@ -227,24 +433,42 @@ int main(int argc, char **argv)
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) {
- set_config_path();
+ use_default_config_path();
}
- printf("skhd: using config '%s'\n", config_file);
- table_init(&mode_map, 13, (table_hash_func) hash_mode, (table_compare_func) same_mode);
- parse_config_helper(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();
- struct event_tap event_tap = { .mask = (1 << kCGEventKeyDown) | (1 << NX_SYSDEFINED) };
+ 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();
struct multitouch multitouch;
multitouch_begin(&multitouch, touch_handler);
-
- struct hotloader hotloader = {};
- hotloader_add_file(&hotloader, config_file);
- hotloader_begin(&hotloader, config_handler);
+ END_SCOPED_TIMED_BLOCK();
CFRunLoopRun();
return EXIT_SUCCESS;
diff --git a/src/synthesize.c b/src/synthesize.c
new file mode 100644
index 0000000..6eddd6f
--- /dev/null
+++ b/src/synthesize.c
@@ -0,0 +1,93 @@
+#include <Carbon/Carbon.h>
+
+#include "synthesize.h"
+#include "locale.h"
+#include "parse.h"
+#include "hotkey.h"
+
+#define internal static
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated"
+
+internal inline void
+create_and_post_keyevent(uint16_t key, bool pressed)
+{
+ CGPostKeyboardEvent((CGCharCode)0, (CGKeyCode)key, pressed);
+}
+
+internal 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
diff --git a/src/synthesize.h b/src/synthesize.h
new file mode 100644
index 0000000..4a2b262
--- /dev/null
+++ b/src/synthesize.h
@@ -0,0 +1,7 @@
+#ifndef SKHD_SYNTHESIZE_H
+#define SKHD_SYNTHESIZE_H
+
+void synthesize_key(char *key_string);
+void synthesize_text(char *text);
+
+#endif
diff --git a/src/timing.h b/src/timing.h
new file mode 100644
index 0000000..44cde56
--- /dev/null
+++ b/src/timing.h
@@ -0,0 +1,76 @@
+#ifndef MACOS_TIMING_H
+#define MACOS_TIMING_H
+
+#include <stdint.h>
+#include <CoreAudio/CoreAudio.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)
+
+#define internal static
+#define global static
+
+global bool profile;
+
+struct timing_info
+{
+ char *note;
+ uint64_t start;
+ uint64_t end;
+ float ms;
+};
+
+void begin_timing(struct timing_info *timing, char *note);
+void end_timing(struct timing_info *timing);
+
+internal inline uint64_t
+macos_get_wall_clock(void)
+{
+ uint64_t result = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+ return result;
+}
+
+internal inline float
+macos_get_seconds_elapsed(uint64_t start, uint64_t end)
+{
+ float result = ((float)(end - start) / 1000.0f) / 1000000.0f;
+ return result;
+}
+
+internal inline float
+macos_get_milliseconds_elapsed(uint64_t start, uint64_t end)
+{
+ float result = 1000.0f * macos_get_seconds_elapsed(start, end);
+ return result;
+}
+
+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);
+ }
+}
+
+#undef internal
+#undef global
+
+#endif
diff --git a/src/tokenize.c b/src/tokenize.c
index 5040a7c..eaf0e3a 100644
--- a/src/tokenize.c
+++ b/src/tokenize.c
@@ -63,6 +63,30 @@ eat_hex(struct tokenizer *tokenizer)
}
}
+internal void
+eat_string(struct tokenizer *tokenizer)
+{
+ /*
+ * NOTE(koekeishiya): 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);
+ }
+}
+
+internal void
+eat_option(struct tokenizer *tokenizer)
+{
+ while (*tokenizer->at && !isspace(*tokenizer->at)) {
+ advance(tokenizer);
+ }
+}
+
internal inline bool
isidentifier(char c)
{
@@ -130,6 +154,30 @@ get_token(struct tokenizer *tokenizer)
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);
diff --git a/src/tokenize.h b/src/tokenize.h
index d6bd4bf..52d76fc 100644
--- a/src/tokenize.h
+++ b/src/tokenize.h
@@ -1,29 +1,31 @@
#ifndef SKHD_TOKENIZE_H
#define SKHD_TOKENIZE_H
-static const char *modifier_flags_str[] =
+#define global static
+
+global const char *modifier_flags_str[] =
{
"alt", "lalt", "ralt",
"shift", "lshift", "rshift",
"cmd", "lcmd", "rcmd",
"ctrl", "lctrl", "rctrl",
- "fn", "hyper",
+ "fn", "hyper", "meh",
};
-static const char *literal_keycode_str[] =
+global 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",
+ "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",
@@ -31,6 +33,8 @@ static const char *literal_keycode_str[] =
"brightness_down", "illumination_up", "illumination_down"
};
+#undef global
+
enum token_type
{
Token_Identifier,
@@ -49,6 +53,13 @@ enum token_type
Token_Dash,
Token_Arrow,
Token_Capture,
+ Token_Unbound,
+ Token_Wildcard,
+ Token_String,
+ Token_Option,
+
+ Token_BeginList,
+ Token_EndList,
Token_Unknown,
Token_EndOfStream,