Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
protocol: add xkb_binding_seat_v1.modifiers_{watch,update}
build.zig | 2 +-
protocol/river-xkb-bindings-v1.xml | 52 +++++++++++++++++++++++++++++++++++---
river/KeyboardGroup.zig | 16 ++++++++++++
river/XkbBindings.zig | 2 +-
river/XkbBindingsSeat.zig | 29 ++++++++++++++++++---
5 files changed, 93 insertions(+), 8 deletions(-)
diff --git a/build.zig b/build.zig
index 71ca684..97392e1 100644
--- a/build.zig
+++ b/build.zig
@@ -121,7 +121,7 @@ pub fn build(b: *Build) !void {
scanner.generate("wp_color_representation_manager_v1", 1);
scanner.generate("river_window_manager_v1", 4);
- scanner.generate("river_xkb_bindings_v1", 2);
+ scanner.generate("river_xkb_bindings_v1", 3);
scanner.generate("river_layer_shell_v1", 1);
scanner.generate("river_input_manager_v1", 1);
scanner.generate("river_libinput_config_v1", 1);
diff --git a/protocol/river-xkb-bindings-v1.xml b/protocol/river-xkb-bindings-v1.xml
index de4f100..5d32967 100644
--- a/protocol/river-xkb-bindings-v1.xml
+++ b/protocol/river-xkb-bindings-v1.xml
@@ -33,7 +33,7 @@
document are to be interpreted as described in IETF RFC 2119.
</description>
- <interface name="river_xkb_bindings_v1" version="2">
+ <interface name="river_xkb_bindings_v1" version="3">
<description summary="xkbcommon bindings global interface">
This global interface should only be advertised to the client if the
river_window_manager_v1 global is also advertised.
@@ -76,7 +76,7 @@
</request>
</interface>
- <interface name="river_xkb_binding_v1" version="2">
+ <interface name="river_xkb_binding_v1" version="3">
<description summary="configure a xkb key binding, receive trigger events">
This object allows the window manager to configure a xkbcommon key binding
and receive events when the key binding is triggered.
@@ -200,7 +200,7 @@
</event>
</interface>
- <interface name="river_xkb_bindings_seat_v1" version="2">
+ <interface name="river_xkb_bindings_seat_v1" version="3">
<description summary="xkb bindings seat">
This object manages xkb bindings state associated with a specific seat.
</description>
@@ -264,5 +264,51 @@
state has been sent by the server.
</description>
</event>
+
+ <request name="modifiers_watch" since="3">
+ <description summary="watch for change in active modifiers">
+ Request that the server send the modifiers_update event whenever a state
+ change occurs for at least one of the modifiers specified by the
+ modifiers argument.
+
+ The window manager should make this request with the modifiers argument
+ set to 0 when it no longer wishes to take action based on a change in
+ modifiers.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+ </description>
+ <arg name="modifiers" type="uint" enum="river_seat_v1.modifiers"/>
+ </request>
+
+ <event name="modifiers_update" since="3">
+ <description summary="active modifiers for the seat changed">
+ The set of currently active modifiers for the seat changed. This event
+ is only sent when there is a change in state for modifiers marked as
+ watched using the modifiers_watch request.
+
+ The old and new arguments convey the set of modifiers active before and
+ after the change. All modifiers are included in the old and new
+ arguments, including modifiers that are not watched.
+
+ Since this event is only sent when there is a change in state for
+ watched modifiers, it follows that at least one watched modifier is
+ active in old but inactive in new or vice-versa.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+ The compositor should wait for the manage sequence to complete before
+ processing further input events. This allows the window manager client
+ to, for example, modify key bindings and keyboard focus without racing
+ against future input events. The window manager should of course respond
+ as soon as possible as the capacity of the compositor to buffer incoming
+ input events is finite.
+ </description>
+ <arg name="old" type="uint" enum="river_seat_v1.modifiers"
+ summary="previously active modifiers"/>
+ <arg name="new" type="uint" enum="river_seat_v1.modifiers"
+ summary="currently active modifiers"/>
+ </event>
</interface>
</protocol>
diff --git a/river/KeyboardGroup.zig b/river/KeyboardGroup.zig
index 00c7d34..177b5d6 100644
--- a/river/KeyboardGroup.zig
+++ b/river/KeyboardGroup.zig
@@ -63,6 +63,7 @@ config: Keyboard.Config,
/// This is the keyboard that actually gets passed to wlr_seat functions for
/// setting keyboard focus.
state: wlr.Keyboard,
+modifiers_old: wlr.Keyboard.ModifierMask = .{},
/// Maps from pressed libinput keycode (not xkb keycode) to information
/// about where the press event has been sent.
@@ -360,6 +361,21 @@ pub fn processModifiers(group: *KeyboardGroup, modifiers: wlr.Keyboard.Modifiers
fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
const group: *KeyboardGroup = @fieldParentPtr("modifiers", listener);
+
+ {
+ const old: u32 = @bitCast(group.modifiers_old);
+ const new: u32 = @bitCast(group.state.getModifiers());
+ const watched: u32 = @bitCast(group.seat.xkb_bindings_seat.requested.mods_watched);
+ if (old & watched != new & watched) {
+ group.seat.xkb_bindings_seat.scheduled.mods_update = .{
+ .old = @bitCast(old),
+ .new = @bitCast(new),
+ };
+ server.wm.dirtyWindowing();
+ }
+ group.modifiers_old = @bitCast(new);
+ }
+
if (group.getInputMethodGrab()) |keyboard_grab| {
keyboard_grab.setKeyboard(&group.state);
keyboard_grab.sendModifiers(&group.state.modifiers);
diff --git a/river/XkbBindings.zig b/river/XkbBindings.zig
index f6f18ef..05d2527 100644
--- a/river/XkbBindings.zig
+++ b/river/XkbBindings.zig
@@ -22,7 +22,7 @@ server_destroy: wl.Listener(*wl.Server) = .init(handleServerDestroy),
pub fn init(bindings: *XkbBindings) !void {
bindings.* = .{
- .global = try wl.Global.create(server.wl_server, river.XkbBindingsV1, 2, ?*anyopaque, null, bind),
+ .global = try wl.Global.create(server.wl_server, river.XkbBindingsV1, 3, ?*anyopaque, null, bind),
};
server.wl_server.addDestroyListener(&bindings.server_destroy);
}
diff --git a/river/XkbBindingsSeat.zig b/river/XkbBindingsSeat.zig
index 932d70d..14fef8d 100644
--- a/river/XkbBindingsSeat.zig
+++ b/river/XkbBindingsSeat.zig
@@ -21,14 +21,24 @@ object: ?*river.XkbBindingsSeatV1 = null,
scheduled: struct {
ate_unbound_key: bool = false,
+ mods_update: ?struct {
+ old: river.SeatV1.Modifiers,
+ new: river.SeatV1.Modifiers,
+ } = null,
} = .{},
requested: struct {
next_key_change: enum {
none,
ensure_eaten,
cancel_ensure_eaten,
- } = .none,
-} = .{},
+ },
+ mods_watched: river.SeatV1.Modifiers,
+
+ const init: @This() = .{
+ .next_key_change = .none,
+ .mods_watched = .{},
+ };
+} = .init,
ensure_next_key_eaten: bool = false,
@@ -49,7 +59,7 @@ pub fn createObject(
pub fn makeInert(bindings_seat: *XkbBindingsSeat) void {
if (bindings_seat.object) |object| {
object.setHandler(?*anyopaque, handleRequestInert, null, null);
- bindings_seat.object = null;
+ handleDestroy(object, bindings_seat);
}
}
@@ -63,6 +73,7 @@ fn handleRequestInert(
fn handleDestroy(_: *river.XkbBindingsSeatV1, bindings_seat: *XkbBindingsSeat) void {
bindings_seat.object = null;
+ bindings_seat.requested = .init;
}
fn handleRequest(
@@ -81,6 +92,10 @@ fn handleRequest(
if (!server.wm.ensureWindowing()) return;
bindings_seat.requested.next_key_change = .cancel_ensure_eaten;
},
+ .modifiers_watch => |args| {
+ if (!server.wm.ensureWindowing()) return;
+ bindings_seat.requested.mods_watched = args.modifiers;
+ },
}
}
@@ -93,6 +108,14 @@ pub fn manageStart(bindings_seat: *XkbBindingsSeat) void {
}
bindings_seat.scheduled.ate_unbound_key = false;
}
+ if (bindings_seat.scheduled.mods_update) |mods| {
+ if (bindings_seat.object) |object| {
+ if (object.getVersion() >= 3) {
+ object.sendModifiersUpdate(mods.old, mods.new);
+ }
+ }
+ bindings_seat.scheduled.mods_update = null;
+ }
}
pub fn manageFinish(bindings_seat: *XkbBindingsSeat) void {