git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commite5de022ca740385fd82eb58e7160b00e192464d1
parent2548e2130f
authorIsaac Freund <[email protected]>
date2026-01-15 17:16
KeyboardGroup: don't eat modifiers due to ensure_next_key_eaten

 protocol/river-xkb-bindings-v1.xml | 13 +++++----
 river/KeyboardGroup.zig            | 60 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/protocol/river-xkb-bindings-v1.xml b/protocol/river-xkb-bindings-v1.xml
index bc41dea..041b1bd 100644
--- a/protocol/river-xkb-bindings-v1.xml
+++ b/protocol/river-xkb-bindings-v1.xml
@@ -219,14 +219,15 @@
 
     <request name="ensure_next_key_eaten" since="2">
       <description summary="ensure the next key press event is eaten">
-        Ensure that the next key press and corresponding release events for this
-        seat are not sent to the currently focused surface.
+        Ensure that the next non-modifier key press and corresponding release
+        events for this seat are not sent to the currently focused surface.
 
-        If the next key press triggers a binding, the pressed/released events are
-        sent to the river_xkb_binding_v1 object as usual.
+        If the next non-modifier key press triggers a binding, the
+        pressed/released events are sent to the river_xkb_binding_v1 object as
+        usual.
 
-        If the next key press does not trigger a binding, the ate_unbound_key
-        event is sent instead.
+        If the next non-modifier key press does not trigger a binding, the
+        ate_unbound_key event is sent instead.
 
         Rationale: the window manager may wish to implement "chorded"
         keybindings where triggering a binding activates a "submap" with a
diff --git a/river/KeyboardGroup.zig b/river/KeyboardGroup.zig
index f079ebc..4972f3a 100644
--- a/river/KeyboardGroup.zig
+++ b/river/KeyboardGroup.zig
@@ -214,8 +214,20 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
             };
         }
         if (group.seat.xkb_bindings_seat.ensure_next_key_eaten) {
-            group.seat.xkb_bindings_seat.ensure_next_key_eaten = false;
-            break :blk .ensure_eaten;
+            // This approach for filtering out modifiers feels like a hack.
+            // Open questions:
+            // - Are there keycodes that should be considered a modifier which
+            //   are not yet checked by keysymIsModifier()?
+            // - Is it possible to test whether keysymIsModifier() is complete?
+            // - Is there a way to test the effect the keycode would have on
+            //   the active modifiers of the xkb_state?
+            // - Could we add a function to libxkbcommon to make that possible?
+            for (xkb_state.keyGetSyms(xkb_keycode)) |sym| {
+                if (!keysymIsModifier(sym)) {
+                    group.seat.xkb_bindings_seat.ensure_next_key_eaten = false;
+                    break :blk .ensure_eaten;
+                }
+            }
         }
         if (group.getInputMethodGrab() != null) {
             break :blk .im_grab;
@@ -256,6 +268,50 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
     }
 }
 
+fn keysymIsModifier(keysym: xkb.Keysym) bool {
+    switch (@intFromEnum(keysym)) {
+        xkb.Keysym.Shift_L,
+        xkb.Keysym.Shift_R,
+        xkb.Keysym.Control_L,
+        xkb.Keysym.Control_R,
+        xkb.Keysym.Caps_Lock,
+        xkb.Keysym.Shift_Lock,
+
+        xkb.Keysym.Meta_L,
+        xkb.Keysym.Meta_R,
+        xkb.Keysym.Alt_L,
+        xkb.Keysym.Alt_R,
+        xkb.Keysym.Super_L,
+        xkb.Keysym.Super_R,
+        xkb.Keysym.Hyper_L,
+        xkb.Keysym.Hyper_R,
+
+        xkb.Keysym.Num_Lock,
+
+        xkb.Keysym.ISO_Lock,
+        xkb.Keysym.ISO_Level2_Latch,
+        xkb.Keysym.ISO_Level3_Shift,
+        xkb.Keysym.ISO_Level3_Latch,
+        xkb.Keysym.ISO_Level3_Lock,
+        xkb.Keysym.ISO_Level5_Shift,
+        xkb.Keysym.ISO_Level5_Latch,
+        xkb.Keysym.ISO_Level5_Lock,
+        xkb.Keysym.ISO_Group_Shift,
+        xkb.Keysym.ISO_Group_Latch,
+        xkb.Keysym.ISO_Group_Lock,
+        xkb.Keysym.ISO_Next_Group,
+        xkb.Keysym.ISO_Next_Group_Lock,
+        xkb.Keysym.ISO_Prev_Group,
+        xkb.Keysym.ISO_Prev_Group_Lock,
+        xkb.Keysym.ISO_First_Group,
+        xkb.Keysym.ISO_First_Group_Lock,
+        xkb.Keysym.ISO_Last_Group,
+        xkb.Keysym.ISO_Last_Group_Lock,
+        => return true,
+        else => return false,
+    }
+}
+
 pub fn processModifiers(group: *KeyboardGroup, modifiers: wlr.Keyboard.Modifiers) void {
     group.state.notifyModifiers(modifiers);
 }