From 94c26f456f095ec8334d9c313943c2747d27c0c2 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Sat, 7 Oct 2017 23:31:15 +0200 Subject: #15 modal support --- src/hotkey.c | 62 +++++++++++++++++++++--- src/hotkey.h | 42 ++++++++++++----- src/parse.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++--------- src/parse.h | 15 ++++-- src/skhd.c | 21 +++++---- src/tokenize.c | 27 +++++++++-- src/tokenize.h | 6 +++ 7 files changed, 265 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/hotkey.c b/src/hotkey.c index d9ebde6..fae753b 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -61,6 +61,29 @@ unsigned long hash_hotkey(struct hotkey *a) return a->key; } +bool same_mode(char *a, char *b) +{ + while (*a && *b && *a == *b) { + ++a; + ++b; + } + return *a == '\0' && *b == '\0'; +} + +unsigned long hash_mode(char *key) +{ + unsigned long hash = 0, high; + while(*key) { + hash = (hash << 4) + *key++; + high = hash & 0xF0000000; + if(high) { + hash ^= (high >> 24); + } + hash &= ~high; + } + return hash; +} + internal bool fork_and_exec(char *command) { @@ -81,26 +104,36 @@ fork_and_exec(char *command) return true; } -bool find_and_exec_hotkey(struct hotkey *eventkey, struct table *hotkey_map) +bool find_and_exec_hotkey(struct hotkey *eventkey, struct table *mode_map, struct mode **current_mode) { bool result = false; struct hotkey *hotkey; - if ((hotkey = table_find(hotkey_map, eventkey))) { - if (fork_and_exec(hotkey->command)) { + if ((hotkey = table_find(&(*current_mode)->hotkey_map, eventkey))) { + if (has_flags(hotkey, Hotkey_Flag_Activate)) { + *current_mode = table_find(mode_map, hotkey->command); + if ((*current_mode)->command) { + fork_and_exec((*current_mode)->command); + } + result = has_flags(hotkey, Hotkey_Flag_Passthrough) ? false : true; + } else if (fork_and_exec(hotkey->command)) { result = has_flags(hotkey, Hotkey_Flag_Passthrough) ? false : true; } } return result; } -void free_hotkeys(struct table *hotkey_map) +internal void +free_hotkeys(struct table *hotkey_map) { int count; void **hotkeys = table_reset(hotkey_map, &count); for (int index = 0; index < count; ++index) { struct hotkey *hotkey = (struct hotkey *) hotkeys[index]; - free(hotkey->command); - free(hotkey); + // the same hotkey can be added for multiple modes + // we need to know if this pointer was already freed + // by a mode that was destroyed before us + // free(hotkey->command); + // free(hotkey); } if (count) { @@ -108,6 +141,23 @@ void free_hotkeys(struct table *hotkey_map) } } +void free_mode_map(struct table *mode_map) +{ + int count; + void **modes = table_reset(mode_map, &count); + for (int index = 0; index < count; ++index) { + struct mode *mode = (struct mode *) modes[index]; + if (mode->command) free(mode->command); + if (mode->name) free(mode->name); + free_hotkeys(&mode->hotkey_map); + free(mode); + } + + if (count) { + free(modes); + } +} + internal void cgevent_lrmod_flag_to_hotkey_lrmod_flag(CGEventFlags flags, struct hotkey *eventkey, int mod) { diff --git a/src/hotkey.h b/src/hotkey.h index 40ea630..530520b 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -24,31 +24,45 @@ enum osx_event_mask enum hotkey_flag { - Hotkey_Flag_Alt = (1 << 0), - Hotkey_Flag_LAlt = (1 << 1), - Hotkey_Flag_RAlt = (1 << 2), - Hotkey_Flag_Shift = (1 << 3), - Hotkey_Flag_LShift = (1 << 4), - Hotkey_Flag_RShift = (1 << 5), - Hotkey_Flag_Cmd = (1 << 6), - Hotkey_Flag_LCmd = (1 << 7), - Hotkey_Flag_RCmd = (1 << 8), - Hotkey_Flag_Control = (1 << 9), + Hotkey_Flag_Alt = (1 << 0), + Hotkey_Flag_LAlt = (1 << 1), + Hotkey_Flag_RAlt = (1 << 2), + Hotkey_Flag_Shift = (1 << 3), + Hotkey_Flag_LShift = (1 << 4), + Hotkey_Flag_RShift = (1 << 5), + Hotkey_Flag_Cmd = (1 << 6), + Hotkey_Flag_LCmd = (1 << 7), + Hotkey_Flag_RCmd = (1 << 8), + Hotkey_Flag_Control = (1 << 9), Hotkey_Flag_LControl = (1 << 10), Hotkey_Flag_RControl = (1 << 11), Hotkey_Flag_Fn = (1 << 12), Hotkey_Flag_Passthrough = (1 << 13), + Hotkey_Flag_Activate = (1 << 14), Hotkey_Flag_Hyper = (Hotkey_Flag_Cmd | Hotkey_Flag_Alt | Hotkey_Flag_Shift | Hotkey_Flag_Control) }; +#include "hashtable.h" + +struct mode +{ + int line; + int cursor; + char *name; + char *command; + struct table hotkey_map; +}; + struct hotkey { uint32_t flags; uint32_t key; char *command; + int mode_count; + struct mode *mode_list[16]; }; static inline void @@ -70,13 +84,15 @@ clear_flags(struct hotkey *hotkey, uint32_t flag) hotkey->flags &= ~flag; } +bool same_mode(char *a, char *b); +unsigned long hash_mode(char *key); + bool same_hotkey(struct hotkey *a, struct hotkey *b); unsigned long hash_hotkey(struct hotkey *a); -bool find_and_exec_hotkey(struct hotkey *eventkey, struct table *hotkey_map); +bool find_and_exec_hotkey(struct hotkey *eventkey, struct table *mode_map, struct mode **current_mode); void cgeventflags_to_hotkeyflags(CGEventFlags flags, struct hotkey *eventkey); -struct table; -void free_hotkeys(struct table *hotkey_map); +void free_mode_map(struct table *mode_map); #endif diff --git a/src/parse.c b/src/parse.c index 6a12963..bf5a48d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -137,34 +137,75 @@ parse_modifier(struct parser *parser) if (parser_match(parser, Token_Modifier)) { flags |= parse_modifier(parser); } else { - parser_report_error(parser, "expected modifier"); + parser_report_error(parser, Error_Unexpected_Token, "expected modifier"); } } return flags; } +internal void +parse_mode(struct parser *parser, struct hotkey *hotkey) +{ + struct token identifier = parser_previous(parser); + + char *name = copy_string_count(identifier.text, identifier.length); + struct mode *mode = table_find(parser->mode_map, name); + free(name); + + if (!mode) { + parser_report_error(parser, Error_Undeclared_Ident, "undeclared identifier"); + return; + } + + hotkey->mode_list[hotkey->mode_count++] = mode; + printf("\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"); + return; + } + } +} + internal struct hotkey * parse_hotkey(struct parser *parser) { struct hotkey *hotkey = malloc(sizeof(struct hotkey)); - int found_modifier; + memset(hotkey, 0, sizeof(struct hotkey)); + bool found_modifier; printf("(#%d) hotkey :: {\n", parser->current_token.line); - if (parser_match(parser, Token_Modifier)) { - hotkey->flags = parse_modifier(parser); + if (parser_match(parser, Token_Identifier)) { + parse_mode(parser, hotkey); if (parser->error) { return NULL; } - found_modifier = 1; + } + + if (hotkey->mode_count > 0) { + if (!parser_match(parser, Token_Insert)) { + parser_report_error(parser, Error_Unexpected_Token, "expected '<'"); + return NULL; + } } else { - hotkey->flags = found_modifier = 0; + hotkey->mode_list[hotkey->mode_count++] = table_find(parser->mode_map, "default"); + } + + if ((found_modifier = parser_match(parser, Token_Modifier))) { + hotkey->flags = parse_modifier(parser); + if (parser->error) { + return NULL; + } } if (found_modifier) { if (!parser_match(parser, Token_Dash)) { - parser_report_error(parser, "expected '-'"); + parser_report_error(parser, Error_Unexpected_Token, "expected '-'"); return NULL; } } @@ -176,7 +217,7 @@ parse_hotkey(struct parser *parser) } else if (parser_match(parser, Token_Literal)) { parse_key_literal(parser, hotkey); } else { - parser_report_error(parser, "expected key-literal"); + parser_report_error(parser, Error_Unexpected_Token, "expected key-literal"); return NULL; } @@ -186,8 +227,15 @@ parse_hotkey(struct parser *parser) if (parser_match(parser, Token_Command)) { hotkey->command = parse_command(parser); + } 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"); + return NULL; + } } else { - parser_report_error(parser, "expected ':' followed by command"); + parser_report_error(parser, Error_Unexpected_Token, "expected ':' followed by command or ';' followed by mode"); return NULL; } @@ -196,22 +244,69 @@ parse_hotkey(struct parser *parser) return hotkey; } -void parse_config(struct parser *parser, struct table *hotkey_map) +internal struct mode * +parse_mode_decl(struct parser *parser) +{ + /* :: focus : chunkc border::color 0xff202020 */ + 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, + (table_hash_func) hash_hotkey, + (table_compare_func) same_hotkey); + + if (parser_match(parser, Token_Command)) { + mode->command = copy_string_count(parser->previous_token.text, parser->previous_token.length); + } else { + mode->command = NULL; + } + + return mode; +} + +void parse_config(struct parser *parser) { + struct mode *mode; struct hotkey *hotkey; + while (!parser_eof(parser)) { - if ((parser_check(parser, Token_Modifier)) || - (parser_check(parser, Token_Literal)) || - (parser_check(parser, Token_Key_Hex)) || - (parser_check(parser, Token_Key))) { + if (parser_match(parser, Token_Decl)) { + if (parser_match(parser, Token_Identifier)) { + 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); + return; + } else { + table_add(parser->mode_map, mode->name, mode); + } + } else { + parser_report_error(parser, Error_Unexpected_Token, "expected identifier"); + return; + } + } else if ((parser_check(parser, Token_Identifier)) || + (parser_check(parser, Token_Modifier)) || + (parser_check(parser, Token_Literal)) || + (parser_check(parser, Token_Key_Hex)) || + (parser_check(parser, Token_Key))) { hotkey = parse_hotkey(parser); if (parser->error) { - free_hotkeys(hotkey_map); + free_mode_map(parser->mode_map); return; } - table_add(hotkey_map, hotkey, hotkey); + + for (int i = 0; i < hotkey->mode_count; ++i) { + mode = hotkey->mode_list[i]; + table_add(&mode->hotkey_map, hotkey, hotkey); + } } else { - parser_report_error(parser, "expected modifier or key-literal"); + parser_report_error(parser, Error_Unexpected_Token, "expected decl, modifier or key-literal"); return; } } @@ -261,13 +356,23 @@ bool parser_match(struct parser *parser, enum token_type type) return false; } -void parser_report_error(struct parser *parser, const char *format, ...) +void parser_report_error(struct parser *parser, enum parse_error_type error_type, const char *format, ...) { va_list args; va_start(args, format); - 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); + + 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); + } + va_end(args); parser->error = true; } diff --git a/src/parse.h b/src/parse.h index 241e7ff..1c7719d 100644 --- a/src/parse.h +++ b/src/parse.h @@ -4,16 +4,25 @@ #include "tokenize.h" #include +struct table; struct parser { struct token previous_token; struct token current_token; struct tokenizer tokenizer; + struct table *mode_map; bool error; }; -struct table; -void parse_config(struct parser *parser, struct table *hotkey_map); +enum parse_error_type +{ + Error_Unexpected_Token, + Error_Undeclared_Ident, + Error_Duplicate_Ident, +}; + + +void parse_config(struct parser *parser); struct token parser_peek(struct parser *parser); struct token parser_previous(struct parser *parser); @@ -23,6 +32,6 @@ bool parser_check(struct parser *parser, enum token_type type); bool parser_match(struct parser *parser, enum token_type type); bool parser_init(struct parser *parser, char *file); void parser_destroy(struct parser *parser); -void parser_report_error(struct parser *parser, const char *format, ...); +void parser_report_error(struct parser *parser, enum parse_error_type error_type, const char *format, ...); #endif diff --git a/src/skhd.c b/src/skhd.c index 3e2b072..a8c02e6 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -42,7 +42,8 @@ extern bool CGSIsSecureEventInputSet(); internal unsigned major_version = 0; internal unsigned minor_version = 0; internal unsigned patch_version = 10; -internal struct table hotkey_map; +internal struct mode *current_mode; +internal struct table mode_map; internal char *config_file; internal void @@ -69,16 +70,19 @@ parse_config_helper(char *absolutepath) { struct parser parser; if (parser_init(&parser, absolutepath)) { - parse_config(&parser, &hotkey_map); + parser.mode_map = &mode_map; + parse_config(&parser); parser_destroy(&parser); + current_mode = table_find(&mode_map, "default"); } else { + current_mode = NULL; warn("skhd: could not open file '%s'\n", absolutepath); } } internal HOTLOADER_CALLBACK(config_handler) { - free_hotkeys(&hotkey_map); + free_mode_map(&mode_map); parse_config_helper(absolutepath); } @@ -92,11 +96,12 @@ internal EVENT_TAP_CALLBACK(key_handler) CGEventTapEnable(event_tap->handle, 1); } break; case kCGEventKeyDown: { + if (!current_mode) return event; uint32_t flags = CGEventGetFlags(event); uint32_t key = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); struct hotkey eventkey = { .flags = 0, .key = key }; cgeventflags_to_hotkeyflags(flags, &eventkey); - bool result = find_and_exec_hotkey(&eventkey, &hotkey_map); + bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode); if (result) { return NULL; } @@ -186,10 +191,10 @@ int main(int argc, char **argv) set_config_path(); } - table_init(&hotkey_map, - 131, - (table_hash_func) hash_hotkey, - (table_compare_func) same_hotkey); + table_init(&mode_map, + 13, + (table_hash_func) hash_mode, + (table_compare_func) same_mode); printf("skhd: using config '%s'\n", config_file); parse_config_helper(config_file); diff --git a/src/tokenize.c b/src/tokenize.c index 691b9c8..82cd466 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -94,7 +94,7 @@ resolve_identifier_type(struct token token) } } - return Token_Unknown; + return Token_Identifier; } struct token @@ -121,6 +121,8 @@ get_token(struct tokenizer *tokenizer) switch (c) { case '\0':{ token.type = Token_EndOfStream; } break; case '+': { token.type = Token_Plus; } break; + case ',': { token.type = Token_Comma; } break; + case '<': { token.type = Token_Insert; } break; case '#': { eat_comment(tokenizer); token = get_token(tokenizer); @@ -134,16 +136,33 @@ get_token(struct tokenizer *tokenizer) token.type = Token_Dash; } } break; - case ':': { + case ';': { eat_whitespace(tokenizer); token.text = tokenizer->at; token.line = tokenizer->line; token.cursor = tokenizer->cursor; - eat_command(tokenizer); + eat_identifier(tokenizer); token.length = tokenizer->at - token.text; - token.type = Token_Command; + token.type = Token_Activate; + } break; + case ':': { + if (*tokenizer->at && *tokenizer->at == ':') { + advance(tokenizer); + token.length = tokenizer->at - token.text; + token.type = Token_Decl; + } else { + eat_whitespace(tokenizer); + + token.text = tokenizer->at; + token.line = tokenizer->line; + token.cursor = tokenizer->cursor; + + eat_command(tokenizer); + token.length = tokenizer->at - token.text; + token.type = Token_Command; + } } break; default: { if (c == '0' && *tokenizer->at == 'x') { diff --git a/src/tokenize.h b/src/tokenize.h index 36b4350..114ed0d 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -28,12 +28,18 @@ static const char *literal_keycode_str[] = enum token_type { + Token_Identifier, + Token_Activate, + Token_Command, Token_Modifier, Token_Literal, Token_Key_Hex, Token_Key, + Token_Decl, + Token_Comma, + Token_Insert, Token_Plus, Token_Dash, Token_Arrow, -- cgit v1.2.3