@@ -887,6 +887,8 @@ struct dvec2 *baked_points_opafadeout;
887887static struct wl_event_source * hide_source ;
888888static bool cursor_hidden = false;
889889static bool tag_combo = false;
890+ static bool mod_key_used = false;
891+ static uint32_t prev_mods = 0 ;
890892static const char * cli_config_path = NULL ;
891893static KeyMode keymode = {
892894 .mode = {'d' , 'e' , 'f' , 'a' , 'u' , 'l' , 't' , '\0' },
@@ -1989,6 +1991,9 @@ buttonpress(struct wl_listener *listener, void *data) {
19891991 if (locked )
19901992 break ;
19911993
1994+ /* Invalidate modifier-only bindings on mouse click */
1995+ mod_key_used = true;
1996+
19921997 xytonode (cursor -> x , cursor -> y , & surface , NULL , NULL , NULL , NULL );
19931998 if (toplevel_from_wlr_surface (surface , & c , & l ) >= 0 ) {
19941999 if (c && c -> scene -> node .enabled &&
@@ -3596,6 +3601,10 @@ keybinding(uint32_t state, bool locked, uint32_t mods, xkb_keysym_t sym,
35963601 state != WL_KEYBOARD_KEY_STATE_RELEASED )
35973602 continue ;
35983603
3604+ /* Skip KEY_TYPE_NONE bindings here; they are handled in keypressmod */
3605+ if (config .key_bindings [ji ].keysymcode .type == KEY_TYPE_NONE )
3606+ continue ;
3607+
35993608 k = & config .key_bindings [ji ];
36003609 if ((k -> iscommonmode || (k -> isdefaultmode && keymode .isdefault ) ||
36013610 (strcmp (keymode .mode , k -> mode ) == 0 )) &&
@@ -3726,6 +3735,16 @@ void keypress(struct wl_listener *listener, void *data) {
37263735 }
37273736 }
37283737
3738+ /* Track non-modifier key presses to invalidate modifier-only bindings.
3739+ * Modifier keycodes: 133,134=Super, 37,105=Ctrl, 50,62=Shift, 64,108=Alt */
3740+ if (event -> state == WL_KEYBOARD_KEY_STATE_PRESSED &&
3741+ keycode != 133 && keycode != 134 && /* Super_L, Super_R */
3742+ keycode != 37 && keycode != 105 && /* Control_L, Control_R */
3743+ keycode != 50 && keycode != 62 && /* Shift_L, Shift_R */
3744+ keycode != 64 && keycode != 108 ) { /* Alt_L, Alt_R */
3745+ mod_key_used = true;
3746+ }
3747+
37293748 /* On _press_ if there is no active screen locker,
37303749 * attempt to process a compositor keybinding. */
37313750 for (i = 0 ; i < nsyms ; i ++ )
@@ -3782,6 +3801,47 @@ void keypressmod(struct wl_listener *listener, void *data) {
37823801 * pressed. We simply communicate this to the client. */
37833802 KeyboardGroup * group = wl_container_of (listener , group , modifiers );
37843803
3804+ uint32_t mods = wlr_keyboard_get_modifiers (& group -> wlr_group -> keyboard );
3805+
3806+ /* Handle modifier-only keybindings (KEY_TYPE_NONE).
3807+ * Fire when a modifier is released without any non-modifier key having
3808+ * been pressed during the hold. We detect release by checking if a
3809+ * modifier bit was set in prev_mods but is now cleared.
3810+ * The (prev_mods & ~mods) check ensures a modifier was actually
3811+ * released (bits cleared), not just a new modifier added. */
3812+ if (!locked && group == kb_group && !mod_key_used && prev_mods != 0 &&
3813+ mods != prev_mods && (prev_mods & ~mods ) != 0 ) {
3814+ /* A modifier was released — check if the previously held modifier
3815+ * combination matches any KEY_TYPE_NONE bindings */
3816+ for (int32_t ji = 0 ; ji < config .key_bindings_count ; ji ++ ) {
3817+ const KeyBinding * k = & config .key_bindings [ji ];
3818+ if (k -> keysymcode .type != KEY_TYPE_NONE )
3819+ continue ;
3820+ if (!k -> func )
3821+ continue ;
3822+ if (!(k -> iscommonmode ||
3823+ (k -> isdefaultmode && keymode .isdefault ) ||
3824+ (strcmp (keymode .mode , k -> mode ) == 0 )))
3825+ continue ;
3826+ /* The binding's mod should match the modifiers that were held
3827+ * before this release */
3828+ if (CLEANMASK (prev_mods ) == CLEANMASK (k -> mod )) {
3829+ k -> func (& k -> arg );
3830+ /* Mark as used so we don't fire again during
3831+ * subsequent releases of the same combo */
3832+ mod_key_used = true;
3833+ break ;
3834+ }
3835+ }
3836+ }
3837+
3838+ /* Reset tracking when all modifiers are released */
3839+ if (mods == 0 ) {
3840+ mod_key_used = false;
3841+ }
3842+
3843+ prev_mods = mods ;
3844+
37853845 if (!dwl_im_keyboard_grab_forward_modifiers (group )) {
37863846
37873847 wlr_seat_set_keyboard (seat , & group -> wlr_group -> keyboard );
0 commit comments