From 9757d56fc1cfd34edf3027bdf8c2b1a1fbbe51ae Mon Sep 17 00:00:00 2001 From: Miigon Date: Fri, 7 Jul 2023 10:35:07 +0800 Subject: [PATCH] implement .alias for key and modifier aliases --- README.md | 19 ++++ examples/skhdrc | 19 ++++ src/hashtable.h | 14 ++- src/hotkey.c | 22 +++-- src/hotkey.h | 2 + src/parse.c | 220 ++++++++++++++++++++++++++++++----------------- src/parse.h | 6 +- src/skhd.c | 24 ++++-- src/synthesize.c | 28 +++--- src/synthesize.h | 2 +- src/tokenize.c | 9 ++ src/tokenize.h | 2 + 12 files changed, 258 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index a0acd6b..4f34fe6 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,25 @@ command = command is executed through '$SHELL -c' and an EOL character signifies the end of the bind. ``` +Aliases can also be used anywhere a modifier or a key is expected: +``` +# alias as modifier +.alias $hyper cmd + alt + ctrl +$hyper - t : open -a Terminal.app + +# alias as key +.alias $capslock 0x39 +ctrl - $capslock : open -a Notes.app + +# alias as mod-key +.alias $exclamation_mark shift - 1 +$hyper - $exclamation_mark : open -a "System Preferences.app" + +# alias within alias +.alias $terminal_key $hyper + shift - t +$terminal_key : open -a Terminal.app +``` + General options that configure the behaviour of **skhd**: ``` # specify a file that should be included as an additional config-file. diff --git a/examples/skhdrc b/examples/skhdrc index 28e99fe..aa40d9b 100644 --- a/examples/skhdrc +++ b/examples/skhdrc @@ -113,3 +113,22 @@ cmd + shift - return : ~/Scripts/qtb.sh # open mpv cmd - m : open -na /Applications/mpv.app $(pbpaste) + +################### +# using aliases + +# alias as modifier +.alias $hyper cmd + alt + ctrl +$hyper - t : open -a Terminal.app + +# alias as key +.alias $capslock 0x39 +ctrl - $capslock : open -a Notes.app + +# alias as mod-key +.alias $exclamation_mark shift - 1 +$hyper - $exclamation_mark : open -a "System Preferences.app" + +# alias within alias +.alias $terminal_key $hyper + shift - t +$terminal_key : open -a Terminal.app diff --git a/src/hashtable.h b/src/hashtable.h index f593432..260509e 100644 --- a/src/hashtable.h +++ b/src/hashtable.h @@ -88,11 +88,11 @@ void *table_find(struct table *table, void *key) return bucket ? bucket->value : NULL; } -void table_add(struct table *table, void *key, void *value) +void table_newkeyvalue(struct table *table, void *key, void *value, bool do_replace) { struct bucket **bucket = table_get_bucket(table, key); if (*bucket) { - if (!(*bucket)->value) { + if (do_replace || !(*bucket)->value) { (*bucket)->value = value; } } else { @@ -101,6 +101,16 @@ void table_add(struct table *table, void *key, void *value) } } +void table_add(struct table *table, void *key, void *value) +{ + table_newkeyvalue(table, key, value, false); +} + +void table_replace(struct table *table, void *key, void *value) +{ + table_newkeyvalue(table, key, value, true); +} + void *table_remove(struct table *table, void *key) { void *result = NULL; diff --git a/src/hotkey.c b/src/hotkey.c index 6ed79f5..f4bf1b8 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -219,10 +219,8 @@ next:; free(mode); } - if (mode_count) { - free(modes); - buf_free(freed_pointers); - } + free(modes); + buf_free(freed_pointers); } void free_blacklist(struct table *blacklst) @@ -230,9 +228,21 @@ 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); + free(items[index]); } + + free(items); +} + +void free_alias_map(struct table *alias_map) +{ + int count; + void **items = table_reset(alias_map, &count); + for (int index = 0; index < count; ++index) { + free(items[index]); + } + + free(items); } static void diff --git a/src/hotkey.h b/src/hotkey.h index 78aba3f..9a103fa 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -43,6 +43,7 @@ enum hotkey_flag Hotkey_Flag_LControl = (1 << 10), Hotkey_Flag_RControl = (1 << 11), Hotkey_Flag_Fn = (1 << 12), + Hotkey_Flag_Modifier = ((Hotkey_Flag_Fn << 1) - 1), Hotkey_Flag_Passthrough = (1 << 13), Hotkey_Flag_Activate = (1 << 14), Hotkey_Flag_NX = (1 << 15), @@ -109,6 +110,7 @@ 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 free_alias_map(struct table *alias_map); void init_shell(void); diff --git a/src/parse.c b/src/parse.c index d9c453a..848ac58 100644 --- a/src/parse.c +++ b/src/parse.c @@ -3,6 +3,7 @@ #include "locale.h" #include "hotkey.h" #include "hashtable.h" +#include "log.h" #include #include @@ -55,12 +56,17 @@ read_file(const char *file) } static char * -copy_string_count(char *s, int length) +copy_string_count_nomalloc(char *dst, const char *s, int length) { + memcpy(dst, s, length); + dst[length] = '\0'; + return dst; +} + +static char * +copy_string_count(const char *s, int length) { char *result = malloc(length + 1); - memcpy(result, s, length); - result[length] = '\0'; - return result; + return copy_string_count_nomalloc(result, s, length); } static uint32_t @@ -134,9 +140,9 @@ static uint32_t parse_key_hex(struct parser *parser) { struct token key = parser_previous(parser); - char *hex = copy_string_count(key.text, key.length); + char hex[key.length+1]; + copy_string_count_nomalloc(hex, 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; } @@ -208,29 +214,73 @@ static enum hotkey_flag modifier_flags_value[] = Hotkey_Flag_Fn, Hotkey_Flag_Hyper, Hotkey_Flag_Meh, }; -static uint32_t -parse_modifier(struct parser *parser) +#define INVALID_KEY UINT32_MAX + +static void expand_alias(struct parser *parser, struct hotkey *dst, struct token alias, bool *contains_mod) { - struct token modifier = parser_previous(parser); - uint32_t flags = 0; + if (parser->alias_map == NULL) { + // parser is in text mode (eg. `synthesize_key()`) + parser_report_error(parser, alias, "aliases not supported in this mode\n"); + return; + } + char alias_name[alias.length+1]; + copy_string_count_nomalloc(alias_name, alias.text, alias.length); - 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; + debug("\tuse_alias: $%s\n", alias_name); + + struct hotkey *alias_hotkey = table_find(parser->alias_map, alias_name); + if (alias_hotkey == NULL) { + parser_report_error(parser, alias, "undefined alias $%s\n", alias_name); + return; + } + if (alias_hotkey->key != INVALID_KEY) { + // alias contains keycode + if (dst->key != INVALID_KEY) { + parser_report_error(parser, alias, "multiple keycodes specified by using alias $%s\n", alias_name); + return; } + dst->key = alias_hotkey->key; } + dst->flags |= alias_hotkey->flags; + if (contains_mod) *contains_mod = (alias_hotkey->flags & Hotkey_Flag_Modifier) != 0; +} - if (parser_match(parser, Token_Plus)) { +static bool +parse_modifier(struct parser *parser, struct hotkey *hotkey) +{ + bool first_iter = true; + do { if (parser_match(parser, Token_Modifier)) { - flags |= parse_modifier(parser); + struct token modifier = parser_previous(parser); + + for (int i = 0; i < array_count(modifier_flags_str); ++i) { + if (token_equals(modifier, modifier_flags_str[i])) { + hotkey->flags |= modifier_flags_value[i]; + debug("\tmod: '%s'\n", modifier_flags_str[i]); + break; + } + } + } else if (parser_match(parser, Token_Alias)) { + // starts with an alias that might contain a modifier + struct token alias = parser_previous(parser); + bool contains_mod = false; + expand_alias(parser, hotkey, alias, &contains_mod); + if (parser->error) { + return false; + } + + if (!contains_mod && !first_iter) { + parser_report_error(parser, alias, "alias $%.*s does not contain any modifiers\n", alias.length, alias.text); + return false; + } } else { - parser_report_error(parser, parser_peek(parser), "expected modifier\n"); + if (!first_iter) parser_report_error(parser, parser_advance(parser), "expected modifier\n"); + return false; } - } - return flags; + first_iter = false; + } while(parser_match(parser, Token_Plus)); + return true; } static void @@ -266,7 +316,6 @@ 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); @@ -286,30 +335,7 @@ parse_hotkey(struct parser *parser) 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; - } + parse_keypress(parser, hotkey, false); if (parser_match(parser, Token_Arrow)) { hotkey->flags |= Hotkey_Flag_Passthrough; @@ -440,6 +466,20 @@ void parse_option_load(struct parser *parser, struct token option) })); } +void parse_option_alias(struct parser *parser) +{ + struct token alias_token = parser_previous(parser); + char *alias_name = copy_string_count(alias_token.text, alias_token.length); + debug("\talias_name: $%s\n", alias_name); + + struct hotkey *hotkey = malloc(sizeof(struct hotkey)); + memset(hotkey, 0, sizeof(struct hotkey)); + parse_keypress(parser, hotkey, true); + + // later definition of the same alias takes predecence over previous ones. + table_replace(parser->alias_map, alias_name, hotkey); +} + void parse_option(struct parser *parser) { parser_match(parser, Token_Option); @@ -460,6 +500,15 @@ void parse_option(struct parser *parser) } else { parser_report_error(parser, option, "expected filename\n"); } + } else if (token_equals(option, "alias")) { + if (parser_match(parser, Token_Alias)) { + debug("alias :: #%d {\n", option.line); + parse_option_alias(parser); + debug("}\n"); + } else { + parser_report_error(parser, option, "expected $alias_name\n"); + } + } else { parser_report_error(parser, option, "invalid option specified\n"); } @@ -477,7 +526,8 @@ bool parse_config(struct parser *parser) (parser_check(parser, Token_Modifier)) || (parser_check(parser, Token_Literal)) || (parser_check(parser, Token_Key_Hex)) || - (parser_check(parser, Token_Key))) { + (parser_check(parser, Token_Key)) || + (parser_check(parser, Token_Alias))) { if ((hotkey = parse_hotkey(parser))) { for (int i = 0; i < buf_len(hotkey->mode_list); ++i) { mode = hotkey->mode_list[i]; @@ -502,48 +552,57 @@ bool parse_config(struct parser *parser) return true; } -struct hotkey * -parse_keypress(struct parser *parser) +bool +parse_keypress(struct parser *parser, struct hotkey *hotkey, bool allow_no_keycode) { - if ((parser_check(parser, Token_Modifier)) || + 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; + (parser_check(parser, Token_Key)) || + (parser_check(parser, Token_Alias)))) { + parser_report_error(parser, parser_peek(parser), "expected a hotkey\n"); + return false; + } - if ((found_modifier = parser_match(parser, Token_Modifier))) { - hotkey->flags = parse_modifier(parser); - if (parser->error) { - goto err; - } - } + hotkey->key = INVALID_KEY; // keycode 0 is actually a valid keycode (kVK_ANSI_A) - if (found_modifier) { - if (!parser_match(parser, Token_Dash)) { - goto err; - } - } + bool found_modifier = parse_modifier(parser, hotkey); + if (parser->error) { + return false; + } - 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; - } + if (hotkey->key != INVALID_KEY) { + // found keycode within one of the aliases while parsing modifier. + // no need to look any further for keycode. + return true; + } - return hotkey; + if (found_modifier) { + if (!parser_check(parser, Token_Dash)) { + if (allow_no_keycode) { + return true; + } + parser_report_error(parser, parser_peek(parser), "expected '-'\n"); + return false; + } + parser_advance(parser); + } - err: - free(hotkey); - return NULL; + if (parser_match(parser, Token_Key)) { + hotkey->key = parse_key(parser); + } else if (parser_match(parser, Token_Key_Hex)) { + hotkey->key = parse_key_hex(parser); + } else if (parser_match(parser, Token_Literal)) { + parse_key_literal(parser, hotkey); + } else if (parser_match(parser, Token_Alias)) { + struct token alias = parser_previous(parser); + expand_alias(parser, hotkey, alias, NULL); + } else { + parser_report_error(parser, parser_peek(parser), "expected key-literal\n"); + return false; } - return NULL; + return true; } struct token @@ -608,7 +667,7 @@ void parser_do_directives(struct parser *parser, struct hotloader *hotloader, bo 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 (parser_init(&directive_parser, parser->mode_map, parser->blacklst, parser->alias_map, load.file)) { if (!thwart_hotloader) { hotloader_add_file(hotloader, load.file); } @@ -634,7 +693,7 @@ void parser_do_directives(struct parser *parser, struct hotloader *hotloader, bo } } -bool parser_init(struct parser *parser, struct table *mode_map, struct table *blacklst, char *file) +bool parser_init(struct parser *parser, struct table *mode_map, struct table *blacklst, struct table *alias_map, char *file) { memset(parser, 0, sizeof(struct parser)); char *buffer = read_file(file); @@ -642,6 +701,7 @@ bool parser_init(struct parser *parser, struct table *mode_map, struct table *bl parser->file = file; parser->mode_map = mode_map; parser->blacklst = blacklst; + parser->alias_map = alias_map; tokenizer_init(&parser->tokenizer, buffer); parser_advance(parser); return true; diff --git a/src/parse.h b/src/parse.h index 6576f7a..abfdaac 100644 --- a/src/parse.h +++ b/src/parse.h @@ -2,6 +2,7 @@ #define SKHD_PARSE_H #include "tokenize.h" +#include "hotkey.h" #include struct load_directive @@ -19,12 +20,13 @@ struct parser struct tokenizer tokenizer; struct table *mode_map; struct table *blacklst; + struct table *alias_map; struct load_directive *load_directives; bool error; }; bool parse_config(struct parser *parser); -struct hotkey *parse_keypress(struct parser *parser); +bool parse_keypress(struct parser *parser, struct hotkey *hotkey, bool allow_no_keycode); struct token parser_peek(struct parser *parser); struct token parser_previous(struct parser *parser); @@ -32,7 +34,7 @@ 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(struct parser *parser, struct table *mode_map, struct table *blacklst, struct table * alias_map, 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, ...); diff --git a/src/skhd.c b/src/skhd.c index 02f35f0..38385ae 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -70,6 +70,7 @@ static struct hotloader hotloader; static struct mode *current_mode; static struct table mode_map; static struct table blacklst; +static struct table alias_map; static bool thwart_hotloader; static char config_file[4096]; @@ -79,7 +80,7 @@ static void parse_config_helper(char *absolutepath) { struct parser parser; - if (parser_init(&parser, &mode_map, &blacklst, absolutepath)) { + if (parser_init(&parser, &mode_map, &blacklst, &alias_map, absolutepath)) { if (!thwart_hotloader) { hotloader_end(&hotloader); hotloader_add_file(&hotloader, absolutepath); @@ -107,12 +108,18 @@ parse_config_helper(char *absolutepath) current_mode = table_find(&mode_map, "default"); } +static void free_tables() +{ + free_mode_map(&mode_map); + free_blacklist(&blacklst); + free_alias_map(&alias_map); +} + 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); + free_tables(); parse_config_helper(config_file); END_TIMED_BLOCK(); } @@ -122,8 +129,7 @@ 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); + free_tables(); parse_config_helper(config_file); } END_TIMED_BLOCK(); @@ -197,8 +203,7 @@ 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); + free_tables(); parse_config_helper(config_file); END_TIMED_BLOCK(); } @@ -329,7 +334,9 @@ static bool parse_arguments(int argc, char **argv) thwart_hotloader = true; } break; case 'k': { - synthesize_key(optarg); + if (!synthesize_key(optarg)) { + exit(EXIT_FAILURE); + } return true; } break; case 't': { @@ -503,6 +510,7 @@ int main(int argc, char **argv) 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); + table_init(&alias_map, 13, (table_hash_func) hash_string, (table_compare_func) compare_string); END_SCOPED_TIMED_BLOCK(); BEGIN_SCOPED_TIMED_BLOCK("parse_config"); diff --git a/src/synthesize.c b/src/synthesize.c index 0279f3d..b5574a9 100644 --- a/src/synthesize.c +++ b/src/synthesize.c @@ -38,27 +38,35 @@ synthesize_modifiers(struct hotkey *hotkey, bool pressed) } } -void synthesize_key(char *key_string) +bool synthesize_key(char *key_string) { - if (!initialize_keycode_map()) return; + if (!initialize_keycode_map()) return false; struct parser parser; parser_init_text(&parser, key_string); - close(1); - close(2); + if (!verbose) { + close(1); + close(2); + } - struct hotkey *hotkey = parse_keypress(&parser); - if (!hotkey) return; + struct hotkey hotkey; + memset(&hotkey, 0, sizeof(hotkey)); + if (!parse_keypress(&parser, &hotkey, false)) { + if (parser.error) { + return false; + } + } CGSetLocalEventsSuppressionInterval(0.0f); CGEnableEventStateCombining(false); - synthesize_modifiers(hotkey, true); - create_and_post_keyevent(hotkey->key, true); + synthesize_modifiers(&hotkey, true); + create_and_post_keyevent(hotkey.key, true); - create_and_post_keyevent(hotkey->key, false); - synthesize_modifiers(hotkey, false); + create_and_post_keyevent(hotkey.key, false); + synthesize_modifiers(&hotkey, false); + return true; } void synthesize_text(char *text) diff --git a/src/synthesize.h b/src/synthesize.h index 4a2b262..be4741c 100644 --- a/src/synthesize.h +++ b/src/synthesize.h @@ -1,7 +1,7 @@ #ifndef SKHD_SYNTHESIZE_H #define SKHD_SYNTHESIZE_H -void synthesize_key(char *key_string); +bool synthesize_key(char *key_string); void synthesize_text(char *text); #endif diff --git a/src/tokenize.c b/src/tokenize.c index f379a9f..76728cb 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -218,6 +218,15 @@ get_token(struct tokenizer *tokenizer) token.type = Token_Command; } } break; + case '$': { + token.text = tokenizer->at; + token.line = tokenizer->line; + token.cursor = tokenizer->cursor; + + eat_identifier(tokenizer); + token.length = tokenizer->at - token.text; + token.type = Token_Alias; + } break; default: { if (c == '0' && *tokenizer->at == 'x') { advance(tokenizer); diff --git a/src/tokenize.h b/src/tokenize.h index 13d7001..75c21c4 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -57,6 +57,8 @@ enum token_type Token_BeginList, Token_EndList, + Token_Alias, + Token_Unknown, Token_EndOfStream, };