From f7ad393c6d7b9a9b80fbaa67ebb4f2f30af4ab0e Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Sat, 25 Mar 2023 22:49:12 +0000 Subject: [PATCH 1/2] feature: add key forwarding --- README.md | 10 ++++++++++ src/hotkey.c | 6 ++++++ src/hotkey.h | 1 + src/parse.c | 11 ++++++++++- src/skhd.c | 17 +++++++++++++++++ src/synthesize.c | 34 ++++++++++++++++++++++++++++++++++ src/synthesize.h | 3 +++ src/tokenize.c | 9 +++++++++ src/tokenize.h | 1 + 9 files changed, 91 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b819fc0..9f6a726 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ list of features | blacklist applications | [x] | | use media-keys as hotkey | [x] | | synthesize a key-press | [x] | +| noremap / key forwarding | [x] | ### Install @@ -96,6 +97,7 @@ mode = 'name of mode' | ',' action = '[' ']' | '->' '[' ']' ':' | '->' ':' ';' | '->' ';' + '|' keysym = '-' | @@ -172,3 +174,11 @@ General options that configure the behaviour of **skhd**: "google chrome" ] ``` + +Key forwarding (noremap like functionality): + +``` +# specify source key (left) and target key (right) to synthesize +cmd - 1 : yabai -m space --focus 1 +ctrl - 1 | cmd - 1 # press `ctrl - 1` will be `cmd - 1`, bypassing `cmd - 1` command above +``` diff --git a/src/hotkey.c b/src/hotkey.c index 780aa3a..75e069b 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -1,4 +1,5 @@ #include "hotkey.h" +#include "synthesize.h" #define internal static #define global static @@ -167,6 +168,10 @@ bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m, st { uint32_t c = MODE_CAPTURE((int)(*m)->capture); for (struct hotkey *h = find_hotkey(*m, k, &c); h; passthrough(h, &c), h = 0) { + if (h->forwarded_hotkey) { + synthesize_forward_hotkey(h->forwarded_hotkey); + continue; + } char *cmd = h->command[0]; if (has_flags(h, Hotkey_Flag_Activate)) { *m = table_find(t, cmd); @@ -212,6 +217,7 @@ void free_mode_map(struct table *mode_map) } buf_free(hotkey->command); + if (hotkey->forwarded_hotkey) free(hotkey->forwarded_hotkey); free(hotkey); next:; } diff --git a/src/hotkey.h b/src/hotkey.h index 6b6e92a..de62cad 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -76,6 +76,7 @@ struct hotkey char **command; char *wildcard_command; struct mode **mode_list; + struct hotkey *forwarded_hotkey; }; #define internal static diff --git a/src/parse.c b/src/parse.c index 42feca2..a88398f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -317,7 +317,16 @@ parse_hotkey(struct parser *parser) hotkey->flags |= Hotkey_Flag_Passthrough; } - if (parser_match(parser, Token_Command)) { + if (parser_match(parser, Token_Forward)) { + debug(" forward key stroke: {\n"); + struct hotkey *forwarded = parse_keypress(parser); + if (!forwarded) { + parser_report_error(parser, parser_peek(parser), "expect keysym\n"); + goto err; + } + hotkey->forwarded_hotkey = forwarded; + debug(" }\n"); + } else if (parser_match(parser, Token_Command)) { parse_command(parser, hotkey); } else if (parser_match(parser, Token_BeginList)) { parse_process_command_list(parser, hotkey); diff --git a/src/skhd.c b/src/skhd.c index 3525269..be19110 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -152,6 +152,13 @@ internal EVENT_TAP_CALLBACK(key_observer_handler) return event; } +// is_synthetic_event returns true if the event was generated by skhd. +internal bool is_synthetic_event(CGEventRef event) +{ + int user_data = CGEventGetIntegerValueField(event, kCGEventSourceUserData); + return user_data == SHKD_CGEVENT_USER_DATA; +} + internal EVENT_TAP_CALLBACK(key_handler) { switch (type) { @@ -165,6 +172,11 @@ internal EVENT_TAP_CALLBACK(key_handler) if (table_find(&blacklst, carbon.process_name)) return event; if (!current_mode) return event; + if (is_synthetic_event(event)) { + debug("skhd: ignoring synthetic event\n"); + return event; + } + BEGIN_TIMED_BLOCK("handle_keypress"); struct hotkey eventkey = create_eventkey(event); bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode, &carbon); @@ -176,6 +188,11 @@ internal EVENT_TAP_CALLBACK(key_handler) if (table_find(&blacklst, carbon.process_name)) return event; if (!current_mode) return event; + if (is_synthetic_event(event)) { + debug("skhd: ignoring synthetic event\n"); + return event; + } + struct hotkey eventkey; if (intercept_systemkey(event, &eventkey)) { bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode, &carbon); diff --git a/src/synthesize.c b/src/synthesize.c index 6eddd6f..c8359f5 100644 --- a/src/synthesize.c +++ b/src/synthesize.c @@ -63,6 +63,40 @@ void synthesize_key(char *key_string) synthesize_modifiers(hotkey, false); } +void synthesize_forward_hotkey(struct hotkey *hotkey) +{ + CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventSourceSetUserData(source, SHKD_CGEVENT_USER_DATA); + + CGEventRef de = CGEventCreateKeyboardEvent(source, hotkey->key, true); + CGEventRef ue = CGEventCreateKeyboardEvent(source, hotkey->key, false); + + int flags = 0; + if (has_flags(hotkey, Hotkey_Flag_Alt)) { + flags |= kCGEventFlagMaskAlternate; + } + if (has_flags(hotkey, Hotkey_Flag_Shift)) { + flags |= kCGEventFlagMaskShift; + } + if (has_flags(hotkey, Hotkey_Flag_Cmd)) { + flags |= kCGEventFlagMaskCommand; + } + if (has_flags(hotkey, Hotkey_Flag_Control)) { + flags |= kCGEventFlagMaskControl; + } + if (has_flags(hotkey, Hotkey_Flag_Fn)) { + flags |= kCGEventFlagMaskSecondaryFn; + } + CGEventSetFlags(de, flags); + + CGEventPost(kCGSessionEventTap, de); + CGEventPost(kCGSessionEventTap, ue); + + CFRelease(ue); + CFRelease(de); + CFRelease(source); +} + void synthesize_text(char *text) { CFStringRef text_ref = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); diff --git a/src/synthesize.h b/src/synthesize.h index 4a2b262..be7d3dc 100644 --- a/src/synthesize.h +++ b/src/synthesize.h @@ -4,4 +4,7 @@ void synthesize_key(char *key_string); void synthesize_text(char *text); +void synthesize_forward_hotkey(struct hotkey *hotkey); +#define SHKD_CGEVENT_USER_DATA 0x12345 + #endif diff --git a/src/tokenize.c b/src/tokenize.c index eaf0e3a..2c73f0a 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -219,6 +219,15 @@ get_token(struct tokenizer *tokenizer) token.type = Token_Command; } } break; + case '|': { + eat_whitespace(tokenizer); + + token.text = tokenizer->at; + token.line = tokenizer->line; + token.cursor = tokenizer->cursor; + + token.type = Token_Forward; + } break; default: { if (c == '0' && *tokenizer->at == 'x') { advance(tokenizer); diff --git a/src/tokenize.h b/src/tokenize.h index 52d76fc..aa372d0 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -47,6 +47,7 @@ enum token_type Token_Key, Token_Decl, + Token_Forward, Token_Comma, Token_Insert, Token_Plus, From 33bce36b4275a61c8d84f62b0d4923e6b6ac26cc Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Mon, 27 Mar 2023 23:55:34 +0100 Subject: [PATCH 2/2] alternative approach without synthesize --- src/hotkey.c | 33 ++++++++++++++++++++++++++++----- src/skhd.c | 18 ++---------------- src/synthesize.c | 34 ---------------------------------- src/synthesize.h | 3 --- 4 files changed, 30 insertions(+), 58 deletions(-) diff --git a/src/hotkey.c b/src/hotkey.c index 75e069b..95c35c4 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -1,5 +1,4 @@ #include "hotkey.h" -#include "synthesize.h" #define internal static #define global static @@ -164,14 +163,38 @@ find_process_command_mapping(struct hotkey *hotkey, uint32_t *capture, struct ca return result; } +bool find_and_forward_hotkey(struct hotkey *k, struct mode *m, CGEventRef event) +{ + struct hotkey *found = table_find(&m->hotkey_map, k); + if (!found || !found->forwarded_hotkey) return false; + debug("forwarding hotkey\n"); + struct hotkey *forwarded = found->forwarded_hotkey; + + int flags = 0; + if (has_flags(forwarded, Hotkey_Flag_Alt)) { + flags |= kCGEventFlagMaskAlternate; + } + if (has_flags(forwarded, Hotkey_Flag_Shift)) { + flags |= kCGEventFlagMaskShift; + } + if (has_flags(forwarded, Hotkey_Flag_Cmd)) { + flags |= kCGEventFlagMaskCommand; + } + if (has_flags(forwarded, Hotkey_Flag_Control)) { + flags |= kCGEventFlagMaskControl; + } + if (has_flags(forwarded, Hotkey_Flag_Fn)) { + flags |= kCGEventFlagMaskSecondaryFn; + } + CGEventSetFlags(event, flags); + CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, forwarded->key); + return true; +} + 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) { - if (h->forwarded_hotkey) { - synthesize_forward_hotkey(h->forwarded_hotkey); - continue; - } char *cmd = h->command[0]; if (has_flags(h, Hotkey_Flag_Activate)) { *m = table_find(t, cmd); diff --git a/src/skhd.c b/src/skhd.c index be19110..20047a7 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -152,13 +152,6 @@ internal EVENT_TAP_CALLBACK(key_observer_handler) return event; } -// is_synthetic_event returns true if the event was generated by skhd. -internal bool is_synthetic_event(CGEventRef event) -{ - int user_data = CGEventGetIntegerValueField(event, kCGEventSourceUserData); - return user_data == SHKD_CGEVENT_USER_DATA; -} - internal EVENT_TAP_CALLBACK(key_handler) { switch (type) { @@ -172,13 +165,11 @@ internal EVENT_TAP_CALLBACK(key_handler) if (table_find(&blacklst, carbon.process_name)) return event; if (!current_mode) return event; - if (is_synthetic_event(event)) { - debug("skhd: ignoring synthetic event\n"); + struct hotkey eventkey = create_eventkey(event); + if (find_and_forward_hotkey(&eventkey, current_mode, event)) { return event; } - BEGIN_TIMED_BLOCK("handle_keypress"); - struct hotkey eventkey = create_eventkey(event); bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode, &carbon); END_TIMED_BLOCK(); @@ -188,11 +179,6 @@ internal EVENT_TAP_CALLBACK(key_handler) if (table_find(&blacklst, carbon.process_name)) return event; if (!current_mode) return event; - if (is_synthetic_event(event)) { - debug("skhd: ignoring synthetic event\n"); - return event; - } - struct hotkey eventkey; if (intercept_systemkey(event, &eventkey)) { bool result = find_and_exec_hotkey(&eventkey, &mode_map, ¤t_mode, &carbon); diff --git a/src/synthesize.c b/src/synthesize.c index c8359f5..6eddd6f 100644 --- a/src/synthesize.c +++ b/src/synthesize.c @@ -63,40 +63,6 @@ void synthesize_key(char *key_string) synthesize_modifiers(hotkey, false); } -void synthesize_forward_hotkey(struct hotkey *hotkey) -{ - CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); - CGEventSourceSetUserData(source, SHKD_CGEVENT_USER_DATA); - - CGEventRef de = CGEventCreateKeyboardEvent(source, hotkey->key, true); - CGEventRef ue = CGEventCreateKeyboardEvent(source, hotkey->key, false); - - int flags = 0; - if (has_flags(hotkey, Hotkey_Flag_Alt)) { - flags |= kCGEventFlagMaskAlternate; - } - if (has_flags(hotkey, Hotkey_Flag_Shift)) { - flags |= kCGEventFlagMaskShift; - } - if (has_flags(hotkey, Hotkey_Flag_Cmd)) { - flags |= kCGEventFlagMaskCommand; - } - if (has_flags(hotkey, Hotkey_Flag_Control)) { - flags |= kCGEventFlagMaskControl; - } - if (has_flags(hotkey, Hotkey_Flag_Fn)) { - flags |= kCGEventFlagMaskSecondaryFn; - } - CGEventSetFlags(de, flags); - - CGEventPost(kCGSessionEventTap, de); - CGEventPost(kCGSessionEventTap, ue); - - CFRelease(ue); - CFRelease(de); - CFRelease(source); -} - void synthesize_text(char *text) { CFStringRef text_ref = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); diff --git a/src/synthesize.h b/src/synthesize.h index be7d3dc..4a2b262 100644 --- a/src/synthesize.h +++ b/src/synthesize.h @@ -4,7 +4,4 @@ void synthesize_key(char *key_string); void synthesize_text(char *text); -void synthesize_forward_hotkey(struct hotkey *hotkey); -#define SHKD_CGEVENT_USER_DATA 0x12345 - #endif