From 01a7bf5a8d7bdc747cc2772e47c3854c405b3b43 Mon Sep 17 00:00:00 2001 From: porl Date: Sun, 10 May 2026 15:52:56 +1000 Subject: [PATCH] feat: add bindm (modifier-only exclusive keybind) --- src/config/parse_config.h | 87 +++++++++++++++++++++++++++++++++++++++ src/mango.c | 45 ++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 006968f0..50bbb2ba 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -45,6 +45,7 @@ typedef struct { bool islockapply; bool isreleaseapply; bool ispassapply; + bool ismodonly; } KeyBinding; typedef struct { @@ -2436,6 +2437,92 @@ bool parse_option(Config *config, char *key, char *value) { config->key_bindings_count++; } + } else if (regex_match("^bindm[lp]*$", key)) { + /* bindm: fire on modifier release if no other bind used the hold + * format: bindm[flags]=MODIFIERS,,COMMAND,PARAMS */ + config->key_bindings = + realloc(config->key_bindings, + (config->key_bindings_count + 1) * sizeof(KeyBinding)); + if (!config->key_bindings) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for key bindings (bindm)\n"); + return false; + } + + KeyBinding *binding = &config->key_bindings[config->key_bindings_count]; + memset(binding, 0, sizeof(KeyBinding)); + + char mod_str[256], func_name[256], + arg_value[256] = "0\0", arg_value2[256] = "0\0", + arg_value3[256] = "0\0", arg_value4[256] = "0\0", + arg_value5[256] = "0\0"; + + if (sscanf(value, + "%255[^,],,%255[^,],%255[^,],%255[^,],%255[^,],%255[^\n]", + mod_str, func_name, arg_value, arg_value2, + arg_value3, arg_value4) < 2) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid bindm format " + "(expected MODIFIERS,,COMMAND,...): " + "\033[1m\033[31m%s\n", + value); + return false; + } + trim_whitespace(mod_str); + trim_whitespace(func_name); + trim_whitespace(arg_value); + trim_whitespace(arg_value2); + trim_whitespace(arg_value3); + trim_whitespace(arg_value4); + trim_whitespace(arg_value5); + + strcpy(binding->mode, config->keymode); + if (strcmp(binding->mode, "common") == 0) { + binding->iscommonmode = true; + binding->isdefaultmode = false; + } else if (strcmp(binding->mode, "default") == 0) { + binding->isdefaultmode = true; + binding->iscommonmode = false; + } else { + binding->isdefaultmode = false; + binding->iscommonmode = false; + } + + for (const char *f = key + 5; *f != '\0'; f++) { + if (*f == 'l') binding->islockapply = true; + if (*f == 'p') binding->ispassapply = true; + } + + binding->mod = parse_mod(mod_str); + binding->ismodonly = true; + + binding->func = + parse_func_name(func_name, &binding->arg, arg_value, arg_value2, + arg_value3, arg_value4, arg_value5); + if (!binding->func || binding->mod == UINT32_MAX) { + if (binding->arg.v) { + free(binding->arg.v); + binding->arg.v = NULL; + } + if (binding->arg.v2) { + free(binding->arg.v2); + binding->arg.v2 = NULL; + } + if (binding->arg.v3) { + free(binding->arg.v3); + binding->arg.v3 = NULL; + } + if (!binding->func) + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "bindm: \033[1m\033[31m%s\n", + func_name); + return false; + } else { + config->key_bindings_count++; + } + } else if (strncmp(key, "mousebind", 9) == 0) { config->mouse_bindings = realloc(config->mouse_bindings, diff --git a/src/mango.c b/src/mango.c index 34d4d764..8381df42 100644 --- a/src/mango.c +++ b/src/mango.c @@ -922,6 +922,10 @@ static struct wl_event_source *hide_cursor_source; static struct wl_event_source *keep_idle_inhibit_source; static bool cursor_hidden = false; static bool tag_combo = false; + +/* set when a bind fires while modifiers held; cleared on modifier press */ +static bool mod_used_for_bind = false; + static const char *cli_config_path = NULL; static bool cli_debug_log = false; static KeyMode keymode = { @@ -2332,6 +2336,7 @@ buttonpress(struct wl_listener *listener, void *data) { event->button == m->button && m->func && (CLEANMASK(m->mod) != 0 || (event->button != BTN_LEFT && event->button != BTN_RIGHT))) { + mod_used_for_bind = true; m->func(&m->arg); return; } @@ -3996,6 +4001,9 @@ keybinding(uint32_t state, bool locked, uint32_t mods, xkb_keysym_t sym, state != WL_KEYBOARD_KEY_STATE_RELEASED) continue; + if (config.key_bindings[ji].ismodonly) + continue; + k = &config.key_bindings[ji]; if ((k->iscommonmode || (k->isdefaultmode && keymode.isdefault) || (strcmp(keymode.mode, k->mode) == 0)) && @@ -4014,6 +4022,9 @@ keybinding(uint32_t state, bool locked, uint32_t mods, xkb_keysym_t sym, else handled = 0; + if (!k->ispassapply) + mod_used_for_bind = true; + isbreak = k->func(&k->arg); if (isbreak) @@ -4115,6 +4126,15 @@ void keypress(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + bool is_modifier_key = + (keycode == 133 || keycode == 134 || + keycode == 37 || keycode == 105 || + keycode == 64 || keycode == 108 || + keycode == 50 || keycode == 62); + + if (is_modifier_key && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) + mod_used_for_bind = false; + // ov tab mode detect moe key release if (config.ov_tab_mode && !locked && group == kb_group && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && @@ -4132,6 +4152,31 @@ void keypress(struct wl_listener *listener, void *data) { handled = keybinding(event->state, locked, mods, syms[i], keycode) || handled; + /* bindm: fire mod-only binds on release if no combo fired */ + if (is_modifier_key && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && + !mod_used_for_bind) { + int32_t ji; + for (ji = 0; ji < config.key_bindings_count; ji++) { + const KeyBinding *k = &config.key_bindings[ji]; + + if (!k->ismodonly) + continue; + if (locked && !k->islockapply) + continue; + if (!(k->iscommonmode || + (k->isdefaultmode && keymode.isdefault) || + (strcmp(keymode.mode, k->mode) == 0))) + continue; + if (CLEANMASK(mods) != CLEANMASK(k->mod)) + continue; + if (!k->func) + continue; + + handled = 1; + k->func(&k->arg); + } + } + if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { tag_combo = false; }