Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
ShellSurface: implement input/focus handling
protocol/river-window-management-v1.xml | 21 ++++++++++++++++++++
river/Cursor.zig | 6 +++++-
river/IdleInhibitManager.zig | 4 ++--
river/InputPopup.zig | 3 ++-
river/SceneNodeData.zig | 2 ++
river/Seat.zig | 35 +++++++++++++++++++++++----------
river/ShellSurface.zig | 4 ++++
rivercompat/Seat.zig | 4 ++++
rivercompat/main.zig | 3 +++
9 files changed, 68 insertions(+), 14 deletions(-)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 7a41e70..ae93d19 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -1161,6 +1161,27 @@
<arg name="window" type="object" interface="river_window_v1"/>
</event>
+ <event name="shell_surface_interaction">
+ <description summary="a shell surface has been interacted with">
+ A shell surface has been interacted with beyond the pointer merely
+ passing over it. This event might be sent due to a pointer button press
+ or due to a touch/tablet tool interaction with the shell_surface.
+
+ There are no guarantees regarding how this event is sent in relation to
+ the pointer_enter and pointer_leave events as the interaction may use
+ touch or tablet tool input.
+
+ Rationale: While the shell surface does receive all wl_pointer,
+ wl_touch, etc. input events for the surface directly, these events do
+ not necessarily trigger an update sequence and therefore do not allow
+ the window manager to update focus in a race-free way.
+
+ This event is double-buffered state and will be followed by a
+ river_window_manager_v1.update event.
+ </description>
+ <arg name="shell_surface" type="object" interface="river_shell_surface_v1"/>
+ </event>
+
<request name="op_start_serial">
<description summary="start an interactive operation with a serial">
Start an interactive seat operation with a serial from either the
diff --git a/river/Cursor.zig b/river/Cursor.zig
index d8ced5b..67d22a3 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -483,7 +483,11 @@ pub fn processAxis(cursor: *Cursor, event: *const wlr.Pointer.event.Axis) void {
fn interact(cursor: Cursor, result: Scene.AtResult) void {
switch (result.data) {
.window => |window| {
- cursor.seat.pending.window_interaction = window;
+ cursor.seat.pending.interaction = .{ .window = window };
+ server.wm.dirtyPending();
+ },
+ .shell_surface => |shell_surface| {
+ cursor.seat.pending.interaction = .{ .shell_surface = shell_surface };
server.wm.dirtyPending();
},
.lock_surface => |lock_surface| {
diff --git a/river/IdleInhibitManager.zig b/river/IdleInhibitManager.zig
index 7ba4f99..a131787 100644
--- a/river/IdleInhibitManager.zig
+++ b/river/IdleInhibitManager.zig
@@ -54,10 +54,10 @@ pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
const node_data = SceneNodeData.fromSurface(node.data.wlr_inhibitor.surface) orelse continue;
switch (node_data.data) {
.window => {
- inhibited = true;
+ inhibited = true; // XXX be strict
break;
},
- .lock_surface, .override_redirect => {
+ .shell_surface, .lock_surface, .override_redirect => {
inhibited = true;
break;
},
diff --git a/river/InputPopup.zig b/river/InputPopup.zig
index 84975a2..7c6c576 100644
--- a/river/InputPopup.zig
+++ b/river/InputPopup.zig
@@ -108,7 +108,7 @@ pub fn update(input_popup: *InputPopup) void {
const focused = SceneNodeData.fromSurface(focused_surface) orelse return;
const output = switch (focused.data) {
- .window => @panic("TODO"),
+ .window, .shell_surface => @panic("TODO"),
.lock_surface => |lock_surface| lock_surface.getOutput(),
// Xwayland doesn't use the text-input protocol
.override_redirect => unreachable,
@@ -116,6 +116,7 @@ pub fn update(input_popup: *InputPopup) void {
const popup_tree = switch (focused.data) {
.window => |window| window.popup_tree,
+ .shell_surface => @panic("TODO"),
.lock_surface => |_| server.scene.layers.popups, // XXX Do we need per-lock-surface popup trees?
// Xwayland doesn't use the text-input protocol
.override_redirect => unreachable,
diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig
index d830f21..aae8273 100644
--- a/river/SceneNodeData.zig
+++ b/river/SceneNodeData.zig
@@ -25,10 +25,12 @@ const util = @import("util.zig");
const LockSurface = @import("LockSurface.zig");
const InputPopup = @import("InputPopup.zig");
const Window = @import("Window.zig");
+const ShellSurface = @import("ShellSurface.zig");
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
pub const Data = union(enum) {
window: *Window,
+ shell_surface: *ShellSurface,
lock_surface: *LockSurface,
override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn,
};
diff --git a/river/Seat.zig b/river/Seat.zig
index ee8b659..9907f2f 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -39,6 +39,7 @@ const LockSurface = @import("LockSurface.zig");
const Output = @import("Output.zig");
const PointerBinding = @import("PointerBinding.zig");
const PointerConstraint = @import("PointerConstraint.zig");
+const ShellSurface = @import("ShellSurface.zig");
const Switch = @import("Switch.zig");
const Tablet = @import("Tablet.zig");
const Window = @import("Window.zig");
@@ -87,18 +88,20 @@ pub const WmState = struct {
pub const WmFocus = union(enum) {
none,
window: *Window,
- // TODO shell_surface: *ShellSurface,
+ shell_surface: *ShellSurface,
};
pub const Focus = union(enum) {
none,
window: *Window,
+ shell_surface: *ShellSurface,
override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn,
lock_surface: *LockSurface,
pub fn surface(target: Focus) ?*wlr.Surface {
return switch (target) {
.window => |window| window.rootSurface(),
+ .shell_surface => |shell_surface| shell_surface.surface,
.override_redirect => |override_redirect| override_redirect.xsurface.surface,
.lock_surface => |lock_surface| lock_surface.wlr_lock_surface.surface,
.none => null,
@@ -125,7 +128,7 @@ pending: struct {
/// The window entered/hovered by the pointer, if any
window: ?*Window = null,
/// The window clicked on, touched, etc.
- window_interaction: ?*Window = null,
+ interaction: WmFocus = .none,
} = .{},
/// State sent to the window manager client in the latest update sequence.
@@ -365,11 +368,18 @@ pub fn sendDirty(seat: *Seat) void {
}
}
- if (seat.pending.window_interaction) |window| {
- if (window.object) |window_v1| {
- seat_v1.sendWindowInteraction(window_v1);
- seat.pending.window_interaction = null;
- }
+ switch (seat.pending.interaction) {
+ .none => {},
+ .window => |window| {
+ if (window.object) |window_v1| {
+ seat_v1.sendWindowInteraction(window_v1);
+ seat.pending.interaction = .none;
+ }
+ },
+ .shell_surface => |shell_surface| {
+ seat_v1.sendShellSurfaceInteraction(shell_surface.object);
+ seat.pending.interaction = .none;
+ },
}
{
@@ -442,7 +452,11 @@ fn handleRequest(
const window: *Window = @ptrCast(@alignCast(data));
seat.uncommitted.focus = .{ .window = window };
},
- .focus_shell_surface => {},
+ .focus_shell_surface => |args| {
+ const data = args.shell_surface.getUserData() orelse return;
+ const shell_surface: *ShellSurface = @ptrCast(@alignCast(data));
+ seat.uncommitted.focus = .{ .shell_surface = shell_surface };
+ },
.clear_focus => seat.uncommitted.focus = .none,
.op_start_serial => {},
@@ -522,6 +536,7 @@ pub fn applyCommitted(seat: *Seat) void {
switch (seat.committed.focus) {
.none => seat.focus(.none),
.window => |window| seat.focus(.{ .window = window }),
+ .shell_surface => |shell_surface| seat.focus(.{ .shell_surface = shell_surface }),
}
switch (seat.committed.op) {
@@ -601,12 +616,12 @@ pub fn focus(seat: *Seat, new_focus: Focus) void {
// First clear the current focus
switch (seat.focused) {
.window => |window| window.destroyPopups(),
- .override_redirect, .lock_surface, .none => {},
+ .shell_surface, .override_redirect, .lock_surface, .none => {},
}
// Set the new focus
switch (new_focus) {
- .window => assert(server.lock_manager.state != .locked),
+ .window, .shell_surface => assert(server.lock_manager.state != .locked),
.lock_surface => assert(server.lock_manager.state != .unlocked),
.override_redirect, .none => {},
}
diff --git a/river/ShellSurface.zig b/river/ShellSurface.zig
index 0336e6e..11d33f2 100644
--- a/river/ShellSurface.zig
+++ b/river/ShellSurface.zig
@@ -79,6 +79,8 @@ pub fn create(
const surfaces = try Scene.SaveableSurfaces.init(tree);
_ = try surfaces.tree.createSceneSubsurfaceTree(surface);
+ try SceneNodeData.attach(&tree.node, .{ .shell_surface = shell_surface });
+
shell_surface.* = .{
.object = shell_surface_v1,
.surface = surface,
@@ -97,6 +99,8 @@ fn handleDestroy(_: *river.ShellSurfaceV1, shell_surface: *ShellSurface) void {
shell_surface.node.makeInert();
shell_surface.node.deinit();
+ shell_surface.tree.node.destroy();
+
util.gpa.destroy(shell_surface);
}
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index dc48e96..5583d31 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -96,6 +96,10 @@ fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) v
const window: *Window = @ptrCast(@alignCast(window_v1.getUserData()));
seat.focus(window);
},
+ .shell_surface_interaction => |args| {
+ const shell_surface_v1 = args.shell_surface orelse return;
+ seat_v1.focusShellSurface(shell_surface_v1);
+ },
}
}
diff --git a/rivercompat/main.zig b/rivercompat/main.zig
index e8d07e1..681a7c8 100644
--- a/rivercompat/main.zig
+++ b/rivercompat/main.zig
@@ -56,6 +56,9 @@ const Globals = struct {
globals.viewporter = registry.bind(global.name, wp.Viewporter, 1) catch return;
} else if (mem.orderZ(u8, global.interface, wp.SinglePixelBufferManagerV1.interface.name) == .eq) {
globals.single_pixel = registry.bind(global.name, wp.SinglePixelBufferManagerV1, 1) catch return;
+ } else if (mem.orderZ(u8, global.interface, wl.Seat.interface.name) == .eq) {
+ const wl_seat = registry.bind(global.name, wl.Seat, 1) catch return;
+ _ = wl_seat.getKeyboard() catch return;
}
},
.global_remove => {},