Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Root: restructure scene graph for rwm protocol
river/InputPopup.zig | 2 +-
river/LayerSurface.zig | 22 ++++-----
river/LockManager.zig | 33 +++++---------
river/LockSurface.zig | 7 +--
river/Output.zig | 120 ++++++-------------------------------------------
river/Root.zig | 87 +++++++++++++++++++++--------------
6 files changed, 89 insertions(+), 182 deletions(-)
diff --git a/river/InputPopup.zig b/river/InputPopup.zig
index b4cb096..bee0802 100644
--- a/river/InputPopup.zig
+++ b/river/InputPopup.zig
@@ -118,7 +118,7 @@ pub fn update(input_popup: *InputPopup) void {
const popup_tree = switch (focused.data) {
.window => |window| window.popup_tree,
.layer_surface => |layer_surface| layer_surface.popup_tree,
- .lock_surface => |lock_surface| lock_surface.getOutput().layers.popups,
+ .lock_surface => |_| server.root.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/LayerSurface.zig b/river/LayerSurface.zig
index 9fe11a9..42765d7 100644
--- a/river/LayerSurface.zig
+++ b/river/LayerSurface.zig
@@ -47,13 +47,13 @@ pub fn create(wlr_layer_surface: *wlr.LayerSurfaceV1) error{OutOfMemory}!void {
const layer_surface = try util.gpa.create(LayerSurface);
errdefer util.gpa.destroy(layer_surface);
- const layer_tree = output.layerSurfaceTree(wlr_layer_surface.current.layer);
+ const layer_tree = server.root.layerSurfaceTree(wlr_layer_surface.current.layer);
layer_surface.* = .{
.output = output,
.wlr_layer_surface = wlr_layer_surface,
.scene_layer_surface = try layer_tree.createSceneLayerSurfaceV1(wlr_layer_surface),
- .popup_tree = try output.layers.popups.createSceneTree(),
+ .popup_tree = try server.root.layers.popups.createSceneTree(),
};
try SceneNodeData.attach(&layer_surface.scene_layer_surface.tree.node, .{ .layer_surface = layer_surface });
@@ -103,10 +103,7 @@ fn handleMap(listener: *wl.Listener(void)) void {
const consider = wlr_surface.current.keyboard_interactive == .on_demand and
(wlr_surface.current.layer == .top or wlr_surface.current.layer == .overlay);
- handleKeyboardInteractiveExclusive(
- layer_surface.output,
- if (consider) layer_surface else null,
- );
+ handleKeyboardInteractiveExclusive(if (consider) layer_surface else null);
server.wm.dirtyPending();
}
@@ -117,7 +114,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace});
layer_surface.output.arrangeLayers();
- handleKeyboardInteractiveExclusive(layer_surface.output, null);
+ handleKeyboardInteractiveExclusive(null);
server.wm.dirtyPending();
}
@@ -129,7 +126,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
// If the layer was changed, move the LayerSurface to the proper tree.
if (wlr_layer_surface.current.committed.layer) {
- const tree = layer_surface.output.layerSurfaceTree(wlr_layer_surface.current.layer);
+ const tree = server.root.layerSurfaceTree(wlr_layer_surface.current.layer);
layer_surface.scene_layer_surface.tree.node.reparent(tree);
}
@@ -137,7 +134,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
@as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0)
{
layer_surface.output.arrangeLayers();
- handleKeyboardInteractiveExclusive(layer_surface.output, null);
+ handleKeyboardInteractiveExclusive(null);
server.wm.dirtyPending();
}
}
@@ -145,13 +142,13 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
/// Focus topmost keyboard-interactivity-exclusive layer surface above normal
/// content, or if none found, focus the surface given as `consider`.
/// Requires a call to WindowManager.dirtyPending()
-fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface) void {
+fn handleKeyboardInteractiveExclusive(consider: ?*LayerSurface) void {
if (server.lock_manager.state != .unlocked) return;
// Find the topmost layer surface (if any) in the top or overlay layers which
// requests exclusive keyboard interactivity.
const to_focus = outer: for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
- const tree = output.layerSurfaceTree(layer);
+ const tree = server.root.layerSurfaceTree(layer);
// Iterate in reverse to match rendering order.
var it = tree.children.iterator(.reverse);
while (it.next()) |node| {
@@ -179,8 +176,7 @@ fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface)
if (true) @panic("TODO");
if (false) {
if (to_focus) |s| {
- // If we found a surface on the output that requires focus, grab the focus of all
- // seats that are focusing that output.
+ // If we found a surface that requires focus, grab the focus of all seats.
seat.setFocusRaw(.{ .layer = s });
continue;
}
diff --git a/river/LockManager.zig b/river/LockManager.zig
index c40b124..5c50b10 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -135,13 +135,7 @@ fn handleLockSurfacesTimeout(manager: *LockManager) c_int {
assert(manager.state == .waiting_for_lock_surfaces);
manager.state = .waiting_for_blank;
- {
- var it = server.root.active_outputs.iterator(.forward);
- while (it.next()) |output| {
- output.normal_content.node.setEnabled(false);
- output.locked_content.node.setEnabled(true);
- }
- }
+ if (true) @panic("XXX blank all outputs");
// This call is necessary in the case that all outputs in the layout are disabled.
manager.maybeLock();
@@ -193,20 +187,11 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
log.info("session unlocked", .{});
- {
- var it = server.root.active_outputs.iterator(.forward);
- while (it.next()) |output| {
- assert(!output.normal_content.node.enabled);
- output.normal_content.node.setEnabled(true);
-
- assert(output.locked_content.node.enabled);
- output.locked_content.node.setEnabled(false);
- }
- }
+ assert(!server.root.normal_tree.node.enabled);
+ server.root.normal_tree.node.setEnabled(true);
- if (build_options.xwayland) {
- server.root.layers.override_redirect.node.setEnabled(true);
- }
+ assert(server.root.locked_tree.node.enabled);
+ server.root.locked_tree.node.setEnabled(true);
{
var it = server.input_manager.seats.first;
@@ -254,14 +239,16 @@ fn handleSurface(
};
}
-pub fn updateLockSurfaceSize(manager: *LockManager, output: *Output) void {
- const lock = manager.lock orelse return;
+pub fn lockSurfaceFromOutput(manager: *LockManager, output: *Output) ?*LockSurface {
+ const lock = manager.lock orelse return null;
var it = lock.surfaces.iterator(.forward);
while (it.next()) |wlr_lock_surface| {
const lock_surface: *LockSurface = @ptrFromInt(wlr_lock_surface.data);
if (output == lock_surface.getOutput()) {
- lock_surface.configure();
+ return lock_surface;
}
}
+
+ return null;
}
diff --git a/river/LockSurface.zig b/river/LockSurface.zig
index 60f0aa8..fb6e14c 100644
--- a/river/LockSurface.zig
+++ b/river/LockSurface.zig
@@ -46,8 +46,7 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc
};
wlr_lock_surface.data = @intFromPtr(lock_surface);
- const output = lock_surface.getOutput();
- const tree = try output.locked_content.createSceneSubsurfaceTree(wlr_lock_surface.surface);
+ const tree = try server.root.locked_tree.createSceneSubsurfaceTree(wlr_lock_surface.surface);
errdefer tree.node.destroy();
try SceneNodeData.attach(&tree.node, .{ .lock_surface = lock_surface });
@@ -104,10 +103,6 @@ pub fn configure(lock_surface: *LockSurface) void {
fn handleMap(listener: *wl.Listener(void)) void {
const lock_surface: *LockSurface = @fieldParentPtr("map", listener);
- const output = lock_surface.getOutput();
-
- output.normal_content.node.setEnabled(false);
- output.locked_content.node.setEnabled(true);
// Unfortunately the surface commit handlers for the scene subsurface tree corresponding to
// this lock surface won't be called until after this function returns, which means that we cannot
diff --git a/river/Output.zig b/river/Output.zig
index b73cffb..79a7f0a 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -52,31 +52,6 @@ active_link: wl.list.Link,
/// TODO: this should be part of the output's State
usable_box: wlr.Box,
-/// Scene node representing the entire output.
-/// Position must be updated when the output is moved in the layout.
-tree: *wlr.SceneTree,
-normal_content: *wlr.SceneTree,
-locked_content: *wlr.SceneTree,
-
-/// Child nodes of normal_content
-layers: struct {
- background_color_rect: *wlr.SceneRect,
- /// Background layer shell layer
- background: *wlr.SceneTree,
- /// Bottom layer shell layer
- bottom: *wlr.SceneTree,
- /// Windows and shell surfaces of the window manager
- wm: *wlr.SceneTree,
- /// Top layer shell layer
- top: *wlr.SceneTree,
- /// Fullscreen windows
- fullscreen: *wlr.SceneTree,
- /// Overlay layer shell layer
- overlay: *wlr.SceneTree,
- /// Popups from xdg-shell and input-method-v2 clients
- popups: *wlr.SceneTree,
-},
-
/// Tracks the currently presented frame on the output as it pertains to ext-session-lock.
/// The output is initially considered blanked:
/// If using the DRM backend it will be blanked with the initial modeset.
@@ -102,20 +77,6 @@ lock_render_state: enum {
/// This request is handled while rendering the next frame in handleFrame().
gamma_dirty: bool = false,
-/// The state most recently sent to the layout generator and clients.
-/// This state is immutable until all clients have replied and the transaction
-/// is completed, at which point this inflight state is copied to current.
-inflight: struct {
- /// The window to be made fullscreen, if any.
- fullscreen: ?*Window = null,
-} = .{},
-
-/// The current state represented by the scene graph.
-current: struct {
- /// The currently fullscreen window, if any.
- fullscreen: ?*Window = null,
-} = .{},
-
destroy: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleDestroy),
request_state: wl.Listener(*wlr.Output.event.RequestState) = wl.Listener(*wlr.Output.event.RequestState).init(handleRequestState),
frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
@@ -175,31 +136,11 @@ pub fn create(wlr_output: *wlr.Output) !void {
const scene_output = try server.root.scene.createSceneOutput(wlr_output);
- const tree = try server.root.layers.outputs.createSceneTree();
- const normal_content = try tree.createSceneTree();
-
output.* = .{
.wlr_output = wlr_output,
.scene_output = scene_output,
.all_link = undefined,
.active_link = undefined,
- .tree = tree,
- .normal_content = normal_content,
- .locked_content = try tree.createSceneTree(),
- .layers = .{
- .background_color_rect = try normal_content.createSceneRect(
- width,
- height,
- &server.config.background_color,
- ),
- .background = try normal_content.createSceneTree(),
- .bottom = try normal_content.createSceneTree(),
- .wm = try normal_content.createSceneTree(),
- .top = try normal_content.createSceneTree(),
- .fullscreen = try normal_content.createSceneTree(),
- .overlay = try normal_content.createSceneTree(),
- .popups = try normal_content.createSceneTree(),
- },
.usable_box = .{
.x = 0,
.y = 0,
@@ -209,9 +150,6 @@ pub fn create(wlr_output: *wlr.Output) !void {
};
wlr_output.data = @intFromPtr(output);
- _ = try output.layers.fullscreen.createSceneRect(width, height, &[_]f32{ 0, 0, 0, 1.0 });
- output.layers.fullscreen.node.setEnabled(false);
-
wlr_output.events.destroy.add(&output.destroy);
wlr_output.events.request_state.add(&output.request_state);
wlr_output.events.frame.add(&output.frame);
@@ -225,16 +163,6 @@ pub fn create(wlr_output: *wlr.Output) !void {
output.handleEnableDisable();
}
-pub fn layerSurfaceTree(output: Output, layer: zwlr.LayerShellV1.Layer) *wlr.SceneTree {
- const trees = [_]*wlr.SceneTree{
- output.layers.background,
- output.layers.bottom,
- output.layers.top,
- output.layers.overlay,
- };
- return trees[@intCast(@intFromEnum(layer))];
-}
-
/// Arrange all layer surfaces of this output and adjust the usable area.
/// Will arrange windows as well if the usable area changes.
/// Requires a call to WindowManager.dirtyPending()
@@ -264,6 +192,7 @@ fn sendLayerConfigures(
usable_box: *wlr.Box,
mode: enum { exclusive, non_exclusive },
) void {
+ if (true) @panic("XXX");
for ([_]zwlr.LayerShellV1.Layer{ .background, .bottom, .top, .overlay }) |layer| {
const tree = output.layerSurfaceTree(layer);
var it = tree.children.safeIterator(.forward);
@@ -321,8 +250,6 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
output.frame.link.remove();
output.present.link.remove();
- output.tree.node.destroy();
-
output.wlr_output.data = 0;
util.gpa.destroy(output);
@@ -362,9 +289,8 @@ pub fn applyState(output: *Output, state: *wlr.Output.State) error{CommitFailed}
}
if (state.committed.mode) {
- output.updateBackgroundRect();
output.arrangeLayers();
- server.lock_manager.updateLockSurfaceSize(output);
+ if (server.lock_manager.lockSurfaceFromOutput(output)) |s| s.configure();
}
}
@@ -382,40 +308,14 @@ fn handleEnableDisable(output: *Output) void {
}
pub fn updateLockRenderStateOnEnableDisable(output: *Output) void {
- // We can't assert the current state of normal_content/locked_content
- // here as this output may be newly created.
if (output.wlr_output.enabled) {
- switch (server.lock_manager.state) {
- .unlocked => {
- assert(output.lock_render_state == .blanked);
- output.normal_content.node.setEnabled(true);
- output.locked_content.node.setEnabled(false);
- },
- .waiting_for_lock_surfaces, .waiting_for_blank, .locked => {
- assert(output.lock_render_state == .blanked);
- output.normal_content.node.setEnabled(false);
- output.locked_content.node.setEnabled(true);
- },
- }
+ assert(output.lock_render_state == .blanked);
} else {
// Disabling and re-enabling an output always blanks it.
output.lock_render_state = .blanked;
- output.normal_content.node.setEnabled(false);
- output.locked_content.node.setEnabled(true);
}
}
-pub fn updateBackgroundRect(output: *Output) void {
- var width: c_int = undefined;
- var height: c_int = undefined;
- output.wlr_output.effectiveResolution(&width, &height);
- output.layers.background_color_rect.setSize(width, height);
-
- var it = output.layers.fullscreen.children.iterator(.forward);
- const fullscreen_background: *wlr.SceneRect = @fieldParentPtr("node", it.next().?);
- fullscreen_background.setSize(width, height);
-}
-
fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
const output: *Output = @fieldParentPtr("frame", listener);
const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?;
@@ -457,12 +357,20 @@ fn renderAndCommit(output: *Output, scene_output: *wlr.SceneOutput) !void {
if (!scene_output.commit(null)) return error.CommitFailed;
}
+ const lock_surface_mapped = blk: {
+ if (server.lock_manager.lockSurfaceFromOutput(output)) |lock_surface| {
+ break :blk lock_surface.wlr_lock_surface.surface.mapped;
+ } else {
+ break :blk false;
+ }
+ };
+
if (server.lock_manager.state == .locked or
- (server.lock_manager.state == .waiting_for_lock_surfaces and output.locked_content.node.enabled) or
+ (server.lock_manager.state == .waiting_for_lock_surfaces and lock_surface_mapped) or
server.lock_manager.state == .waiting_for_blank)
{
- assert(!output.normal_content.node.enabled);
- assert(output.locked_content.node.enabled);
+ assert(!server.root.normal_tree.node.enabled);
+ assert(server.root.locked_tree.node.enabled);
switch (server.lock_manager.state) {
.unlocked => unreachable,
diff --git a/river/Root.zig b/river/Root.zig
index 0f6fb10..4ecf190 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -37,23 +37,38 @@ const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
scene: *wlr.Scene,
/// All windows, status bars, drowdown menus, etc. that can recieve pointer events and similar.
-interactive_content: *wlr.SceneTree,
-/// Drag icons, which cannot recieve e.g. pointer events and are therefore kept in a separate tree.
+interactive_tree: *wlr.SceneTree,
+/// Drag icons, which cannot recieve e.g. pointer events and are therefore kept
+/// in a separate tree from the interactive tree.
drag_icons: *wlr.SceneTree,
+/// Always disabled, used for staging changes
+/// TODO can this be refactored away?
+hidden_tree: *wlr.SceneTree,
+/// Direct child of interactive_tree, disabled when the session is locked
+normal_tree: *wlr.SceneTree,
+/// Direct child of interactive_tree, enabled when the session is locked
+locked_tree: *wlr.SceneTree,
-/// All direct children of the interactive_content scene node
+/// All direct children of the normal_tree scene node
layers: struct {
- /// Parent tree for output trees which have their position updated when
- /// outputs are moved in the layout.
- outputs: *wlr.SceneTree,
+ /// Background layer shell layer
+ background: *wlr.SceneTree,
+ /// Bottom layer shell layer
+ bottom: *wlr.SceneTree,
+ /// Windows and shell surfaces of the window manager
+ wm: *wlr.SceneTree,
+ /// Top layer shell layer
+ top: *wlr.SceneTree,
+ /// Overlay layer shell layer
+ overlay: *wlr.SceneTree,
+ /// Popups from xdg-shell and input-method-v2 clients
+ popups: *wlr.SceneTree,
/// Xwayland override redirect windows are a legacy wart that decide where
/// to place themselves in layout coordinates. Unfortunately this is how
/// X11 decided to make dropdown menus and the like possible.
override_redirect: if (build_options.xwayland) *wlr.SceneTree else void,
},
-hidden_tree: *wlr.SceneTree,
-
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
output_layout: *wlr.OutputLayout,
@@ -90,28 +105,34 @@ pub fn init(root: *Root) !void {
const scene = try wlr.Scene.create();
errdefer scene.tree.node.destroy();
- const interactive_content = try scene.tree.createSceneTree();
+ const interactive_tree = try scene.tree.createSceneTree();
const drag_icons = try scene.tree.createSceneTree();
const hidden_tree = try scene.tree.createSceneTree();
hidden_tree.node.setEnabled(false);
- const outputs = try interactive_content.createSceneTree();
- const override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree();
+ const normal_tree = try interactive_tree.createSceneTree();
+ const locked_tree = try interactive_tree.createSceneTree();
root.* = .{
.scene = scene,
- .interactive_content = interactive_content,
+ .interactive_tree = interactive_tree,
.drag_icons = drag_icons,
+ .hidden_tree = hidden_tree,
+ .normal_tree = normal_tree,
+ .locked_tree = locked_tree,
.layers = .{
- .outputs = outputs,
- .override_redirect = override_redirect,
+ .background = try normal_tree.createSceneTree(),
+ .bottom = try normal_tree.createSceneTree(),
+ .wm = try normal_tree.createSceneTree(),
+ .top = try normal_tree.createSceneTree(),
+ .overlay = try normal_tree.createSceneTree(),
+ .popups = try normal_tree.createSceneTree(),
+ .override_redirect = if (build_options.xwayland) try normal_tree.createSceneTree(),
},
.output_layout = output_layout,
.all_outputs = undefined,
.active_outputs = undefined,
- .hidden_tree = hidden_tree,
-
.presentation = try wlr.Presentation.create(server.wl_server, server.backend),
.xdg_output_manager = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout),
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
@@ -142,12 +163,12 @@ pub const AtResult = struct {
data: SceneNodeData.Data,
};
-/// Return information about what is currently rendered in the interactive_content
+/// Return information about what is currently rendered in the interactive_tree
/// tree at the given layout coordinates, taking surface input regions into account.
pub fn at(root: Root, lx: f64, ly: f64) ?AtResult {
var sx: f64 = undefined;
var sy: f64 = undefined;
- const node = root.interactive_content.node.at(lx, ly, &sx, &sy) orelse return null;
+ const node = root.interactive_tree.node.at(lx, ly, &sx, &sy) orelse return null;
const surface: ?*wlr.Surface = blk: {
if (node.type == .buffer) {
@@ -172,6 +193,16 @@ pub fn at(root: Root, lx: f64, ly: f64) ?AtResult {
}
}
+pub fn layerSurfaceTree(root: Root, layer: zwlr.LayerShellV1.Layer) *wlr.SceneTree {
+ const trees = [_]*wlr.SceneTree{
+ root.layers.background,
+ root.layers.bottom,
+ root.layers.top,
+ root.layers.overlay,
+ };
+ return trees[@intCast(@intFromEnum(layer))];
+}
+
fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
const log = std.log.scoped(.output_manager);
@@ -203,22 +234,11 @@ pub fn deactivateOutput(root: *Root, output: *Output) void {
}
root.output_layout.remove(output.wlr_output);
- output.tree.node.setEnabled(false);
output.active_link.remove();
output.active_link.init();
- // Close all layer surfaces on the removed output
- for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top, .bottom, .background }) |layer| {
- const tree = output.layerSurfaceTree(layer);
- var it = tree.children.safeIterator(.forward);
- while (it.next()) |scene_node| {
- assert(scene_node.type == .tree);
- if (@as(?*SceneNodeData, @ptrFromInt(scene_node.data))) |node_data| {
- node_data.data.layer_surface.wlr_layer_surface.destroy();
- }
- }
- }
+ // XXX Close all layer surfaces on the removed output
// We must call reconfigureDevices here to unmap devices that might be mapped to this output
// in order to prevent a segfault in wlroots.
@@ -271,9 +291,10 @@ pub fn handleOutputConfigChange(root: *Root) !void {
var box: wlr.Box = undefined;
root.output_layout.getBox(output.wlr_output, &box);
- output.tree.node.setEnabled(!box.empty());
- output.tree.node.setPosition(box.x, box.y);
- output.scene_output.setPosition(box.x, box.y);
+ // XXX
+ //output.tree.node.setEnabled(!box.empty());
+ //output.tree.node.setPosition(box.x, box.y);
+ //output.scene_output.setPosition(box.x, box.y);
const head = try wlr.OutputConfigurationV1.Head.create(config, output.wlr_output);
head.state.x = box.x;