From 6236adbe07cd05963c91abb797d146fcaa413f16 Mon Sep 17 00:00:00 2001 From: Yujonpradhananga Date: Fri, 20 Feb 2026 23:15:35 +0545 Subject: [PATCH] feat: allow-none-key-name Fixed none keybind trigger involving an additional modifier bug Fixed mouse click behaviour bug --- src/config/parse_config.h | 30 +++++++++++++++++++- src/mango.c | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 830d22ba..d19d2e4f 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -20,7 +20,7 @@ enum { NUM_TYPE_MINUS, NUM_TYPE_PLUS, NUM_TYPE_DEFAULT }; -enum { KEY_TYPE_CODE, KEY_TYPE_SYM }; +enum { KEY_TYPE_CODE, KEY_TYPE_SYM, KEY_TYPE_NONE }; typedef struct { uint32_t keycode1; @@ -768,6 +768,22 @@ void create_config_keymap(void) { KeySymCode parse_key(const char *key_str, bool isbindsym) { KeySymCode kc = {0}; // 初始化为0 + // Handle "none" key for modifier-only bindings + { + char lower_key[10]; + int32_t ki = 0; + while (key_str[ki] && ki < 9) { + lower_key[ki] = tolower(key_str[ki]); + ki++; + } + lower_key[ki] = '\0'; + if (strcmp(lower_key, "none") == 0) { + kc.type = KEY_TYPE_NONE; + kc.keysym = XKB_KEY_NoSymbol; + return kc; + } + } + if (config.keymap == NULL || config.ctx == NULL) { // 处理错误 kc.type = KEY_TYPE_SYM; @@ -2315,6 +2331,18 @@ bool parse_option(Config *config, char *key, char *value) { binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); + + /* KEY_TYPE_NONE bindings must use the release flag (bindr) */ + if (binding->keysymcode.type == KEY_TYPE_NONE) { + if (!binding->isreleaseapply) { + fprintf(stderr, + "\033[1m\033[33m[WARN]:\033[0m " + "\"none\" key only works with bindr " + "(release bind), forcing release mode\n"); + binding->isreleaseapply = true; + } + } + if (!binding->func || binding->mod == UINT32_MAX || (binding->keysymcode.type == KEY_TYPE_SYM && binding->keysymcode.keysym == XKB_KEY_NoSymbol)) { diff --git a/src/mango.c b/src/mango.c index 540395ae..411e3eb2 100644 --- a/src/mango.c +++ b/src/mango.c @@ -887,6 +887,8 @@ struct dvec2 *baked_points_opafadeout; static struct wl_event_source *hide_source; static bool cursor_hidden = false; static bool tag_combo = false; +static bool mod_key_used = false; +static uint32_t prev_mods = 0; static const char *cli_config_path = NULL; static KeyMode keymode = { .mode = {'d', 'e', 'f', 'a', 'u', 'l', 't', '\0'}, @@ -1989,6 +1991,9 @@ buttonpress(struct wl_listener *listener, void *data) { if (locked) break; + /* Invalidate modifier-only bindings on mouse click */ + mod_key_used = true; + xytonode(cursor->x, cursor->y, &surface, NULL, NULL, NULL, NULL); if (toplevel_from_wlr_surface(surface, &c, &l) >= 0) { if (c && c->scene->node.enabled && @@ -3596,6 +3601,10 @@ keybinding(uint32_t state, bool locked, uint32_t mods, xkb_keysym_t sym, state != WL_KEYBOARD_KEY_STATE_RELEASED) continue; + /* Skip KEY_TYPE_NONE bindings here; they are handled in keypressmod */ + if (config.key_bindings[ji].keysymcode.type == KEY_TYPE_NONE) + continue; + k = &config.key_bindings[ji]; if ((k->iscommonmode || (k->isdefaultmode && keymode.isdefault) || (strcmp(keymode.mode, k->mode) == 0)) && @@ -3726,6 +3735,16 @@ void keypress(struct wl_listener *listener, void *data) { } } + /* Track non-modifier key presses to invalidate modifier-only bindings. + * Modifier keycodes: 133,134=Super, 37,105=Ctrl, 50,62=Shift, 64,108=Alt */ + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED && + keycode != 133 && keycode != 134 && /* Super_L, Super_R */ + keycode != 37 && keycode != 105 && /* Control_L, Control_R */ + keycode != 50 && keycode != 62 && /* Shift_L, Shift_R */ + keycode != 64 && keycode != 108) { /* Alt_L, Alt_R */ + mod_key_used = true; + } + /* On _press_ if there is no active screen locker, * attempt to process a compositor keybinding. */ for (i = 0; i < nsyms; i++) @@ -3782,6 +3801,47 @@ void keypressmod(struct wl_listener *listener, void *data) { * pressed. We simply communicate this to the client. */ KeyboardGroup *group = wl_container_of(listener, group, modifiers); + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); + + /* Handle modifier-only keybindings (KEY_TYPE_NONE). + * Fire when a modifier is released without any non-modifier key having + * been pressed during the hold. We detect release by checking if a + * modifier bit was set in prev_mods but is now cleared. + * The (prev_mods & ~mods) check ensures a modifier was actually + * released (bits cleared), not just a new modifier added. */ + if (!locked && group == kb_group && !mod_key_used && prev_mods != 0 && + mods != prev_mods && (prev_mods & ~mods) != 0) { + /* A modifier was released — check if the previously held modifier + * combination matches any KEY_TYPE_NONE bindings */ + for (int32_t ji = 0; ji < config.key_bindings_count; ji++) { + const KeyBinding *k = &config.key_bindings[ji]; + if (k->keysymcode.type != KEY_TYPE_NONE) + continue; + if (!k->func) + continue; + if (!(k->iscommonmode || + (k->isdefaultmode && keymode.isdefault) || + (strcmp(keymode.mode, k->mode) == 0))) + continue; + /* The binding's mod should match the modifiers that were held + * before this release */ + if (CLEANMASK(prev_mods) == CLEANMASK(k->mod)) { + k->func(&k->arg); + /* Mark as used so we don't fire again during + * subsequent releases of the same combo */ + mod_key_used = true; + break; + } + } + } + + /* Reset tracking when all modifiers are released */ + if (mods == 0) { + mod_key_used = false; + } + + prev_mods = mods; + if (!dwl_im_keyboard_grab_forward_modifiers(group)) { wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard);