diff --git a/river/Cursor.zig b/river/Cursor.zig index ca8745e..b710903 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -941,7 +941,7 @@ fn warp(self: *Self) void { } fn updateDragIcons(self: *Self) void { - var it = server.root.layers.drag_icons.children.iterator(.forward); + var it = server.root.drag_icons.children.iterator(.forward); while (it.next()) |node| { const icon = @intToPtr(*DragIcon, node.data); diff --git a/river/DragIcon.zig b/river/DragIcon.zig index bba55f2..979eb2c 100644 --- a/river/DragIcon.zig +++ b/river/DragIcon.zig @@ -36,7 +36,7 @@ unmap: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleUnma commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), pub fn create(wlr_drag_icon: *wlr.Drag.Icon) error{OutOfMemory}!void { - const tree = try server.root.layers.drag_icons.createSceneTree(); + const tree = try server.root.drag_icons.createSceneTree(); errdefer tree.node.destroy(); const drag_icon = try util.gpa.create(DragIcon); diff --git a/river/Root.zig b/river/Root.zig index 01a2b10..1a57426 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -37,15 +37,20 @@ const ViewStack = @import("view_stack.zig").ViewStack; const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig"); scene: *wlr.Scene, -/// All direct children of the root scene node +/// 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. +drag_icons: *wlr.SceneTree, + +/// All direct children of the interactive_content scene node layers: struct { /// Parent tree for output trees which have their position updated when /// outputs are moved in the layout. outputs: *wlr.SceneTree, - /// Drag icons which have a position in layout coordinates that is updated - /// on cursor/touch point movement. - /// This tree is ignored by Root.at() - drag_icons: *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. + xwayland_override_redirect: if (build_options.xwayland) *wlr.SceneTree else void, }, new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput), @@ -73,14 +78,6 @@ outputs: std.TailQueue(Output) = .{}, /// It is not advertised to clients. noop_output: Output = undefined, -/// This list stores all "override redirect" Xwayland windows. This needs to be in root -/// since X is like the wild west and who knows where these things will place themselves. -xwayland_override_redirect_views: if (build_options.xwayland) - std.TailQueue(XwaylandOverrideRedirect) -else - void = if (build_options.xwayland) -.{}, - /// Number of layout demands pending before the transaction may be started. pending_layout_demands: u32 = 0, /// Number of pending configures sent in the current transaction. @@ -96,6 +93,12 @@ pub fn init(self: *Self) !void { const scene = try wlr.Scene.create(); errdefer scene.tree.node.destroy(); + const interactive_content = try scene.tree.createSceneTree(); + const drag_icons = try scene.tree.createSceneTree(); + + const outputs = try interactive_content.createSceneTree(); + const xwayland_override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree(); + try scene.attachOutputLayout(output_layout); _ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout); @@ -104,8 +107,6 @@ pub fn init(self: *Self) !void { const transaction_timer = try event_loop.addTimer(*Self, handleTransactionTimeout, self); errdefer transaction_timer.remove(); - const outputs = try scene.tree.createSceneTree(); - // TODO get rid of this hack somehow const noop_wlr_output = try server.headless_backend.headlessAddOutput(1920, 1080); const noop_tree = try outputs.createSceneTree(); @@ -113,9 +114,11 @@ pub fn init(self: *Self) !void { self.* = .{ .scene = scene, + .interactive_content = interactive_content, + .drag_icons = drag_icons, .layers = .{ .outputs = outputs, - .drag_icons = try scene.tree.createSceneTree(), + .xwayland_override_redirect = xwayland_override_redirect, }, .output_layout = output_layout, .output_manager = try wlr.OutputManagerV1.create(server.wl_server), @@ -170,12 +173,12 @@ pub const AtResult = struct { }, }; -/// Return information about what is currently rendered in the Root.layers.outputs -/// tree at the given layout coordinates. +/// Return information about what is currently rendered in the interactive_content +/// tree at the given layout coordinates, taking surface input regions into account. pub fn at(self: Self, lx: f64, ly: f64) ?AtResult { var sx: f64 = undefined; var sy: f64 = undefined; - const node_at = self.layers.outputs.node.at(lx, ly, &sx, &sy) orelse return null; + const node_at = self.interactive_content.node.at(lx, ly, &sx, &sy) orelse return null; const surface: ?*wlr.Surface = blk: { if (node_at.type == .buffer) { @@ -199,6 +202,9 @@ pub fn at(self: Self, lx: f64, ly: f64) ?AtResult { .view => |view| .{ .view = view }, .layer_surface => |layer_surface| .{ .layer_surface = layer_surface }, .lock_surface => |lock_surface| .{ .lock_surface = lock_surface }, + .xwayland_override_redirect => |xwayland_override_redirect| .{ + .xwayland_override_redirect = xwayland_override_redirect, + }, }, }; } diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig index b0fa8ba..d855ca5 100644 --- a/river/SceneNodeData.zig +++ b/river/SceneNodeData.zig @@ -25,11 +25,13 @@ const util = @import("util.zig"); const LayerSurface = @import("LayerSurface.zig"); const LockSurface = @import("LockSurface.zig"); const View = @import("View.zig"); +const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig"); const Data = union(enum) { view: *View, lock_surface: *LockSurface, layer_surface: *LayerSurface, + xwayland_override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn, }; node: *wlr.SceneNode, diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig index 5a7c420..5760879 100644 --- a/river/XwaylandOverrideRedirect.zig +++ b/river/XwaylandOverrideRedirect.zig @@ -25,41 +25,42 @@ const wl = @import("wayland").server.wl; const server = &@import("main.zig").server; const util = @import("util.zig"); +const SceneNodeData = @import("SceneNodeData.zig"); const View = @import("View.zig"); -const XwaylandView = @import("XwaylandView.zig"); const ViewStack = @import("view_stack.zig").ViewStack; +const XwaylandView = @import("XwaylandView.zig"); const log = std.log.scoped(.xwayland); -/// The corresponding wlroots object xwayland_surface: *wlr.XwaylandSurface, +surface_tree: ?*wlr.SceneTree = null, -// Listeners that are always active over the view's lifetime request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) = wl.Listener(*wlr.XwaylandSurface.event.Configure).init(handleRequestConfigure), destroy: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleDestroy), map: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleMap), unmap: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleUnmap), +set_geometry: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetGeometry), set_override_redirect: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetOverrideRedirect), -/// The override redirect surface will add itself to the list in Root when it is mapped. -pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!*Self { - const node = try util.gpa.create(std.TailQueue(Self).Node); - const self = &node.data; +pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void { + const self = try util.gpa.create(Self); + errdefer util.gpa.destroy(self); self.* = .{ .xwayland_surface = xwayland_surface }; // This must be set to 0 for usage in View.fromWlrSurface() xwayland_surface.data = 0; - // Add listeners that are active over the the entire lifetime xwayland_surface.events.request_configure.add(&self.request_configure); xwayland_surface.events.destroy.add(&self.destroy); xwayland_surface.events.map.add(&self.map); xwayland_surface.events.unmap.add(&self.unmap); xwayland_surface.events.set_override_redirect.add(&self.set_override_redirect); - return self; + if (xwayland_surface.mapped) { + handleMap(&self.map, xwayland_surface); + } } fn handleRequestConfigure( @@ -69,28 +70,36 @@ fn handleRequestConfigure( event.surface.configure(event.x, event.y, event.width, event.height); } -/// Called when the xwayland surface is destroyed fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "destroy", listener); - // Remove listeners that are active for the entire lifetime self.request_configure.link.remove(); self.destroy.link.remove(); self.map.link.remove(); self.unmap.link.remove(); self.set_override_redirect.link.remove(); - // Deallocate the node - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - util.gpa.destroy(node); + util.gpa.destroy(self); } -/// Called when the xwayland surface is mapped, or ready to display on-screen. pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "map", listener); - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - server.root.xwayland_override_redirect_views.prepend(node); + self.mapImpl() catch { + log.err("out of memory", .{}); + self.xwayland_surface.surface.?.resource.getClient().postNoMemory(); + }; +} + +fn mapImpl(self: *Self) error{OutOfMemory}!void { + self.surface_tree = try server.root.layers.xwayland_override_redirect.createSceneSubsurfaceTree( + self.xwayland_surface.surface.?, + ); + try SceneNodeData.attach(&self.surface_tree.?.node, .{ .xwayland_override_redirect = self }); + + self.surface_tree.?.node.setPosition(self.xwayland_surface.x, self.xwayland_surface.y); + + self.xwayland_surface.events.set_geometry.add(&self.set_geometry); self.focusIfDesired(); } @@ -117,12 +126,13 @@ pub fn focusIfDesired(self: *Self) void { } } -/// Called when the surface is unmapped and will no longer be displayed. fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "unmap", listener); - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - server.root.xwayland_override_redirect_views.remove(node); + self.set_geometry.link.remove(); + + self.surface_tree.?.node.destroy(); + self.surface_tree = null; // If the unmapped surface is currently focused, pass keyboard focus // to the most appropriate surface. @@ -144,6 +154,12 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSur server.root.startTransaction(); } +fn handleSetGeometry(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "set_geometry", listener); + + self.surface_tree.?.node.setPosition(self.xwayland_surface.x, self.xwayland_surface.y); +} + fn handleSetOverrideRedirect( listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface, @@ -158,12 +174,8 @@ fn handleSetOverrideRedirect( handleDestroy(&self.destroy, xwayland_surface); const output = server.input_manager.defaultSeat().focused_output; - const xwayland_view = XwaylandView.create(output, xwayland_surface) catch { + XwaylandView.create(output, xwayland_surface) catch { log.err("out of memory", .{}); return; }; - - if (xwayland_surface.mapped) { - XwaylandView.handleMap(&xwayland_view.map, xwayland_surface); - } } diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 7ae257c..f5de473 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -36,8 +36,9 @@ const log = std.log.scoped(.xwayland); /// The view this xwayland view implements view: *View, -/// The corresponding wlroots object xwayland_surface: *wlr.XwaylandSurface, +/// Created on map and destroyed on unmap +surface_tree: ?*wlr.SceneTree = null, /// The wlroots Xwayland implementation overwrites xwayland_surface.fullscreen /// immediately when the client requests it, so we track this state here to be @@ -62,8 +63,7 @@ request_fullscreen: wl.Listener(*wlr.XwaylandSurface) = request_minimize: wl.Listener(*wlr.XwaylandSurface.event.Minimize) = wl.Listener(*wlr.XwaylandSurface.event.Minimize).init(handleRequestMinimize), -/// The View will add itself to the output's view stack on map -pub fn create(output: *Output, xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!*Self { +pub fn create(output: *Output, xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void { const node = try util.gpa.create(ViewStack(View).Node); const view = &node.view; @@ -84,7 +84,9 @@ pub fn create(output: *Output, xwayland_surface: *wlr.XwaylandSurface) error{Out xwayland_surface.events.request_configure.add(&self.request_configure); xwayland_surface.events.set_override_redirect.add(&self.set_override_redirect); - return self; + if (xwayland_surface.mapped) { + handleMap(&self.map, xwayland_surface); + } } pub fn needsConfigure(self: Self) bool { @@ -181,7 +183,6 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS self.view.destroy(); } -/// Called when the xwayland surface is mapped, or ready to display on-screen. pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "map", listener); const view = self.view; @@ -194,12 +195,13 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: xwayland_surface.events.request_fullscreen.add(&self.request_fullscreen); xwayland_surface.events.request_minimize.add(&self.request_minimize); - const surface_tree = view.tree.createSceneSubsurfaceTree(surface) catch { + self.surface_tree = view.tree.createSceneSubsurfaceTree(surface) catch { log.err("out of memory", .{}); surface.resource.getClient().postNoMemory(); return; }; - surface_tree.node.lowerToBottom(); + // Place the node below the view's border nodes + self.surface_tree.?.node.lowerToBottom(); // Use the view's "natural" size centered on the output as the default // floating dimensions @@ -231,10 +233,12 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: }; } -/// Called when the surface is unmapped and will no longer be displayed. fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "unmap", listener); + self.surface_tree.?.node.destroy(); + self.surface_tree = null; + // Remove listeners that are only active while mapped self.commit.link.remove(); self.set_title.link.remove(); @@ -278,14 +282,10 @@ fn handleSetOverrideRedirect( if (xwayland_surface.mapped) handleUnmap(&self.unmap, xwayland_surface); handleDestroy(&self.destroy, xwayland_surface); - const override_redirect = XwaylandOverrideRedirect.create(xwayland_surface) catch { + XwaylandOverrideRedirect.create(xwayland_surface) catch { log.err("out of memory", .{}); return; }; - - if (xwayland_surface.mapped) { - XwaylandOverrideRedirect.handleMap(&override_redirect.map, xwayland_surface); - } } fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {