From 0b03bdb56667fc3efcbf2624145409436cac9b78 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Fri, 20 Jul 2018 19:26:15 +0200 Subject: #44 synthesize keypress; skhd -k "alt - h" --- src/parse.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'src/parse.c') diff --git a/src/parse.c b/src/parse.c index 79f4aba..a79d8a3 100644 --- a/src/parse.c +++ b/src/parse.c @@ -367,6 +367,53 @@ void parse_config(struct parser *parser) } } +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)) { + parser_report_error(parser, Error_Unexpected_Token, "expected '-'"); + 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, Error_Unexpected_Token, "expected key-literal"); + goto err; + } + + return hotkey; + + err: + free(hotkey); + return NULL; + } else { + parser_report_error(parser, Error_Unexpected_Token, "expected modifier or key-literal"); + } + return NULL; +} + struct token parser_peek(struct parser *parser) { @@ -445,6 +492,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); -- cgit v1.2.3 From 6e9823e29b5c268545bddffee1d0cc095388f3df Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Fri, 20 Jul 2018 19:28:21 +0200 Subject: #44 don't spend time reporting errors --- src/parse.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/parse.c') diff --git a/src/parse.c b/src/parse.c index a79d8a3..5439a0f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -387,7 +387,6 @@ parse_keypress(struct parser *parser) if (found_modifier) { if (!parser_match(parser, Token_Dash)) { - parser_report_error(parser, Error_Unexpected_Token, "expected '-'"); goto err; } } @@ -399,7 +398,6 @@ parse_keypress(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"); goto err; } @@ -408,9 +406,8 @@ parse_keypress(struct parser *parser) err: free(hotkey); return NULL; - } else { - parser_report_error(parser, Error_Unexpected_Token, "expected modifier or key-literal"); } + return NULL; } -- cgit v1.2.3 From 93917e6609a709cdcff4f23ed6c788cfe6bcc838 Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Mon, 30 Jul 2018 19:22:08 +0100 Subject: Add meh keyword --- src/hotkey.h | 5 ++++- src/parse.c | 2 +- src/tokenize.h | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src/parse.c') diff --git a/src/hotkey.h b/src/hotkey.h index e616303..f06ce90 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -49,7 +49,10 @@ 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" diff --git a/src/parse.c b/src/parse.c index 5439a0f..0b09f02 100644 --- a/src/parse.c +++ b/src/parse.c @@ -158,7 +158,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 diff --git a/src/tokenize.h b/src/tokenize.h index d6bd4bf..3c1bd31 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -7,7 +7,7 @@ static const char *modifier_flags_str[] = "shift", "lshift", "rshift", "cmd", "lcmd", "rcmd", "ctrl", "lctrl", "rctrl", - "fn", "hyper", + "fn", "hyper", "meh", }; static const char *literal_keycode_str[] = -- cgit v1.2.3 From 74915cd6c87dd88f093e8a8819dd8cfca0880a44 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Sun, 5 Aug 2018 02:04:03 +0200 Subject: gate debug information behind '--verbose' flag; add profiling support (must be enabled at compile-time) --- README.md | 3 +++ src/log.h | 36 +++++++++++++++++++++++++++++ src/parse.c | 16 ++++++------- src/skhd.c | 77 +++++++++++++++++++++++++++++++++++-------------------------- 4 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 src/log.h (limited to 'src/parse.c') diff --git a/README.md b/README.md index 58ab70a..5b60c28 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ Requires xcode-8 command-line tools. ### Usage ``` +-V | --verbose: Output debug information + skhd -V + -v | --version: Print version number to stdout skhd -v diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..2e8167b --- /dev/null +++ b/src/log.h @@ -0,0 +1,36 @@ +#ifndef SKHD_LOG_H +#define SKHD_LOG_H + +static bool verbose; + +static inline void +debug(const char *format, ...) +{ + if (!verbose) return; + + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); +} + +static inline void +warn(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +static inline void +error(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + exit(EXIT_FAILURE); +} + +#endif diff --git a/src/parse.c b/src/parse.c index 0b09f02..8dc90dd 100644 --- a/src/parse.c +++ b/src/parse.c @@ -79,7 +79,7 @@ parse_command(struct parser *parser) { struct token command = parser_previous(parser); char *result = copy_string_count(command.text, command.length); - printf("\tcmd: '%s'\n", result); + debug("\tcmd: '%s'\n", result); return result; } @@ -90,7 +90,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 +100,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 +146,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; } } @@ -170,7 +170,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; } } @@ -203,7 +203,7 @@ parse_mode(struct parser *parser, struct hotkey *hotkey) } 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)) { @@ -221,7 +221,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); @@ -282,7 +282,7 @@ parse_hotkey(struct parser *parser) goto err; } - printf("}\n"); + debug("}\n"); return hotkey; err: diff --git a/src/skhd.c b/src/skhd.c index 76eb664..7071965 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -8,6 +8,7 @@ #include #include +#include "log.h" #define HASHTABLE_IMPLEMENTATION #include "hashtable.h" #include "sbuffer.h" @@ -32,14 +33,26 @@ extern bool CGSIsSecureEventInputSet(); #define secure_keyboard_entry_enabled CGSIsSecureEventInputSet #ifdef SKHD_PROFILE -#define BEGIN_TIMED_BLOCK() \ - clock_t timed_block_begin = clock() +#define BEGIN_SCOPED_TIMED_BLOCK(note) \ + do { \ + char *timed_note = note; \ + clock_t timed_block_begin = clock() +#define END_SCOPED_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("%.4fms (%s)\n", timed_block_elapsed, timed_note); \ + } while (0) +#define BEGIN_TIMED_BLOCK(note) \ + char *timed_note = note; \ + 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) + clock_t timed_block_end = clock(); \ + double timed_block_elapsed = ((timed_block_end - timed_block_begin) / (double)CLOCKS_PER_SEC) * 1000.0f; \ + printf("%.4fms (%s)\n", timed_block_elapsed, timed_note) #else -#define BEGIN_TIMED_BLOCK() +#define BEGIN_SCOPED_TIMED_BLOCK(note) +#define END_SCOPED_TIMED_BLOCK() +#define BEGIN_TIMED_BLOCK(note) #define END_TIMED_BLOCK() #endif @@ -53,25 +66,6 @@ internal struct mode *current_mode; internal struct table mode_map; internal char *config_file; -internal void -error(const char *format, ...) -{ - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - exit(EXIT_FAILURE); -} - -internal void -warn(const char *format, ...) -{ - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); -} - internal void parse_config_helper(char *absolutepath) { @@ -87,8 +81,10 @@ parse_config_helper(char *absolutepath) internal HOTLOADER_CALLBACK(config_handler) { + BEGIN_TIMED_BLOCK("hotload_config"); free_mode_map(&mode_map); parse_config_helper(absolutepath); + END_TIMED_BLOCK(); } internal EVENT_TAP_CALLBACK(key_handler) @@ -96,14 +92,14 @@ 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 (!current_mode) return event; - BEGIN_TIMED_BLOCK(); + BEGIN_TIMED_BLOCK("handle_keypress"); struct hotkey eventkey = create_eventkey(event); bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode); END_TIMED_BLOCK(); @@ -127,8 +123,9 @@ internal bool parse_arguments(int argc, char **argv) { int option; - const char *short_option = "vc:k:t:"; + const char *short_option = "Vvc:k:t:"; struct option long_option[] = { + { "verbose", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'v' }, { "config", required_argument, NULL, 'c' }, { "key", required_argument, NULL, 'k' }, @@ -138,6 +135,9 @@ parse_arguments(int argc, char **argv) while ((option = getopt_long(argc, argv, short_option, long_option, NULL)) != -1) { switch (option) { + case 'V': { + verbose = true; + } break; case 'v': { printf("skhd version %d.%d.%d\n", major_version, minor_version, patch_version); return true; @@ -197,6 +197,8 @@ use_default_config_path() int main(int argc, char **argv) { + BEGIN_TIMED_BLOCK("startup"); + BEGIN_SCOPED_TIMED_BLOCK("initialization"); if (parse_arguments(argc, argv)) { return EXIT_SUCCESS; } @@ -221,23 +223,32 @@ int main(int argc, char **argv) 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); signal(SIGCHLD, SIG_IGN); init_shell(); + table_init(&mode_map, 13, (table_hash_func) hash_mode, (table_compare_func) same_mode); + END_SCOPED_TIMED_BLOCK(); + + BEGIN_SCOPED_TIMED_BLOCK("parse_config"); + debug("skhd: using config '%s'\n", config_file); + parse_config_helper(config_file); + END_SCOPED_TIMED_BLOCK(); + BEGIN_SCOPED_TIMED_BLOCK("begin_eventtap"); struct event_tap event_tap; event_tap.mask = (1 << kCGEventKeyDown) | (1 << NX_SYSDEFINED); event_tap_begin(&event_tap, key_handler); + END_SCOPED_TIMED_BLOCK(); + BEGIN_SCOPED_TIMED_BLOCK("begin_hotloader"); struct hotloader hotloader = {}; if (hotloader_add_file(&hotloader, config_file) && hotloader_begin(&hotloader, config_handler)) { - printf("skhd: watching '%s' for changes\n", config_file); + debug("skhd: watching '%s' for changes\n", config_file); } else { - fprintf(stderr, "skhd: could not watch '%s'\n", config_file); + warn("skhd: could not watch '%s'\n", config_file); } + END_SCOPED_TIMED_BLOCK(); + END_TIMED_BLOCK(); CFRunLoopRun(); return EXIT_SUCCESS; -- cgit v1.2.3 From 3d90dbeaa9cf13058ed349ba7beb2532c571c7fb Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Thu, 30 Aug 2018 14:09:56 +0200 Subject: first iteration --- src/carbon.c | 20 ++++++++------------ src/carbon.h | 17 +++++++++++++++++ src/hotkey.c | 29 ++++++++++++++++++++++++++--- src/hotkey.h | 7 +++++-- src/parse.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-------- src/parse.h | 1 + src/skhd.c | 19 ++++++++----------- src/tokenize.c | 29 +++++++++++++++++++++++++++++ src/tokenize.h | 4 ++++ 9 files changed, 146 insertions(+), 36 deletions(-) create mode 100644 src/carbon.h (limited to 'src/parse.c') diff --git a/src/carbon.c b/src/carbon.c index 98e2793..9af606a 100644 --- a/src/carbon.c +++ b/src/carbon.c @@ -1,17 +1,10 @@ -#include +#include "carbon.h" -struct carbon_event -{ - EventTargetRef target; - EventHandlerUPP handler; - EventTypeSpec type; - EventHandlerRef handler_ref; - char * volatile process_name; -}; +#define internal static #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" -static OSStatus +internal OSStatus carbon_event_handler(EventHandlerCallRef ref, EventRef event, void *context) { struct carbon_event *carbon = (struct carbon_event *) context; @@ -29,9 +22,12 @@ carbon_event_handler(EventHandlerCallRef ref, EventRef event, void *context) CFStringRef process_name_ref; if (CopyProcessName(&psn, &process_name_ref) == noErr) { - if (carbon->process_name) free(carbon->process_name); + if (carbon->process_name) { + free(carbon->process_name); + carbon->process_name = NULL; + } + carbon->process_name = copy_cfstring(process_name_ref); - printf("front app changed: %s\n", carbon->process_name); CFRelease(process_name_ref); } diff --git a/src/carbon.h b/src/carbon.h new file mode 100644 index 0000000..d98792a --- /dev/null +++ b/src/carbon.h @@ -0,0 +1,17 @@ +#ifndef SKHD_CARBON_H +#define SKHD_CARBON_H + +#include + +struct carbon_event +{ + EventTargetRef target; + EventHandlerUPP handler; + EventTypeSpec type; + EventHandlerRef handler_ref; + char * volatile process_name; +}; + +bool carbon_event_init(struct carbon_event *carbon); + +#endif diff --git a/src/hotkey.c b/src/hotkey.c index 6448328..1cb4669 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -96,6 +96,13 @@ unsigned long hash_mode(char *key) return hash; } +internal inline bool +same_string(char *a, char *b) +{ + if (!a || !b) return false; + return same_mode(a, b); +} + internal inline void fork_and_exec(char *command) { @@ -142,14 +149,29 @@ should_capture_hotkey(uint32_t capture) return (capture & MODE_CAPTURE(1)); } -bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m) +internal inline char * +find_process_command_mapping(struct hotkey *hotkey, uint32_t *capture, struct carbon_event *carbon) +{ + for (int i = 0; i < buf_len(hotkey->process_name); ++i) { + if (same_string(carbon->process_name, hotkey->process_name[i])) { + return hotkey->command[i]; + } + } + + *capture &= ~FOUND_HOTKEY; + return NULL; +} + +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; + 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); } @@ -178,7 +200,8 @@ void free_mode_map(struct table *mode_map) buf_push(freed_pointers, hotkey); buf_free(hotkey->mode_list); - free(hotkey->command); + buf_free(hotkey->process_name); + buf_free(hotkey->command); free(hotkey); next:; } diff --git a/src/hotkey.h b/src/hotkey.h index f06ce90..f96fcea 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -57,6 +57,8 @@ enum hotkey_flag #include "hashtable.h" +struct carbon_event; + struct mode { int line; @@ -71,7 +73,8 @@ struct hotkey { uint32_t flags; uint32_t key; - char *command; + char **process_name; + char **command; struct mode **mode_list; }; @@ -103,7 +106,7 @@ unsigned long hash_hotkey(struct hotkey *a); struct hotkey create_eventkey(CGEventRef event); bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey); -bool find_and_exec_hotkey(struct hotkey *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 init_shell(); diff --git a/src/parse.c b/src/parse.c index 8dc90dd..46378b2 100644 --- a/src/parse.c +++ b/src/parse.c @@ -74,13 +74,46 @@ 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); debug("\tcmd: '%s'\n", result); - return 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); + buf_push(hotkey->process_name, name); + if (parser_match(parser, Token_Command)) { + parse_command(parser, hotkey); + parse_process_command_list(parser, hotkey); + } else { + parser_report_error(parser, Error_Unexpected_Token, "expected ':' followed by command"); + } + } else if (parser_match(parser, Token_EndList)) { + if (!buf_len(hotkey->process_name)) { + parser_report_error(parser, Error_Missing_Value, "list must contain at least one value"); + } + } else { + parser_report_error(parser, Error_Unexpected_Token, "expected process command mapping or ']'"); + } +} + +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, Error_Undeclared_Ident, "undeclared identifier"); + } } internal uint32_t @@ -269,12 +302,15 @@ 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); + 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 { @@ -468,6 +504,10 @@ void parser_report_error(struct parser *parser, enum parse_error_type error_type 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_Missing_Value) { + fprintf(stderr, "#%d:%d ", parser->previous_token.line, parser->previous_token.cursor); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); } else if (error_type == Error_Duplicate_Ident) { vfprintf(stderr, format, args); } diff --git a/src/parse.h b/src/parse.h index 93fcfc6..9bd11e5 100644 --- a/src/parse.h +++ b/src/parse.h @@ -19,6 +19,7 @@ enum parse_error_type Error_Unexpected_Token, Error_Undeclared_Ident, Error_Duplicate_Ident, + Error_Missing_Value, }; diff --git a/src/skhd.c b/src/skhd.c index 1391125..3725641 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -15,6 +15,7 @@ #include "hotload.h" #include "event_tap.h" #include "locale.h" +#include "carbon.h" #include "tokenize.h" #include "parse.h" #include "hotkey.h" @@ -23,11 +24,11 @@ #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 "carbon.c" #define internal static extern bool CGSIsSecureEventInputSet(); @@ -63,7 +64,7 @@ internal unsigned major_version = 0; internal unsigned minor_version = 2; internal unsigned patch_version = 5; -// internal struct carbon_event carbon; +internal struct carbon_event carbon; internal struct event_tap event_tap; internal struct hotloader hotloader; internal struct mode *current_mode; @@ -105,7 +106,7 @@ internal EVENT_TAP_CALLBACK(key_handler) BEGIN_TIMED_BLOCK("handle_keypress"); struct hotkey eventkey = create_eventkey(event); - bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode); + bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode, &carbon); END_TIMED_BLOCK(); if (result) return NULL; @@ -115,7 +116,7 @@ internal EVENT_TAP_CALLBACK(key_handler) struct hotkey eventkey; if (intercept_systemkey(event, &eventkey)) { - bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode); + bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode, &carbon); if (result) return NULL; } } break; @@ -223,13 +224,9 @@ int main(int argc, char **argv) error("skhd: could not initialize keycode map! abort..\n"); } - /* - * NOTE(koekeishiya: hooks up event for tracking name of focused process/application - * - * if (!carbon_event_init(&carbon)) { - * error("skhd: could not initialize carbon events! abort..\n"); - * } - */ + if (!carbon_event_init(&carbon)) { + error("skhd: could not initialize carbon events! abort..\n"); + } if (!config_file) { use_default_config_path(); diff --git a/src/tokenize.c b/src/tokenize.c index 5040a7c..8445a71 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -63,6 +63,22 @@ 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 inline bool isidentifier(char c) { @@ -130,6 +146,19 @@ 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_BeginList; } break; + case ']': { token.type = Token_EndList; } 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 3c1bd31..73cdb30 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -49,6 +49,10 @@ enum token_type Token_Dash, Token_Arrow, Token_Capture, + Token_String, + + Token_BeginList, + Token_EndList, Token_Unknown, Token_EndOfStream, -- cgit v1.2.3 From 3bdb843b9da0ff4b986c1d117f06b1de4d0f857a Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Thu, 30 Aug 2018 14:20:23 +0200 Subject: minor fixes --- src/hotkey.c | 9 +-------- src/parse.c | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) (limited to 'src/parse.c') diff --git a/src/hotkey.c b/src/hotkey.c index 1cb4669..f1d2ab6 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -96,13 +96,6 @@ unsigned long hash_mode(char *key) return hash; } -internal inline bool -same_string(char *a, char *b) -{ - if (!a || !b) return false; - return same_mode(a, b); -} - internal inline void fork_and_exec(char *command) { @@ -158,7 +151,7 @@ find_process_command_mapping(struct hotkey *hotkey, uint32_t *capture, struct ca } } - *capture &= ~FOUND_HOTKEY; + *capture &= ~HOTKEY_FOUND; return NULL; } diff --git a/src/parse.c b/src/parse.c index 46378b2..399f6c6 100644 --- a/src/parse.c +++ b/src/parse.c @@ -304,7 +304,7 @@ parse_hotkey(struct parser *parser) if (parser_match(parser, Token_Command)) { parse_command(parser, hotkey); } else if (parser_match(parser, Token_BeginList)) { - parse_process_command_list(parser); + parse_process_command_list(parser, hotkey); if (parser->error) { goto err; } -- cgit v1.2.3 From 7fc64890b29d4553c8f7a5067978ade6019c7666 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Thu, 30 Aug 2018 15:37:38 +0200 Subject: cleanup error reporting --- src/hotkey.h | 2 -- src/parse.c | 59 +++++++++++++++++++---------------------------------------- src/parse.h | 11 +---------- 3 files changed, 20 insertions(+), 52 deletions(-) (limited to 'src/parse.c') diff --git a/src/hotkey.h b/src/hotkey.h index f96fcea..3b26ac3 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -61,8 +61,6 @@ struct carbon_event; struct mode { - int line; - int cursor; char *name; char *command; bool capture; diff --git a/src/parse.c b/src/parse.c index 399f6c6..d9202c5 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, @@ -94,14 +92,14 @@ parse_process_command_list(struct parser *parser, struct hotkey *hotkey) parse_command(parser, hotkey); parse_process_command_list(parser, hotkey); } else { - parser_report_error(parser, Error_Unexpected_Token, "expected ':' followed by command"); + parser_report_error(parser, parser_peek(parser), "expected ':' followed by command\n"); } } else if (parser_match(parser, Token_EndList)) { if (!buf_len(hotkey->process_name)) { - parser_report_error(parser, Error_Missing_Value, "list must contain at least one value"); + parser_report_error(parser, parser_previous(parser), "list must contain at least one value\n"); } } else { - parser_report_error(parser, Error_Unexpected_Token, "expected process command mapping or ']'"); + parser_report_error(parser, parser_peek(parser), "expected process command mapping or ']'\n"); } } @@ -112,7 +110,7 @@ parse_activate(struct parser *parser, struct hotkey *hotkey) hotkey->flags |= Hotkey_Flag_Activate; if (!table_find(parser->mode_map, hotkey->command[0])) { - parser_report_error(parser, Error_Undeclared_Ident, "undeclared identifier"); + parser_report_error(parser, parser_previous(parser), "undeclared identifier\n"); } } @@ -212,7 +210,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"); } } @@ -231,7 +229,7 @@ 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; } @@ -242,7 +240,7 @@ parse_mode(struct parser *parser, struct hotkey *hotkey) 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"); } } } @@ -265,7 +263,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 { @@ -281,7 +279,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; } } @@ -293,7 +291,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; } @@ -314,7 +312,7 @@ parse_hotkey(struct parser *parser) 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; } @@ -332,8 +330,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, @@ -357,19 +353,17 @@ 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"); } } @@ -398,7 +392,7 @@ void parse_config(struct parser *parser) } else if (parser_check(parser, Token_Decl)) { parse_declaration(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"); } } } @@ -491,27 +485,12 @@ 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); - - 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_Missing_Value) { - fprintf(stderr, "#%d:%d ", parser->previous_token.line, parser->previous_token.cursor); - vfprintf(stderr, format, args); - fprintf(stderr, "\n"); - } else if (error_type == Error_Duplicate_Ident) { - vfprintf(stderr, format, args); - } - + fprintf(stderr, "#%d:%d ", token.line, token.cursor); + vfprintf(stderr, format, args); va_end(args); parser->error = true; } diff --git a/src/parse.h b/src/parse.h index 9bd11e5..35bc62e 100644 --- a/src/parse.h +++ b/src/parse.h @@ -14,15 +14,6 @@ struct parser bool error; }; -enum parse_error_type -{ - Error_Unexpected_Token, - Error_Undeclared_Ident, - Error_Duplicate_Ident, - Error_Missing_Value, -}; - - void parse_config(struct parser *parser); struct hotkey *parse_keypress(struct parser *parser); @@ -35,6 +26,6 @@ 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_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 -- cgit v1.2.3 From 6f55631142457b6865e955bc2f204525027d4d4f Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Thu, 30 Aug 2018 16:45:46 +0200 Subject: automatically convert to lowercase --- src/carbon.c | 5 ++--- src/parse.c | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/parse.c') diff --git a/src/carbon.c b/src/carbon.c index 285468b..d722104 100644 --- a/src/carbon.c +++ b/src/carbon.c @@ -27,10 +27,9 @@ carbon_event_handler(EventHandlerCallRef ref, EventRef event, void *context) carbon->process_name = NULL; } - // NOTE(koekeishiya): Might want to call ProcessInformationCopyDictionary instead. - // http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/Process_Manager/prmref_main/function_group_1.html#//apple_ref/c/func/GetProcessInformationCopyDictionary - CFStringLowercase((CFMutableStringRef)process_name_ref, CFLocaleGetSystem()); carbon->process_name = copy_cfstring(process_name_ref); + for (char *s = carbon->process_name; *s; ++s) *s = tolower(*s); + CFRelease(process_name_ref); } diff --git a/src/parse.c b/src/parse.c index d9202c5..a5760b7 100644 --- a/src/parse.c +++ b/src/parse.c @@ -87,6 +87,7 @@ 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); -- cgit v1.2.3 From 5287a3192799d8f9bbdca01fa457cd6cb3e7eba5 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Sat, 23 Feb 2019 17:18:49 +0100 Subject: #62 implement option to unbind certain applications etc. --- README.md | 7 ++++++- examples/skhdrc | 6 +++++- src/hotkey.c | 13 ++++++++++--- src/hotkey.h | 1 + src/parse.c | 18 +++++++++++++++++- src/tokenize.c | 2 ++ src/tokenize.h | 2 ++ 7 files changed, 43 insertions(+), 6 deletions(-) (limited to 'src/parse.c') diff --git a/README.md b/README.md index b645b3b..a4ddf41 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ keycode = 'apple keyboard kVK_ values (0x3C)' proc_map_lst = * -proc_map = ':' +proc_map = ':' | ':' '~' | + '*' ':' | '*' '~' string = '"' 'sequence of characters' '"' @@ -108,6 +109,10 @@ command = command is executed through '$SHELL -c' and an EOL character signifies the end of the bind. -> = keypress is not consumed by skhd + +* = matches every application not specified in + +~ = application is unbound and keypress is forwarded per usual, when specified in a ``` A mode is declared according to the following rules: diff --git a/examples/skhdrc b/examples/skhdrc index 5336e71..e68433c 100644 --- a/examples/skhdrc +++ b/examples/skhdrc @@ -23,7 +23,8 @@ # # proc_map_lst = * # -# proc_map = ':' +# proc_map = ':' | ':' '~' | +# '*' ':' | '*' '~' # # string = '"' 'sequence of characters' '"' # @@ -79,7 +80,9 @@ # # cmd - n [ # "kitty" : echo "hello kitty" +# * : echo "hello everyone" # "qutebrowser" : echo "hello qutebrowser" +# "terminal" ~ # "finder" : false # ] @@ -257,3 +260,4 @@ 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 + diff --git a/src/hotkey.c b/src/hotkey.c index 0c8b9f0..2f0df3a 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -145,14 +145,21 @@ should_capture_hotkey(uint32_t capture) 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])) { - return hotkey->command[i]; + result = hotkey->command[i]; + found = true; + break; } } - *capture &= ~HOTKEY_FOUND; - return NULL; + 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) diff --git a/src/hotkey.h b/src/hotkey.h index 71d2e1d..641875d 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -73,6 +73,7 @@ struct hotkey uint32_t key; char **process_name; char **command; + char *wildcard_command; struct mode **mode_list; }; diff --git a/src/parse.c b/src/parse.c index a5760b7..760485d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -92,8 +92,24 @@ parse_process_command_list(struct parser *parser, struct hotkey *hotkey) 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 ':' followed by command\n"); + 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)) { diff --git a/src/tokenize.c b/src/tokenize.c index 8445a71..a0fd93d 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -146,6 +146,8 @@ 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 '"': { diff --git a/src/tokenize.h b/src/tokenize.h index 73cdb30..ff82dee 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -49,6 +49,8 @@ enum token_type Token_Dash, Token_Arrow, Token_Capture, + Token_Unbound, + Token_Wildcard, Token_String, Token_BeginList, -- cgit v1.2.3 From 56b94a6eed9fe4642382f533666dc19901fad3fc Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Sun, 3 Mar 2019 18:39:34 +0100 Subject: #53 allow opt-out for applications --- README.md | 14 +++++++++++++- examples/skhdrc | 8 ++++++++ src/hotkey.c | 14 ++++++++++++-- src/hotkey.h | 5 +++-- src/parse.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/parse.h | 3 ++- src/skhd.c | 10 ++++++++-- src/tokenize.c | 17 +++++++++++++++++ src/tokenize.h | 1 + 9 files changed, 104 insertions(+), 9 deletions(-) (limited to 'src/parse.c') diff --git a/README.md b/README.md index e0b9ded..3ab4481 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ feature comparison between **skhd** and **khd** | hotload config file | [x] | [ ] | | hotkey passthrough | [x] | [x] | | modal hotkey-system | [x] | [x] | -| use media-keys as hotkey | [x] | [ ] | | application specific hotkey| [x] | [x] | +| blacklist applications | [x] | [ ] | +| use media-keys as hotkey | [x] | [ ] | | modifier only hotkey | [ ] | [x] | | caps-lock as hotkey | [ ] | [x] | | mouse-buttons as hotkey | [ ] | [x] | @@ -136,3 +137,14 @@ 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**: +``` +# prevents skhd from monitoring events for listed processes +.blacklist [ + "terminal" + "qutebrowser" + "kitty" + "google chrome" +] +``` diff --git a/examples/skhdrc b/examples/skhdrc index ffd0613..bf0f7a3 100644 --- a/examples/skhdrc +++ b/examples/skhdrc @@ -86,6 +86,14 @@ # "finder" : false # ] +# prevent skhd from monitoring events for specific applications +# +# .blacklist [ +# "kitty" +# "terminal" +# "qutebrowser" +# ] + # open terminal, blazingly fast compared to iTerm/Hyper cmd - return : /Applications/Kitty.app/Contents/MacOS/kitty --single-instance -d ~ diff --git a/src/hotkey.c b/src/hotkey.c index 9b6b85a..780aa3a 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -74,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; @@ -83,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) { @@ -228,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) { diff --git a/src/hotkey.h b/src/hotkey.h index 372715d..4611b09 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -100,8 +100,8 @@ clear_flags(struct hotkey *hotkey, uint32_t flag) #undef internal -bool same_mode(char *a, char *b); -unsigned long hash_mode(char *key); +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); @@ -111,6 +111,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 init_shell(void); diff --git a/src/parse.c b/src/parse.c index 760485d..4e673f4 100644 --- a/src/parse.c +++ b/src/parse.c @@ -384,6 +384,41 @@ void parse_declaration(struct parser *parser) } } +void parse_option_blacklist(struct parser *parser) +{ + if (parser_match(parser, Token_String)) { + struct token name_token = parser_previous(parser); + char *name = copy_string_count(name_token.text, name_token.length); + for (char *s = name; *s; ++s) *s = tolower(*s); + debug("\t%s\n", name); + table_add(parser->blacklst, name, name); + parse_option_blacklist(parser); + } else if (parser_match(parser, Token_EndList)) { + if (parser->blacklst->count == 0) { + parser_report_error(parser, parser_previous(parser), "list must contain at least one value\n"); + } + } else { + parser_report_error(parser, parser_peek(parser), "expected process name or ']'\n"); + } +} + +void parse_option(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 { + parser_report_error(parser, option, "invalid option specified\n"); + } +} + void parse_config(struct parser *parser) { struct mode *mode; @@ -392,6 +427,7 @@ void parse_config(struct parser *parser) while (!parser_eof(parser)) { if (parser->error) { free_mode_map(parser->mode_map); + free_blacklist(parser->blacklst); return; } @@ -408,6 +444,8 @@ 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, parser_peek(parser), "expected decl, modifier or key-literal\n"); } @@ -512,12 +550,13 @@ void parser_report_error(struct parser *parser, struct token token, const char * parser->error = true; } -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->mode_map = mode_map; + parser->blacklst = blacklst; tokenizer_init(&parser->tokenizer, buffer); parser_advance(parser); return true; diff --git a/src/parse.h b/src/parse.h index 35bc62e..95813c2 100644 --- a/src/parse.h +++ b/src/parse.h @@ -11,6 +11,7 @@ struct parser struct token current_token; struct tokenizer tokenizer; struct table *mode_map; + struct table *blacklst; bool error; }; @@ -23,7 +24,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, 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, struct token token, const char *format, ...); diff --git a/src/skhd.c b/src/skhd.c index 05e955f..ceea857 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -48,13 +48,14 @@ 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 char *config_file; internal void parse_config_helper(char *absolutepath) { struct parser parser; - if (parser_init(&parser, &mode_map, absolutepath)) { + if (parser_init(&parser, &mode_map, &blacklst, absolutepath)) { parse_config(&parser); parser_destroy(&parser); } else { @@ -68,6 +69,7 @@ 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); + free_blacklist(&blacklst); parse_config_helper(absolutepath); END_TIMED_BLOCK(); } @@ -78,6 +80,7 @@ internal CF_NOTIFICATION_CALLBACK(keymap_handler) 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(); @@ -93,6 +96,7 @@ internal EVENT_TAP_CALLBACK(key_handler) 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"); @@ -103,6 +107,7 @@ internal EVENT_TAP_CALLBACK(key_handler) if (result) return NULL; } break; case NX_SYSDEFINED: { + if (table_find(&blacklst, carbon.process_name)) return event; if (!current_mode) return event; struct hotkey eventkey; @@ -236,7 +241,8 @@ int main(int argc, char **argv) signal(SIGCHLD, SIG_IGN); init_shell(); - table_init(&mode_map, 13, (table_hash_func) hash_mode, (table_compare_func) same_mode); + table_init(&mode_map, 13, (table_hash_func) hash_string, (table_compare_func) compare_string); + table_init(&blacklst, 13, (table_hash_func) hash_string, (table_compare_func) compare_string); END_SCOPED_TIMED_BLOCK(); BEGIN_SCOPED_TIMED_BLOCK("parse_config"); diff --git a/src/tokenize.c b/src/tokenize.c index a0fd93d..eaf0e3a 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -79,6 +79,14 @@ eat_string(struct tokenizer *tokenizer) } } +internal void +eat_option(struct tokenizer *tokenizer) +{ + while (*tokenizer->at && !isspace(*tokenizer->at)) { + advance(tokenizer); + } +} + internal inline bool isidentifier(char c) { @@ -150,6 +158,15 @@ get_token(struct tokenizer *tokenizer) 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; diff --git a/src/tokenize.h b/src/tokenize.h index 4592ac9..4df5df3 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -56,6 +56,7 @@ enum token_type Token_Unbound, Token_Wildcard, Token_String, + Token_Option, Token_BeginList, Token_EndList, -- cgit v1.2.3 From ac494e3f72e397aaf3245b0401bd1b963bd4ebd3 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Sun, 3 Mar 2019 22:24:14 +0100 Subject: #58 load/include additional config files --- README.md | 10 ++++++- examples/skhdrc | 9 ++++++- src/parse.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/parse.h | 10 ++++++- src/skhd.c | 30 +++++++++++++-------- 5 files changed, 121 insertions(+), 20 deletions(-) (limited to 'src/parse.c') diff --git a/README.md b/README.md index 3ab4481..24b9226 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,15 @@ command = command is executed through '$SHELL -c' and General options that configure the behaviour of **skhd**: ``` -# prevents skhd from monitoring events for listed processes +# 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" diff --git a/examples/skhdrc b/examples/skhdrc index bf0f7a3..48b5424 100644 --- a/examples/skhdrc +++ b/examples/skhdrc @@ -86,7 +86,14 @@ # "finder" : false # ] -# prevent skhd from monitoring events for specific applications +# 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" diff --git a/src/parse.c b/src/parse.c index 4e673f4..9f14389 100644 --- a/src/parse.c +++ b/src/parse.c @@ -402,6 +402,32 @@ void parse_option_blacklist(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); @@ -414,22 +440,26 @@ void parse_option(struct parser *parser) } 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"); } } -void parse_config(struct parser *parser) +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); - free_blacklist(parser->blacklst); - return; - } + if (parser->error) break; if ((parser_check(parser, Token_Identifier)) || (parser_check(parser, Token_Modifier)) || @@ -450,6 +480,14 @@ void parse_config(struct parser *parser) 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 * @@ -550,11 +588,43 @@ void parser_report_error(struct parser *parser, struct token token, const char * parser->error = true; } +void parser_do_directives(struct parser *parser, struct hotloader *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)) { + hotloader_add_file(hotloader, load.file); + + if (parse_config(&directive_parser)) { + parser_do_directives(&directive_parser, hotloader); + } else { + error = true; + } + parser_destroy(&directive_parser); + } else { + warn("skhd: could not open file '%s' from load directive #%d:%d\n", load.file, load.option.line, load.option.cursor); + } + + free(load.file); + } + buf_free(parser->load_directives); + + if (error) { + free_mode_map(parser->mode_map); + free_blacklist(parser->blacklst); + } +} + bool parser_init(struct parser *parser, struct table *mode_map, struct table *blacklst, char *file) { memset(parser, 0, sizeof(struct parser)); char *buffer = read_file(file); if (buffer) { + parser->file = file; parser->mode_map = mode_map; parser->blacklst = blacklst; tokenizer_init(&parser->tokenizer, buffer); diff --git a/src/parse.h b/src/parse.h index 95813c2..6576f7a 100644 --- a/src/parse.h +++ b/src/parse.h @@ -4,18 +4,26 @@ #include "tokenize.h" #include +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; }; -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); diff --git a/src/skhd.c b/src/skhd.c index ceea857..c90c4b6 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -51,16 +51,33 @@ global struct table mode_map; global struct table blacklst; 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, &blacklst, absolutepath)) { - parse_config(&parser); + hotloader_end(&hotloader); + hotloader_add_file(&hotloader, absolutepath); + + if (parse_config(&parser)) { + parser_do_directives(&parser, &hotloader); + } parser_destroy(&parser); + + 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"); } @@ -70,7 +87,7 @@ internal HOTLOADER_CALLBACK(config_handler) debug("skhd: config-file has been modified.. reloading config\n"); free_mode_map(&mode_map); free_blacklist(&blacklst); - parse_config_helper(absolutepath); + parse_config_helper(config_file); END_TIMED_BLOCK(); } @@ -254,15 +271,6 @@ int main(int argc, char **argv) event_tap.mask = (1 << kCGEventKeyDown) | (1 << NX_SYSDEFINED); event_tap_begin(&event_tap, key_handler); END_SCOPED_TIMED_BLOCK(); - - BEGIN_SCOPED_TIMED_BLOCK("begin_hotloader"); - if (hotloader_add_file(&hotloader, config_file) && - hotloader_begin(&hotloader, config_handler)) { - debug("skhd: watching '%s' for changes\n", config_file); - } else { - warn("skhd: could not watch '%s'\n", config_file); - } - END_SCOPED_TIMED_BLOCK(); END_SCOPED_TIMED_BLOCK(); CFRunLoopRun(); -- cgit v1.2.3 From 3922cf456a6c711032a1a6650442be67cfbb80fb Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Thu, 7 Mar 2019 22:43:23 +0100 Subject: #54 ability to disable hotloader and trigger manual reload of config file --- README.md | 6 ++++ src/parse.c | 9 ++++-- src/skhd.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 102 insertions(+), 16 deletions(-) (limited to 'src/parse.c') diff --git a/README.md b/README.md index 24b9226..c4b1a8d 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,12 @@ Requires xcode-8 command-line tools. -c | --config: Specify location of config file skhd -c ~/.skhdrc +-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" diff --git a/src/parse.c b/src/parse.c index 9f14389..e2b4c4d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -588,7 +588,7 @@ void parser_report_error(struct parser *parser, struct token token, const char * parser->error = true; } -void parser_do_directives(struct parser *parser, struct hotloader *hotloader) +void parser_do_directives(struct parser *parser, struct hotloader *hotloader, bool thwart_hotloader) { bool error = false; @@ -597,13 +597,16 @@ void parser_do_directives(struct parser *parser, struct hotloader *hotloader) struct parser directive_parser; if (parser_init(&directive_parser, parser->mode_map, parser->blacklst, load.file)) { - hotloader_add_file(hotloader, load.file); + if (!thwart_hotloader) { + hotloader_add_file(hotloader, load.file); + } if (parse_config(&directive_parser)) { - parser_do_directives(&directive_parser, hotloader); + 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); diff --git a/src/skhd.c b/src/skhd.c index c90c4b6..1ae7976 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -5,6 +5,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -38,6 +42,7 @@ extern bool CGSIsSecureEventInputSet(); #define global static #define SKHD_CONFIG_FILE ".skhdrc" +#define SKHD_PID_FILE "/tmp/skhd.pid" global unsigned major_version = 0; global unsigned minor_version = 3; @@ -49,6 +54,7 @@ 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); @@ -58,21 +64,25 @@ parse_config_helper(char *absolutepath) { struct parser parser; if (parser_init(&parser, &mode_map, &blacklst, absolutepath)) { - hotloader_end(&hotloader); - hotloader_add_file(&hotloader, absolutepath); + if (!thwart_hotloader) { + hotloader_end(&hotloader); + hotloader_add_file(&hotloader, absolutepath); + } if (parse_config(&parser)) { - parser_do_directives(&parser, &hotloader); + parser_do_directives(&parser, &hotloader, thwart_hotloader); } parser_destroy(&parser); - 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); + 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 start watcher.. hotloading is not enabled\n"); } } else { warn("skhd: could not open file '%s'\n", absolutepath); @@ -137,18 +147,73 @@ internal EVENT_TAP_CALLBACK(key_handler) return event; } +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) +{ + pid_t pid = 0; + + int handle = open(SKHD_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) +{ + pid_t pid = getpid(); + + int handle = open(SKHD_PID_FILE, O_CREAT | O_WRONLY, 0644); + if (handle == -1) { + error("skhd: could not create pid-file! abort..\n"); + } + + if (flock(handle, LOCK_EX | LOCK_NB) == -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 = "VPvc:k:t:"; + const char *short_option = "VPvc:k:t:rh"; 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' }, { NULL, 0, NULL, 0 } }; @@ -167,6 +232,9 @@ parse_arguments(int argc, char **argv) case 'c': { config_file = copy_string(optarg); } break; + case 'h': { + thwart_hotloader = true; + } break; case 'k': { synthesize_key(optarg); return true; @@ -175,6 +243,11 @@ parse_arguments(int argc, char **argv) synthesize_text(optarg); return true; } break; + case 'r': { + pid_t pid = read_pid_file(); + if (pid) kill(pid, SIGUSR1); + return true; + } break; } } @@ -219,15 +292,17 @@ use_default_config_path(void) 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"); - if (getuid() == 0 || geteuid() == 0) { - error("skhd: running as root is not allowed! abort..\n"); - } + create_pid_file(); if (secure_keyboard_entry_enabled()) { error("skhd: secure keyboard entry is enabled! abort..\n"); @@ -257,6 +332,8 @@ int main(int argc, char **argv) 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); -- cgit v1.2.3