From b38676f0784b59a0a5b858fd947e7aad6d5872d7 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 31 Jan 2023 15:47:19 +0100 Subject: [PATCH] session-lock: use the scene graph --- river/Cursor.zig | 12 ++++++---- river/LockManager.zig | 35 ++++++++++++++++++++++++++++- river/LockSurface.zig | 26 +++++++++++++--------- river/Output.zig | 49 +++++++++++++++++++++++++++-------------- river/Root.zig | 47 +++++++++++++++++---------------------- river/SceneNodeData.zig | 2 ++ river/Seat.zig | 4 ++-- river/XdgToplevel.zig | 2 +- river/render.zig | 22 +++++++++++++++++- 9 files changed, 136 insertions(+), 63 deletions(-) diff --git a/river/Cursor.zig b/river/Cursor.zig index f696a4e..953b57f 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -368,7 +368,7 @@ fn updateKeyboardFocus(self: Self, result: Root.AtResult) void { self.seat.setFocusRaw(.{ .lock_surface = lock_surface }); }, .xwayland_override_redirect => |override_redirect| { - assert(server.lock_manager.state == .unlocked); + assert(server.lock_manager.state != .locked); override_redirect.focusIfDesired(); }, } @@ -857,7 +857,7 @@ fn shouldPassthrough(self: Self) bool { return false; }, .resize, .move => { - assert(server.lock_manager.state == .unlocked); + assert(server.lock_manager.state != .locked); const target = if (self.mode == .resize) self.mode.resize.view else self.mode.move.view; // The target view is no longer visible, is part of the layout, or is fullscreen. return target.current.tags & target.output.current.tags == 0 or @@ -872,8 +872,12 @@ fn passthrough(self: *Self, time: u32) void { assert(self.mode == .passthrough); if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| { - // TODO audit session lock assertions after wlr_scene upgrade - assert((result.node == .lock_surface) == (server.lock_manager.state != .unlocked)); + if (result.node == .lock_surface) { + assert(server.lock_manager.state != .unlocked); + } else { + assert(server.lock_manager.state != .locked); + } + if (result.surface) |surface| { self.seat.wlr_seat.pointerNotifyEnter(surface, result.sx, result.sy); self.seat.wlr_seat.pointerNotifyMotion(time, result.sx, result.sy); diff --git a/river/LockManager.zig b/river/LockManager.zig index eab3c4a..9757d93 100644 --- a/river/LockManager.zig +++ b/river/LockManager.zig @@ -130,6 +130,24 @@ fn handleLockSurfacesTimeout(manager: *LockManager) c_int { assert(manager.state == .waiting_for_lock_surfaces); manager.state = .waiting_for_blank; + { + var it = server.root.outputs.first; + while (it) |node| : (it = node.next) { + const output = &node.data; + + switch (output.lock_render_state) { + .unlocked, .pending_lock_surface => {}, + .pending_blank, .blanked, .lock_surface => { + assert(!output.normal_content.node.enabled); + assert(output.locked_content.node.enabled); + }, + } + + output.normal_content.node.setEnabled(false); + output.locked_content.node.setEnabled(true); + } + } + // This call is necessary in the case that all outputs in the layout are disabled. manager.maybeLock(); @@ -187,6 +205,19 @@ fn handleUnlock(listener: *wl.Listener(void)) void { log.info("session unlocked", .{}); + { + var it = server.root.outputs.first; + while (it) |node| : (it = node.next) { + const output = &node.data; + + assert(!output.normal_content.node.enabled); + output.normal_content.node.setEnabled(true); + + assert(output.locked_content.node.enabled); + output.locked_content.node.setEnabled(false); + } + } + { var it = server.input_manager.seats.first; while (it) |node| : (it = node.next) { @@ -230,5 +261,7 @@ fn handleSurface( assert(manager.state != .unlocked); assert(manager.lock != null); - LockSurface.create(wlr_lock_surface, manager.lock.?); + LockSurface.create(wlr_lock_surface, manager.lock.?) catch { + wlr_lock_surface.resource.postNoMemory(); + }; } diff --git a/river/LockSurface.zig b/river/LockSurface.zig index 541944f..0bc4ba4 100644 --- a/river/LockSurface.zig +++ b/river/LockSurface.zig @@ -17,6 +17,7 @@ const LockSurface = @This(); const std = @import("std"); +const assert = std.debug.assert; const wlr = @import("wlroots"); const wl = @import("wayland").server.wl; @@ -25,6 +26,7 @@ const util = @import("util.zig"); const Output = @import("Output.zig"); const Seat = @import("Seat.zig"); +const SceneNodeData = @import("SceneNodeData.zig"); wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLockV1, @@ -33,11 +35,8 @@ output_mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleOutp map: wl.Listener(void) = wl.Listener(void).init(handleMap), surface_destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy), -pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLockV1) void { - const lock_surface = util.gpa.create(LockSurface) catch { - wlr_lock_surface.resource.getClient().postNoMemory(); - return; - }; +pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLockV1) error{OutOfMemory}!void { + const lock_surface = try util.gpa.create(LockSurface); lock_surface.* = .{ .wlr_lock_surface = wlr_lock_surface, @@ -45,6 +44,10 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc }; wlr_lock_surface.data = @ptrToInt(lock_surface); + const output = lock_surface.getOutput(); + const tree = try output.locked_content.createSceneSubsurfaceTree(wlr_lock_surface.surface); + try SceneNodeData.attach(&tree.node, .{ .lock_surface = lock_surface }); + wlr_lock_surface.output.events.mode.add(&lock_surface.output_mode); wlr_lock_surface.events.map.add(&lock_surface.map); wlr_lock_surface.events.destroy.add(&lock_surface.surface_destroy); @@ -53,8 +56,6 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc } pub fn destroy(lock_surface: *LockSurface) void { - lock_surface.output().lock_surface = null; - { var surface_it = lock_surface.lock.surfaces.iterator(.forward); const new_focus: Seat.FocusTarget = while (surface_it.next()) |surface| { @@ -79,7 +80,7 @@ pub fn destroy(lock_surface: *LockSurface) void { util.gpa.destroy(lock_surface); } -pub fn output(lock_surface: *LockSurface) *Output { +fn getOutput(lock_surface: *LockSurface) *Output { return @intToPtr(*Output, lock_surface.wlr_lock_surface.output.data); } @@ -88,14 +89,19 @@ fn handleOutputMode(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { var output_width: i32 = undefined; var output_height: i32 = undefined; - lock_surface.output().wlr_output.effectiveResolution(&output_width, &output_height); + lock_surface.getOutput().wlr_output.effectiveResolution(&output_width, &output_height); _ = lock_surface.wlr_lock_surface.configure(@intCast(u32, output_width), @intCast(u32, output_height)); } fn handleMap(listener: *wl.Listener(void)) void { const lock_surface = @fieldParentPtr(LockSurface, "map", listener); - lock_surface.output().lock_surface = lock_surface; + const output = lock_surface.getOutput(); + assert(output.normal_content.node.enabled); + output.normal_content.node.setEnabled(false); + + assert(!output.locked_content.node.enabled); + output.locked_content.node.setEnabled(true); { var it = server.input_manager.seats.first; diff --git a/river/Output.zig b/river/Output.zig index ca4ac29..83b4831 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -67,11 +67,12 @@ 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, /// The top of the stack is the "most important" view. views: ViewStack(View) = .{}, -lock_surface: ?*LockSurface = null, /// 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. @@ -121,18 +122,17 @@ mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode), frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame), present: wl.Listener(*wlr.Output.event.Present) = wl.Listener(*wlr.Output.event.Present).init(handlePresent), -pub fn init(self: *Self, wlr_output: *wlr.Output) !void { - if (!wlr_output.initRender(server.allocator, server.renderer)) return; +pub fn create(wlr_output: *wlr.Output) !void { + const node = try util.gpa.create(std.TailQueue(Self).Node); + errdefer util.gpa.destroy(node); + const self = &node.data; + + if (!wlr_output.initRender(server.allocator, server.renderer)) return error.InitRenderFailed; - // Some backends don't have modes. DRM+KMS does, and we need to set a mode - // before we can use the output. The mode is a tuple of (width, height, - // refresh rate), and each monitor supports only a specific set of modes. We - // just pick the monitor's preferred mode, a more sophisticated compositor - // would let the user configure it. if (wlr_output.preferredMode()) |preferred_mode| { wlr_output.setMode(preferred_mode); wlr_output.enable(true); - wlr_output.commit() catch |err| { + wlr_output.commit() catch { var it = wlr_output.modes.iterator(.forward); while (it.next()) |mode| { if (mode == preferred_mode) continue; @@ -140,15 +140,18 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void { wlr_output.commit() catch continue; // This mode works, use it break; - } else { - return err; } + // If no mode works, then we will just leave the output disabled. + // Perhaps the user will want to set a custom mode using wlr-output-management. }; } + const tree = try server.root.scene.tree.createSceneTree(); self.* = .{ .wlr_output = wlr_output, - .tree = try server.root.scene.tree.createSceneTree(), + .tree = tree, + .normal_content = try tree.createSceneTree(), + .locked_content = try tree.createSceneTree(), .usable_box = undefined, }; wlr_output.data = @ptrToInt(self); @@ -162,8 +165,8 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void { // Ensure that a cursor image at the output's scale factor is loaded // for each seat. var it = server.input_manager.seats.first; - while (it) |node| : (it = node.next) { - const seat = &node.data; + while (it) |seat_node| : (it = seat_node.next) { + const seat = &seat_node.data; seat.cursor.xcursor_manager.load(wlr_output.scale) catch std.log.scoped(.cursor).err("failed to load xcursor theme at scale {}", .{wlr_output.scale}); } @@ -177,6 +180,12 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void { self.wlr_output.effectiveResolution(&self.usable_box.width, &self.usable_box.height); self.setTitle(); + + const ptr_node = try util.gpa.create(std.TailQueue(*Self).Node); + ptr_node.data = &node.data; + server.root.all_outputs.append(ptr_node); + + handleEnable(&self.enable, self.wlr_output); } pub fn getLayer(self: *Self, layer: zwlr.LayerShellV1.Layer) *std.TailQueue(LayerSurface) { @@ -477,8 +486,6 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { } } - if (self.lock_surface) |surface| surface.destroy(); - // Remove all listeners self.destroy.link.remove(); self.enable.link.remove(); @@ -503,11 +510,19 @@ fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) vo // already been added. if (wlr_output.enabled) server.root.addOutput(self); + // We can't assert the current state of normal_content/locked_content + // here as this output may be newly created. if (wlr_output.enabled) { switch (server.lock_manager.state) { - .unlocked => self.lock_render_state = .unlocked, + .unlocked => { + self.lock_render_state = .unlocked; + self.normal_content.node.setEnabled(true); + self.locked_content.node.setEnabled(false); + }, .waiting_for_lock_surfaces, .waiting_for_blank, .locked => { assert(self.lock_render_state == .blanked); + self.normal_content.node.setEnabled(false); + self.locked_content.node.setEnabled(true); }, } } else { diff --git a/river/Root.zig b/river/Root.zig index 429bc74..2eebd68 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -103,6 +103,8 @@ pub fn init(self: *Self) !void { .noop_output = .{ .wlr_output = noop_wlr_output, .tree = try scene.tree.createSceneTree(), + .normal_content = try scene.tree.createSceneTree(), + .locked_content = try scene.tree.createSceneTree(), .usable_box = .{ .x = 0, .y = 0, .width = 0, .height = 0 }, }, }; @@ -153,14 +155,15 @@ pub fn at(self: Self, lx: f64, ly: f64) ?AtResult { var it: ?*wlr.SceneNode = node_at; while (it) |node| : (it = node.parent) { if (@intToPtr(?*SceneNodeData, node.data)) |scene_node_data| { - switch (scene_node_data.data) { - .view => |view| return .{ - .surface = surface, - .sx = sx, - .sy = sy, - .node = .{ .view = view }, + return .{ + .surface = surface, + .sx = sx, + .sy = sy, + .node = switch (scene_node_data.data) { + .view => |view| .{ .view = view }, + .lock_surface => |lock_surface| .{ .lock_surface = lock_surface }, }, - } + }; } } } @@ -168,28 +171,18 @@ pub fn at(self: Self, lx: f64, ly: f64) ?AtResult { return null; } -fn handleNewOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { - const self = @fieldParentPtr(Self, "new_output", listener); - std.log.scoped(.output_manager).debug("new output {s}", .{wlr_output.name}); +fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { + const log = std.log.scoped(.output_manager); - const node = util.gpa.create(std.TailQueue(Output).Node) catch { - wlr_output.destroy(); - return; - }; - node.data.init(wlr_output) catch { - wlr_output.destroy(); - util.gpa.destroy(node); - return; - }; - const ptr_node = util.gpa.create(std.TailQueue(*Output).Node) catch { - wlr_output.destroy(); - util.gpa.destroy(node); - return; - }; - ptr_node.data = &node.data; + log.debug("new output {s}", .{wlr_output.name}); - self.all_outputs.append(ptr_node); - self.addOutput(&node.data); + Output.create(wlr_output) catch |err| { + switch (err) { + error.OutOfMemory => log.err("out of memory", .{}), + error.InitRenderFailed => log.err("failed to initialize renderer for output {s}", .{wlr_output.name}), + } + wlr_output.destroy(); + }; } /// Remove the output from self.outputs and evacuate views if it is a member of diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig index 36a815d..63ce09a 100644 --- a/river/SceneNodeData.zig +++ b/river/SceneNodeData.zig @@ -22,10 +22,12 @@ const wl = @import("wayland").server.wl; const util = @import("util.zig"); +const LockSurface = @import("LockSurface.zig"); const View = @import("View.zig"); const Data = union(enum) { view: *View, + lock_surface: *LockSurface, }; node: *wlr.SceneNode, diff --git a/river/Seat.zig b/river/Seat.zig index 6f206f6..a0ac936 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -241,14 +241,14 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { // Set the new focus switch (new_focus) { .view => |target_view| { - assert(server.lock_manager.state == .unlocked); + assert(server.lock_manager.state != .locked); assert(self.focused_output == target_view.output); if (target_view.pending.focus == 0) target_view.setActivated(true); target_view.pending.focus += 1; target_view.pending.urgent = false; }, .layer => |target_layer| { - assert(server.lock_manager.state == .unlocked); + assert(server.lock_manager.state != .locked); assert(self.focused_output == target_layer.output); }, .lock_surface => assert(server.lock_manager.state != .unlocked), diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index a9509ac..564189a 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -63,7 +63,7 @@ pub fn create(output: *Output, xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory errdefer util.gpa.destroy(node); const view = &node.view; - const tree = try output.tree.createSceneXdgSurface(xdg_toplevel.base); + const tree = try output.normal_content.createSceneXdgSurface(xdg_toplevel.base); errdefer tree.node.destroy(); try view.init(output, tree, .{ .xdg_toplevel = .{ diff --git a/river/render.zig b/river/render.zig index b343aeb..86d14cd 100644 --- a/river/render.zig +++ b/river/render.zig @@ -15,6 +15,7 @@ // along with this program. If not, see . const std = @import("std"); +const assert = std.debug.assert; const os = std.os; const server = &@import("main.zig").server; @@ -25,7 +26,26 @@ const log = std.log.scoped(.render); pub fn renderOutput(output: *Output) void { const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?; - if (!scene_output.commit()) { + + if (scene_output.commit()) { + 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_blank) + { + assert(!output.normal_content.node.enabled); + assert(output.locked_content.node.enabled); + + switch (server.lock_manager.state) { + .unlocked => unreachable, + .locked => switch (output.lock_render_state) { + .unlocked, .pending_blank, .pending_lock_surface => unreachable, + .blanked, .lock_surface => {}, + }, + .waiting_for_blank => output.lock_render_state = .pending_blank, + .waiting_for_lock_surfaces => output.lock_render_state = .pending_lock_surface, + } + } + } else { log.err("output commit failed for {s}", .{output.wlr_output.name}); }