From 7ee6c79b6b5dd926497959cf80d1283749e30b5e Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 1 Dec 2023 14:57:18 +0100 Subject: build: update to wlroots 0.17 --- .builds/alpine.yml | 4 +- .builds/archlinux.yml | 4 +- .builds/freebsd.yml | 5 +- README.md | 2 +- deps/zig-wlroots | 2 +- river/Cursor.zig | 71 +++++++--------------------- river/DragIcon.zig | 50 +++----------------- river/IdleInhibitorManager.zig | 2 +- river/Keyboard.zig | 12 ++--- river/LayerSurface.zig | 27 ++++++----- river/LockManager.zig | 19 +++++--- river/LockSurface.zig | 13 ++---- river/Output.zig | 96 ++++++++++++++++++++------------------ river/Root.zig | 24 ++++++++-- river/SceneNodeData.zig | 6 +-- river/Seat.zig | 2 +- river/Server.zig | 20 ++------ river/XdgDecoration.zig | 16 ++----- river/XdgToplevel.zig | 27 ++--------- river/XwaylandOverrideRedirect.zig | 54 ++++++++++++++++----- river/XwaylandView.zig | 56 ++++++++++++++++------ 21 files changed, 238 insertions(+), 274 deletions(-) diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 2cd5417..4b8dcf0 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -27,13 +27,13 @@ sources: tasks: - install_deps: | cd wayland - git checkout 1.21.0 + git checkout 1.22.0 meson setup build -Ddocumentation=false -Dtests=false --prefix /usr sudo ninja -C build install cd .. cd wlroots - git checkout 0.16.0 + git checkout 0.17.0 meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \ -Dwerror=false -Db_ndebug=false -Dxcb-errors=disabled --prefix /usr sudo ninja -C build/ install diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 8b7db18..9ea597a 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -25,13 +25,13 @@ sources: tasks: - install_deps: | cd wayland - git checkout 1.21.0 + git checkout 1.22.0 meson setup build -Ddocumentation=false -Dtests=false --prefix /usr sudo ninja -C build install cd .. cd wlroots - git checkout 0.16.0 + git checkout 0.17.0 meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \ -Dwerror=false -Db_ndebug=false --prefix /usr sudo ninja -C build/ install diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 5207f68..367f5c0 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -19,6 +19,7 @@ packages: - x11/xcb-util-wm - x11-servers/xwayland - sysutils/seatd + - sysutils/libdisplay-info - gmake - scdoc - wget @@ -29,13 +30,13 @@ sources: tasks: - install_deps: | cd wayland - git checkout 1.21.0 + git checkout 1.22.0 meson setup build -Ddocumentation=false -Dtests=false --prefix /usr sudo ninja -C build install cd .. cd wlroots - git checkout 0.16.0 + git checkout 0.17.0 meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \ -Dwerror=false -Db_ndebug=false --prefix /usr sudo ninja -C build/ install diff --git a/README.md b/README.md index 618cf7d..e36f8ad 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ distribution. - [zig](https://ziglang.org/download/) 0.11 - wayland - wayland-protocols -- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.16 +- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.17 - xkbcommon - libevdev - pixman diff --git a/deps/zig-wlroots b/deps/zig-wlroots index 0f07b2c..a4e1005 160000 --- a/deps/zig-wlroots +++ b/deps/zig-wlroots @@ -1 +1 @@ -Subproject commit 0f07b2c666125d06529dfc688da4e71bff9a04f9 +Subproject commit a4e100599b9f742215fa09afce8c56cffea2e796 diff --git a/river/Cursor.zig b/river/Cursor.zig index e4ab7b3..b288ee2 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -98,37 +98,6 @@ const Mode = union(enum) { }, }; -const Image = enum { - /// The current image of the cursor is unknown, perhaps because it was set by a client. - unknown, - left_ptr, - move, - @"n-resize", - @"s-resize", - @"w-resize", - @"e-resize", - @"nw-resize", - @"ne-resize", - @"sw-resize", - @"se-resize", - - fn resize(edges: wlr.Edges) Image { - assert(!(edges.top and edges.bottom)); - assert(!(edges.left and edges.right)); - - if (edges.top and edges.left) return .@"nw-resize"; - if (edges.top and edges.right) return .@"ne-resize"; - if (edges.bottom and edges.left) return .@"sw-resize"; - if (edges.bottom and edges.right) return .@"se-resize"; - if (edges.top) return .@"n-resize"; - if (edges.bottom) return .@"s-resize"; - if (edges.left) return .@"w-resize"; - if (edges.right) return .@"e-resize"; - - return .@"se-resize"; - } -}; - const default_size = 24; const LayoutPoint = struct { @@ -150,9 +119,12 @@ inflight_mode: Mode = .passthrough, seat: *Seat, wlr_cursor: *wlr.Cursor, pointer_gestures: *wlr.PointerGesturesV1, -xcursor_manager: *wlr.XcursorManager, -image: Image = .unknown, +/// Xcursor manager for the currently configured Xcursor theme. +xcursor_manager: *wlr.XcursorManager, +/// Name of the current Xcursor shape, or null if a client has configured a +/// surface to be used as the cursor shape instead. +xcursor_name: ?[*:0]const u8 = null, /// Number of distinct buttons currently pressed pressed_count: u32 = 0, @@ -301,26 +273,15 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void { @intCast(image.hotspot_y), ); } - - if (self.image != .unknown) { - self.xcursor_manager.setCursorImage(@tagName(self.image), self.wlr_cursor); - } } -} -/// It seems that setCursorImage is actually fairly expensive to call repeatedly -/// as it does no checks to see if the the given image is already set. Therefore, -/// do that check here. -fn setImage(self: *Self, image: Image) void { - assert(image != .unknown); - - if (image == self.image) return; - self.image = image; - self.xcursor_manager.setCursorImage(@tagName(image), self.wlr_cursor); + if (self.xcursor_name) |name| { + self.wlr_cursor.setXcursor(self.xcursor_manager, name); + } } fn clearFocus(self: *Self) void { - self.setImage(.left_ptr); + self.wlr_cursor.setXcursor(self.xcursor_manager, "left_ptr"); self.seat.wlr_seat.pointerNotifyClearFocus(); } @@ -685,15 +646,15 @@ fn handleRequestSetCursor( // cursor moves between outputs. log.debug("focused client set cursor", .{}); self.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y); - self.image = .unknown; + self.xcursor_name = null; } } pub fn hide(self: *Self) void { if (self.pressed_count > 0) return; self.hidden = true; - self.wlr_cursor.setImage(null, 0, 0, 0, 0, 0, 0); - self.image = .unknown; + self.wlr_cursor.unsetImage(); + self.xcursor_name = null; self.seat.wlr_seat.pointerNotifyClearFocus(); self.hide_cursor_timer.timerUpdate(0) catch { log.err("failed to update cursor hide timeout", .{}); @@ -725,7 +686,7 @@ pub fn startMove(cursor: *Self, view: *View) void { .offset_x = @as(i32, @intFromFloat(cursor.wlr_cursor.x)) - view.current.box.x, .offset_y = @as(i32, @intFromFloat(cursor.wlr_cursor.y)) - view.current.box.y, } }; - cursor.enterMode(new_mode, view, .move); + cursor.enterMode(new_mode, view, "move"); } pub fn startResize(cursor: *Self, view: *View, proposed_edges: ?wlr.Edges) void { @@ -758,7 +719,7 @@ pub fn startResize(cursor: *Self, view: *View, proposed_edges: ?wlr.Edges) void .initial_width = @intCast(box.width), .initial_height = @intCast(box.height), } }; - cursor.enterMode(new_mode, view, Image.resize(edges)); + cursor.enterMode(new_mode, view, wlr.Xcursor.getResizeName(edges)); } fn computeEdges(cursor: *const Self, view: *const View) wlr.Edges { @@ -798,7 +759,7 @@ fn computeEdges(cursor: *const Self, view: *const View) wlr.Edges { } } -fn enterMode(cursor: *Self, mode: Mode, view: *View, image: Image) void { +fn enterMode(cursor: *Self, mode: Mode, view: *View, xcursor_name: [*:0]const u8) void { assert(cursor.mode == .passthrough or cursor.mode == .down); assert(mode == .move or mode == .resize); @@ -814,7 +775,7 @@ fn enterMode(cursor: *Self, mode: Mode, view: *View, image: Image) void { } cursor.seat.wlr_seat.pointerNotifyClearFocus(); - cursor.setImage(image); + cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, xcursor_name); server.root.applyPending(); } diff --git a/river/DragIcon.zig b/river/DragIcon.zig index a7f15ba..2505e4b 100644 --- a/river/DragIcon.zig +++ b/river/DragIcon.zig @@ -27,43 +27,33 @@ const Cursor = @import("Cursor.zig"); const SceneNodeData = @import("SceneNodeData.zig"); wlr_drag_icon: *wlr.Drag.Icon, - -tree: *wlr.SceneTree, -surface: *wlr.SceneTree, +scene_drag_icon: *wlr.SceneTree, destroy: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleDestroy), -map: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleMap), -unmap: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleUnmap), -commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), pub fn create(wlr_drag_icon: *wlr.Drag.Icon, cursor: *Cursor) error{OutOfMemory}!void { - const tree = try server.root.drag_icons.createSceneTree(); - errdefer tree.node.destroy(); + const scene_drag_icon = try server.root.drag_icons.createSceneDragIcon(wlr_drag_icon); + errdefer scene_drag_icon.node.destroy(); const drag_icon = try util.gpa.create(DragIcon); errdefer util.gpa.destroy(drag_icon); drag_icon.* = .{ .wlr_drag_icon = wlr_drag_icon, - .tree = tree, - .surface = try tree.createSceneSubsurfaceTree(wlr_drag_icon.surface), + .scene_drag_icon = scene_drag_icon, }; - tree.node.data = @intFromPtr(drag_icon); + scene_drag_icon.node.data = @intFromPtr(drag_icon); drag_icon.updatePosition(cursor); - tree.node.setEnabled(wlr_drag_icon.mapped); wlr_drag_icon.events.destroy.add(&drag_icon.destroy); - wlr_drag_icon.events.map.add(&drag_icon.map); - wlr_drag_icon.events.unmap.add(&drag_icon.unmap); - wlr_drag_icon.surface.events.commit.add(&drag_icon.commit); } pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void { switch (drag_icon.wlr_drag_icon.drag.grab_type) { .keyboard => unreachable, .keyboard_pointer => { - drag_icon.tree.node.setPosition( + drag_icon.scene_drag_icon.node.setPosition( @intFromFloat(cursor.wlr_cursor.x), @intFromFloat(cursor.wlr_cursor.y), ); @@ -71,7 +61,7 @@ pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void { .keyboard_touch => { const touch_id = drag_icon.wlr_drag_icon.drag.touch_id; if (cursor.touch_points.get(touch_id)) |point| { - drag_icon.tree.node.setPosition( + drag_icon.scene_drag_icon.node.setPosition( @intFromFloat(point.lx), @intFromFloat(point.ly), ); @@ -83,33 +73,7 @@ pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void { fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void { const drag_icon = @fieldParentPtr(DragIcon, "destroy", listener); - drag_icon.tree.node.destroy(); - drag_icon.destroy.link.remove(); - drag_icon.map.link.remove(); - drag_icon.unmap.link.remove(); - drag_icon.commit.link.remove(); util.gpa.destroy(drag_icon); } - -fn handleMap(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void { - const drag_icon = @fieldParentPtr(DragIcon, "map", listener); - - drag_icon.tree.node.setEnabled(true); -} - -fn handleUnmap(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void { - const drag_icon = @fieldParentPtr(DragIcon, "unmap", listener); - - drag_icon.tree.node.setEnabled(false); -} - -fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { - const drag_icon = @fieldParentPtr(DragIcon, "commit", listener); - - drag_icon.surface.node.setPosition( - drag_icon.surface.node.x + surface.current.dx, - drag_icon.surface.node.y + surface.current.dy, - ); -} diff --git a/river/IdleInhibitorManager.zig b/river/IdleInhibitorManager.zig index 945f3f8..b64f9aa 100644 --- a/river/IdleInhibitorManager.zig +++ b/river/IdleInhibitorManager.zig @@ -44,7 +44,7 @@ pub fn idleInhibitCheckActive(self: *Self) void { } }, .layer_surface => |layer_surface| { - if (layer_surface.wlr_layer_surface.mapped) { + if (layer_surface.wlr_layer_surface.surface.mapped) { inhibited = true; break; } diff --git a/river/Keyboard.zig b/river/Keyboard.zig index b98d9ec..568117d 100644 --- a/river/Keyboard.zig +++ b/river/Keyboard.zig @@ -147,13 +147,11 @@ fn handleBuiltinMapping(keysym: xkb.Keysym) bool { switch (@intFromEnum(keysym)) { xkb.Keysym.XF86Switch_VT_1...xkb.Keysym.XF86Switch_VT_12 => { log.debug("switch VT keysym received", .{}); - if (server.backend.isMulti()) { - if (server.backend.getSession()) |session| { - const vt = @intFromEnum(keysym) - xkb.Keysym.XF86Switch_VT_1 + 1; - const log_server = std.log.scoped(.server); - log_server.info("switching to VT {}", .{vt}); - session.changeVt(vt) catch log_server.err("changing VT failed", .{}); - } + if (server.session) |session| { + const vt = @intFromEnum(keysym) - xkb.Keysym.XF86Switch_VT_1 + 1; + const log_server = std.log.scoped(.server); + log_server.info("switching to VT {}", .{vt}); + session.changeVt(vt) catch log_server.err("changing VT failed", .{}); } return true; }, diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig index 5fea847..3ab20dc 100644 --- a/river/LayerSurface.zig +++ b/river/LayerSurface.zig @@ -37,8 +37,8 @@ scene_layer_surface: *wlr.SceneLayerSurfaceV1, popup_tree: *wlr.SceneTree, destroy: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleDestroy), -map: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleMap), -unmap: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleUnmap), +map: wl.Listener(void) = wl.Listener(void).init(handleMap), +unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap), commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), @@ -63,8 +63,8 @@ pub fn create(wlr_layer_surface: *wlr.LayerSurfaceV1) error{OutOfMemory}!void { wlr_layer_surface.surface.data = @intFromPtr(&layer_surface.scene_layer_surface.tree.node); wlr_layer_surface.events.destroy.add(&layer_surface.destroy); - wlr_layer_surface.events.map.add(&layer_surface.map); - wlr_layer_surface.events.unmap.add(&layer_surface.unmap); + wlr_layer_surface.surface.events.map.add(&layer_surface.map); + wlr_layer_surface.surface.events.unmap.add(&layer_surface.unmap); wlr_layer_surface.surface.events.commit.add(&layer_surface.commit); wlr_layer_surface.events.new_popup.add(&layer_surface.new_popup); @@ -96,20 +96,20 @@ fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), _: *wlr.LayerSurfa util.gpa.destroy(layer_surface); } -fn handleMap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void { +fn handleMap(listener: *wl.Listener(void)) void { const layer_surface = @fieldParentPtr(LayerSurface, "map", listener); - log.debug("layer surface '{s}' mapped", .{wlr_layer_surface.namespace}); + log.debug("layer surface '{s}' mapped", .{layer_surface.wlr_layer_surface.namespace}); layer_surface.output.arrangeLayers(); handleKeyboardInteractiveExclusive(layer_surface.output); server.root.applyPending(); } -fn handleUnmap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void { +fn handleUnmap(listener: *wl.Listener(void)) void { const layer_surface = @fieldParentPtr(LayerSurface, "unmap", listener); - log.debug("layer surface '{s}' unmapped", .{wlr_layer_surface.namespace}); + log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace}); layer_surface.output.arrangeLayers(); handleKeyboardInteractiveExclusive(layer_surface.output); @@ -128,10 +128,9 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { layer_surface.scene_layer_surface.tree.node.reparent(tree); } - // If a surface is committed while it is not mapped, we must send a configure. - // TODO: this mapped check is not correct as it will be true in the commit - // that triggers the unmap as well. - if (!wlr_layer_surface.mapped or @as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0) { + if (wlr_layer_surface.initial_commit or + @as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0) + { layer_surface.output.arrangeLayers(); handleKeyboardInteractiveExclusive(layer_surface.output); server.root.applyPending(); @@ -153,7 +152,7 @@ fn handleKeyboardInteractiveExclusive(output: *Output) void { if (@as(?*SceneNodeData, @ptrFromInt(node.data))) |node_data| { const layer_surface = node_data.data.layer_surface; const wlr_layer_surface = layer_surface.wlr_layer_surface; - if (wlr_layer_surface.mapped and + if (wlr_layer_surface.surface.mapped and wlr_layer_surface.current.keyboard_interactive == .exclusive) { break :outer layer_surface; @@ -179,7 +178,7 @@ fn handleKeyboardInteractiveExclusive(output: *Output) void { const current_focus = seat.focused.layer.wlr_layer_surface; // If the seat is currently focusing an unmapped layer surface or one // without keyboard interactivity, stop focusing that layer surface. - if (!current_focus.mapped or current_focus.current.keyboard_interactive == .none) { + if (!current_focus.surface.mapped or current_focus.current.keyboard_interactive == .none) { seat.setFocusRaw(.{ .none = {} }); } } diff --git a/river/LockManager.zig b/river/LockManager.zig index a500260..ae5db2c 100644 --- a/river/LockManager.zig +++ b/river/LockManager.zig @@ -28,6 +28,7 @@ const server = &@import("main.zig").server; const util = @import("util.zig"); const LockSurface = @import("LockSurface.zig"); +const Output = @import("Output.zig"); const log = std.log.scoped(.session_lock); @@ -190,12 +191,6 @@ pub fn maybeLock(manager: *LockManager) void { fn handleUnlock(listener: *wl.Listener(void)) void { const manager = @fieldParentPtr(LockManager, "unlock", listener); - // TODO(wlroots): this will soon be handled by the wlroots session lock implementation - if (manager.state != .locked) { - manager.lock.?.resource.postError(.invalid_unlock, "the locked event was never sent"); - return; - } - manager.state = .unlocked; log.info("session unlocked", .{}); @@ -263,3 +258,15 @@ fn handleSurface( wlr_lock_surface.resource.postNoMemory(); }; } + +pub fn updateLockSurfaceSize(manager: *LockManager, output: *Output) void { + const lock = manager.lock orelse return; + + 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(); + } + } +} diff --git a/river/LockSurface.zig b/river/LockSurface.zig index 2de43ee..9c8ec94 100644 --- a/river/LockSurface.zig +++ b/river/LockSurface.zig @@ -33,7 +33,6 @@ lock: *wlr.SessionLockV1, idle_update_focus: ?*wl.EventSource = null, -output_mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleOutputMode), map: wl.Listener(void) = wl.Listener(void).init(handleMap), surface_destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy), @@ -55,11 +54,10 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc wlr_lock_surface.surface.data = @intFromPtr(&tree.node); - wlr_lock_surface.output.events.mode.add(&lock_surface.output_mode); - wlr_lock_surface.events.map.add(&lock_surface.map); + wlr_lock_surface.surface.events.map.add(&lock_surface.map); wlr_lock_surface.events.destroy.add(&lock_surface.surface_destroy); - handleOutputMode(&lock_surface.output_mode, wlr_lock_surface.output); + lock_surface.configure(); } pub fn destroy(lock_surface: *LockSurface) void { @@ -84,20 +82,17 @@ pub fn destroy(lock_surface: *LockSurface) void { event_source.remove(); } - lock_surface.output_mode.link.remove(); lock_surface.map.link.remove(); lock_surface.surface_destroy.link.remove(); util.gpa.destroy(lock_surface); } -fn getOutput(lock_surface: *LockSurface) *Output { +pub fn getOutput(lock_surface: *LockSurface) *Output { return @ptrFromInt(lock_surface.wlr_lock_surface.output.data); } -fn handleOutputMode(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { - const lock_surface = @fieldParentPtr(LockSurface, "output_mode", listener); - +pub fn configure(lock_surface: *LockSurface) void { var output_width: i32 = undefined; var output_height: i32 = undefined; lock_surface.getOutput().wlr_output.effectiveResolution(&output_width, &output_height); diff --git a/river/Output.zig b/river/Output.zig index 09c8d49..35b98ea 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -184,8 +184,7 @@ layout: ?*Layout = null, status: OutputStatus, destroy: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleDestroy), -enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable), -mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode), +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), present: wl.Listener(*wlr.Output.event.Present) = wl.Listener(*wlr.Output.event.Present).init(handlePresent), @@ -195,23 +194,19 @@ pub fn create(wlr_output: *wlr.Output) !void { if (!wlr_output.initRender(server.allocator, server.renderer)) return error.InitRenderFailed; + var state = wlr.Output.State.init(); + defer state.finish(); + + state.setEnabled(true); + if (wlr_output.preferredMode()) |preferred_mode| { - wlr_output.setMode(preferred_mode); - wlr_output.enable(true); - wlr_output.commit() catch { - var it = wlr_output.modes.iterator(.forward); - while (it.next()) |mode| { - if (mode == preferred_mode) continue; - wlr_output.setMode(mode); - wlr_output.commit() catch continue; - // This mode works, use it - break; - } - // 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. - }; + state.setMode(preferred_mode); } + // Ignore failure here and create the Output anyways. + // It will stay disabled unless the user configures a custom mode which may work. + _ = wlr_output.commitState(&state); + var width: c_int = undefined; var height: c_int = undefined; wlr_output.effectiveResolution(&width, &height); @@ -270,8 +265,7 @@ pub fn create(wlr_output: *wlr.Output) !void { output.layers.fullscreen.node.setEnabled(false); wlr_output.events.destroy.add(&output.destroy); - wlr_output.events.enable.add(&output.enable); - wlr_output.events.mode.add(&output.mode); + wlr_output.events.request_state.add(&output.request_state); wlr_output.events.frame.add(&output.frame); wlr_output.events.present.add(&output.present); @@ -289,7 +283,7 @@ pub fn create(wlr_output: *wlr.Output) !void { output.active_link.init(); server.root.all_outputs.append(output); - handleEnable(&output.enable, wlr_output); + output.handleEnable(); } pub fn layerSurfaceTree(self: Self, layer: zwlr.LayerShellV1.Layer) *wlr.SceneTree { @@ -370,9 +364,8 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { output.all_link.remove(); output.destroy.link.remove(); - output.enable.link.remove(); + output.request_state.link.remove(); output.frame.link.remove(); - output.mode.link.remove(); output.present.link.remove(); output.tree.node.destroy(); @@ -386,42 +379,53 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { server.root.applyPending(); } -fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { - const self = @fieldParentPtr(Self, "enable", listener); +fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), event: *wlr.Output.event.RequestState) void { + const output = @fieldParentPtr(Self, "request_state", listener); + + // TODO double buffer output state changes for frame perfection and cleaner code. + // Schedule a frame and commit in the frame handler. + if (!output.wlr_output.commitState(event.state)) { + log.err("failed to commit requested state", .{}); + return; + } + + if (event.state.committed.enabled) { + output.handleEnable(); + } + if (event.state.committed.mode) { + output.updateBackgroundRect(); + output.arrangeLayers(); + server.lock_manager.updateLockSurfaceSize(output); + server.root.applyPending(); + } +} + +fn handleEnable(output: *Self) void { // We can't assert the current state of normal_content/locked_content // here as this output may be newly created. - if (wlr_output.enabled) { + if (output.wlr_output.enabled) { switch (server.lock_manager.state) { .unlocked => { - assert(self.lock_render_state == .blanked); - self.normal_content.node.setEnabled(true); - self.locked_content.node.setEnabled(false); + 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(self.lock_render_state == .blanked); - self.normal_content.node.setEnabled(false); - self.locked_content.node.setEnabled(true); + assert(output.lock_render_state == .blanked); + output.normal_content.node.setEnabled(false); + output.locked_content.node.setEnabled(true); }, } + // Add the output to root.active_outputs and the output layout if it has not + // already been added. + server.root.activateOutput(output); } else { // Disabling and re-enabling an output always blanks it. - self.lock_render_state = .blanked; - self.normal_content.node.setEnabled(false); - self.locked_content.node.setEnabled(true); + output.lock_render_state = .blanked; + output.normal_content.node.setEnabled(false); + output.locked_content.node.setEnabled(true); } - - // Add the output to root.active_outputs and the output layout if it has not - // already been added. - if (wlr_output.enabled) server.root.activateOutput(self); -} - -fn handleMode(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { - const output = @fieldParentPtr(Self, "mode", listener); - - output.updateBackgroundRect(); - output.arrangeLayers(); - server.root.applyPending(); } pub fn updateBackgroundRect(output: *Self) void { @@ -439,7 +443,7 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { const output = @fieldParentPtr(Self, "frame", listener); const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?; - if (scene_output.commit()) { + if (scene_output.commit(null)) { 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) diff --git a/river/Root.zig b/river/Root.zig index cc3a630..d17f6ed 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -90,6 +90,7 @@ views: wl.list.Head(View, .link), new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput), output_layout: *wlr.OutputLayout, +scene_output_layout: *wlr.SceneOutputLayout, layout_change: wl.Listener(*wlr.OutputLayout) = wl.Listener(*wlr.OutputLayout).init(handleLayoutChange), output_manager: *wlr.OutputManagerV1, @@ -133,7 +134,7 @@ pub fn init(self: *Self) !void { const outputs = try interactive_content.createSceneTree(); const xwayland_override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree(); - try scene.attachOutputLayout(output_layout); + const scene_output_layout = try scene.attachOutputLayout(output_layout); _ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout); @@ -175,6 +176,7 @@ pub fn init(self: *Self) !void { }, .views = undefined, .output_layout = output_layout, + .scene_output_layout = scene_output_layout, .all_outputs = undefined, .active_outputs = undefined, .output_manager = try wlr.OutputManagerV1.create(server.wl_server), @@ -226,7 +228,7 @@ pub fn at(self: Self, lx: f64, ly: f64) ?AtResult { const surface: ?*wlr.Surface = blk: { if (node.type == .buffer) { const scene_buffer = wlr.SceneBuffer.fromNode(node); - if (wlr.SceneSurface.fromBuffer(scene_buffer)) |scene_surface| { + if (wlr.SceneSurface.tryFromBuffer(scene_buffer)) |scene_surface| { break :blk scene_surface.surface; } } @@ -354,9 +356,20 @@ pub fn activateOutput(root: *Self, output: *Output) void { // This arranges outputs from left-to-right in the order they appear. The // wlr-output-management protocol may be used to modify this arrangement. // This also creates a wl_output global which is advertised to clients. - root.output_layout.addAuto(output.wlr_output); + const layout_output = root.output_layout.addAuto(output.wlr_output) catch { + // This would currently be very awkward to handle well and this output + // handling code needs to be heavily refactored soon anyways for double + // buffered state application as part of the transaction system. + // In any case, wlroots 0.16 would have crashed here, the error is only + // possible to handle after updating to 0.17. + @panic("TODO handle allocation failure here"); + }; + const scene_output = root.scene.createSceneOutput(output.wlr_output) catch { + // See above + @panic("TODO handle allocation failure here"); + }; + root.scene_output_layout.addOutput(layout_output, scene_output); - const layout_output = root.output_layout.get(output.wlr_output).?; output.tree.node.setEnabled(true); output.tree.node.setPosition(layout_output.x, layout_output.y); @@ -767,7 +780,8 @@ fn processOutputConfig( if (wlr_output.commitState(&proposed_state)) { if (head.state.enabled) { // Just updates the output's position if it is already in the layout - self.output_layout.add(output.wlr_output, head.state.x, head.state.y); + // This can't fail if the output is already in the layout, which we know to be the case here. + _ = self.output_layout.add(output.wlr_output, head.state.x, head.state.y) catch unreachable; output.tree.node.setEnabled(true); output.tree.node.setPosition(head.state.x, head.state.y); // Even though we call this in the output's handler for the mode event diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig index 6533b01..536a714 100644 --- a/river/SceneNodeData.zig +++ b/river/SceneNodeData.zig @@ -51,9 +51,9 @@ pub fn attach(node: *wlr.SceneNode, data: Data) error{OutOfMemory}!void { } pub fn fromNode(node: *wlr.SceneNode) ?*SceneNodeData { - var it: ?*wlr.SceneNode = node; - while (it) |n| : (it = n.parent) { - if (@as(?*SceneNodeData, @ptrFromInt(n.data))) |scene_node_data| { + var it: ?*wlr.SceneTree = node.parent; + while (it) |tree| : (it = tree.node.parent) { + if (@as(?*SceneNodeData, @ptrFromInt(tree.node.data))) |scene_node_data| { return scene_node_data; } } diff --git a/river/Seat.zig b/river/Seat.zig index 4984363..3fe8693 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -152,7 +152,7 @@ pub fn focus(self: *Self, _target: ?*View) void { // While a layer surface is exclusively focused, views may not receive focus if (self.focused == .layer) { const wlr_layer_surface = self.focused.layer.wlr_layer_surface; - assert(wlr_layer_surface.mapped); + assert(wlr_layer_surface.surface.mapped); if (wlr_layer_surface.current.keyboard_interactive == .exclusive and (wlr_layer_surface.current.layer == .top or wlr_layer_surface.current.layer == .overlay)) { diff --git a/river/Server.zig b/river/Server.zig index a05a539..f17f2c1 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -48,6 +48,7 @@ sigint_source: *wl.EventSource, sigterm_source: *wl.EventSource, backend: *wlr.Backend, +session: ?*wlr.Session, renderer: *wlr.Renderer, allocator: *wlr.Allocator, @@ -89,7 +90,7 @@ pub fn init(self: *Self) !void { errdefer self.sigterm_source.remove(); // This frees itself when the wl.Server is destroyed - self.backend = try wlr.Backend.autocreate(self.wl_server); + self.backend = try wlr.Backend.autocreate(self.wl_server, &self.session); self.renderer = try wlr.Renderer.autocreate(self.backend); errdefer self.renderer.destroy(); @@ -98,7 +99,7 @@ pub fn init(self: *Self) !void { self.allocator = try wlr.Allocator.autocreate(self.backend, self.renderer); errdefer self.allocator.destroy(); - const compositor = try wlr.Compositor.create(self.wl_server, self.renderer); + const compositor = try wlr.Compositor.create(self.wl_server, 6, self.renderer); _ = try wlr.Subcompositor.create(self.wl_server); self.xdg_shell = try wlr.XdgShell.create(self.wl_server, 5); @@ -109,7 +110,7 @@ pub fn init(self: *Self) !void { self.new_toplevel_decoration.setNotify(handleNewToplevelDecoration); self.xdg_decoration_manager.events.new_toplevel_decoration.add(&self.new_toplevel_decoration); - self.layer_shell = try wlr.LayerShellV1.create(self.wl_server); + self.layer_shell = try wlr.LayerShellV1.create(self.wl_server, 4); self.new_layer_surface.setNotify(handleNewLayerSurface); self.layer_shell.events.new_surface.add(&self.new_layer_surface); @@ -203,7 +204,7 @@ fn handleNewXdgSurface(_: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSu log.debug("new xdg_toplevel", .{}); - XdgToplevel.create(xdg_surface.role_data.toplevel) catch { + XdgToplevel.create(xdg_surface.role_data.toplevel.?) catch { log.err("out of memory", .{}); xdg_surface.resource.postNoMemory(); return; @@ -214,17 +215,6 @@ fn handleNewToplevelDecoration( _: *wl.Listener(*wlr.XdgToplevelDecorationV1), wlr_decoration: *wlr.XdgToplevelDecorationV1, ) void { - const xdg_toplevel: *XdgToplevel = @ptrFromInt(wlr_decoration.surface.data); - - // TODO(wlroots): The next wlroots version will handle this for us - if (xdg_toplevel.decoration != null) { - wlr_decoration.resource.postError( - .already_constructed, - "xdg_toplevel already has a decoration object", - ); - return; - } - XdgDecoration.init(wlr_decoration); } diff --git a/river/XdgDecoration.zig b/river/XdgDecoration.zig index f43f5e9..91ab481 100644 --- a/river/XdgDecoration.zig +++ b/river/XdgDecoration.zig @@ -34,7 +34,7 @@ request_mode: wl.Listener(*wlr.XdgToplevelDecorationV1) = wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleRequestMode), pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void { - const xdg_toplevel: *XdgToplevel = @ptrFromInt(wlr_decoration.surface.data); + const xdg_toplevel: *XdgToplevel = @ptrFromInt(wlr_decoration.toplevel.base.data); xdg_toplevel.decoration = .{ .wlr_decoration = wlr_decoration }; const decoration = &xdg_toplevel.decoration.?; @@ -52,21 +52,15 @@ pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void { xdg_toplevel.view.pending.ssd = ssd; } -// TODO(wlroots): remove this function when updating to 0.17.0 -// https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4051 -pub fn deinit(decoration: *XdgDecoration) void { - decoration.destroy.link.remove(); - decoration.request_mode.link.remove(); -} - fn handleDestroy( listener: *wl.Listener(*wlr.XdgToplevelDecorationV1), _: *wlr.XdgToplevelDecorationV1, ) void { const decoration = @fieldParentPtr(XdgDecoration, "destroy", listener); - const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.surface.data); + const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data); - decoration.deinit(); + decoration.destroy.link.remove(); + decoration.request_mode.link.remove(); assert(xdg_toplevel.decoration != null); xdg_toplevel.decoration = null; @@ -78,7 +72,7 @@ fn handleRequestMode( ) void { const decoration = @fieldParentPtr(XdgDecoration, "request_mode", listener); - const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.surface.data); + const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data); const view = xdg_toplevel.view; const ssd = server.config.rules.ssd.match(xdg_toplevel.view) orelse diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index dd42a99..4d28ffe 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -90,8 +90,8 @@ pub fn create(xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void { // Add listeners that are active over the toplevel's entire lifetime xdg_toplevel.base.events.destroy.add(&self.destroy); - xdg_toplevel.base.events.map.add(&self.map); - xdg_toplevel.base.events.unmap.add(&self.unmap); + xdg_toplevel.base.surface.events.map.add(&self.map); + xdg_toplevel.base.surface.events.unmap.add(&self.unmap); xdg_toplevel.base.events.new_popup.add(&self.new_popup); _ = xdg_toplevel.setWmCapabilities(.{ .fullscreen = true }); @@ -183,16 +183,7 @@ pub fn destroyPopups(self: Self) void { fn handleDestroy(listener: *wl.Listener(void)) void { const self = @fieldParentPtr(Self, "destroy", listener); - // TODO(wlroots): Replace this with an assertion when updating to wlroots 0.17.0 - // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4051 - if (self.decoration) |*decoration| { - decoration.wlr_decoration.resource.postError( - .orphaned, - "xdg_toplevel destroyed before xdg_toplevel_decoration", - ); - decoration.deinit(); - self.decoration = null; - } + assert(self.decoration == null); // Remove listeners that are active for the entire lifetime of the view self.destroy.link.remove(); @@ -392,18 +383,6 @@ fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), ev const seat: *Seat = @ptrFromInt(event.seat.seat.data); const view = self.view; - { - // TODO(wlroots) remove this after updating to the next wlroots version - // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4041 - if ((event.edges.top and event.edges.bottom) or (event.edges.left and event.edges.right)) { - self.xdg_toplevel.resource.postError( - .invalid_resize_edge, - "provided value is not a valid variant of the resize_edge enum", - ); - return; - } - } - if (view.current.output == null or view.pending.output == null) return; if (view.current.tags & view.current.output.?.current.tags == 0) return; if (view.pending.fullscreen) return; diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig index 2fdc978..609161a 100644 --- a/river/XwaylandOverrideRedirect.zig +++ b/river/XwaylandOverrideRedirect.zig @@ -34,14 +34,21 @@ const log = std.log.scoped(.xwayland); xwayland_surface: *wlr.XwaylandSurface, surface_tree: ?*wlr.SceneTree = null, +// Active over entire 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(void) = wl.Listener(void).init(handleSetGeometry), set_override_redirect: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetOverrideRedirect), +associate: wl.Listener(void) = wl.Listener(void).init(handleAssociate), +dissociate: wl.Listener(void) = wl.Listener(void).init(handleDissociate), + +// Active while the xwayland_surface is associated with a wlr_surface +map: wl.Listener(void) = wl.Listener(void).init(handleMap), +unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap), + +// Active while mapped +set_geometry: wl.Listener(void) = wl.Listener(void).init(handleSetGeometry), pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void { const self = try util.gpa.create(Self); @@ -51,12 +58,16 @@ pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void { 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); - if (xwayland_surface.mapped) { - handleMap(&self.map, xwayland_surface); + xwayland_surface.events.associate.add(&self.associate); + xwayland_surface.events.dissociate.add(&self.dissociate); + + if (xwayland_surface.surface) |surface| { + handleAssociate(&self.associate); + if (surface.mapped) { + handleMap(&self.map); + } } } @@ -72,14 +83,28 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS self.request_configure.link.remove(); self.destroy.link.remove(); - self.map.link.remove(); - self.unmap.link.remove(); + self.associate.link.remove(); + self.dissociate.link.remove(); self.set_override_redirect.link.remove(); util.gpa.destroy(self); } -pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { +fn handleAssociate(listener: *wl.Listener(void)) void { + const self = @fieldParentPtr(Self, "associate", listener); + + self.xwayland_surface.surface.?.events.map.add(&self.map); + self.xwayland_surface.surface.?.events.unmap.add(&self.unmap); +} + +fn handleDissociate(listener: *wl.Listener(void)) void { + const self = @fieldParentPtr(Self, "dissociate", listener); + + self.map.link.remove(); + self.unmap.link.remove(); +} + +pub fn handleMap(listener: *wl.Listener(void)) void { const self = @fieldParentPtr(Self, "map", listener); self.mapImpl() catch { @@ -124,7 +149,7 @@ pub fn focusIfDesired(self: *Self) void { } } -fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { +fn handleUnmap(listener: *wl.Listener(void)) void { const self = @fieldParentPtr(Self, "unmap", listener); self.set_geometry.link.remove(); @@ -165,7 +190,12 @@ fn handleSetOverrideRedirect( assert(!xwayland_surface.override_redirect); - if (xwayland_surface.mapped) handleUnmap(&self.unmap, xwayland_surface); + if (xwayland_surface.surface) |surface| { + if (surface.mapped) { + handleUnmap(&self.unmap); + } + handleDissociate(&self.dissociate); + } handleDestroy(&self.destroy, xwayland_surface); XwaylandView.create(xwayland_surface) catch { diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 8fd3be1..95ca92b 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -39,16 +39,20 @@ xwayland_surface: *wlr.XwaylandSurface, /// Created on map and destroyed on unmap surface_tree: ?*wlr.SceneTree = null, -// Listeners that are always active over the view's lifetime +// Active over entire lifetime 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), request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) = wl.Listener(*wlr.XwaylandSurface.event.Configure).init(handleRequestConfigure), set_override_redirect: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetOverrideRedirect), +associate: wl.Listener(void) = wl.Listener(void).init(handleAssociate), +dissociate: wl.Listener(void) = wl.Listener(void).init(handleDissociate), -// Listeners that are only active while the view is mapped +// Active while the xwayland_surface is associated with a wlr_surface +map: wl.Listener(void) = wl.Listener(void).init(handleMap), +unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap), + +// Active while mapped set_title: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetTitle), set_class: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetClass), set_decorations: wl.Listener(*wlr.XwaylandSurface) = @@ -70,13 +74,16 @@ pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void { // Add listeners that are active over the view's entire lifetime xwayland_surface.events.destroy.add(&self.destroy); - xwayland_surface.events.map.add(&self.map); - xwayland_surface.events.unmap.add(&self.unmap); + xwayland_surface.events.associate.add(&self.associate); + xwayland_surface.events.dissociate.add(&self.dissociate); xwayland_surface.events.request_configure.add(&self.request_configure); xwayland_surface.events.set_override_redirect.add(&self.set_override_redirect); - if (xwayland_surface.mapped) { - handleMap(&self.map, xwayland_surface); + if (xwayland_surface.surface) |surface| { + handleAssociate(&self.associate); + if (surface.mapped) { + handleMap(&self.map); + } } } @@ -150,8 +157,8 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS // Remove listeners that are active for the entire lifetime of the view self.destroy.link.remove(); - self.map.link.remove(); - self.unmap.link.remove(); + self.associate.link.remove(); + self.dissociate.link.remove(); self.request_configure.link.remove(); self.set_override_redirect.link.remove(); @@ -160,10 +167,24 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS view.destroy(); } -pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { +fn handleAssociate(listener: *wl.Listener(void)) void { + const self = @fieldParentPtr(Self, "associate", listener); + + self.xwayland_surface.surface.?.events.map.add(&self.map); + self.xwayland_surface.surface.?.events.unmap.add(&self.unmap); +} + +fn handleDissociate(listener: *wl.Listener(void)) void { + const self = @fieldParentPtr(Self, "dissociate", listener); + self.map.link.remove(); + self.unmap.link.remove(); +} + +pub fn handleMap(listener: *wl.Listener(void)) void { const self = @fieldParentPtr(Self, "map", listener); const view = self.view; + const xwayland_surface = self.xwayland_surface; const surface = xwayland_surface.surface.?; surface.data = @intFromPtr(&view.tree.node); @@ -213,7 +234,7 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: }; } -fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void { +fn handleUnmap(listener: *wl.Listener(void)) void { const self = @fieldParentPtr(Self, "unmap", listener); self.xwayland_surface.surface.?.data = 0; @@ -239,7 +260,9 @@ fn handleRequestConfigure( const self = @fieldParentPtr(Self, "request_configure", listener); // If unmapped, let the client do whatever it wants - if (!self.xwayland_surface.mapped) { + if (self.xwayland_surface.surface == null or + !self.xwayland_surface.surface.?.mapped) + { self.xwayland_surface.configure(event.x, event.y, event.width, event.height); return; } @@ -262,7 +285,12 @@ fn handleSetOverrideRedirect( assert(xwayland_surface.override_redirect); - if (xwayland_surface.mapped) handleUnmap(&self.unmap, xwayland_surface); + if (xwayland_surface.surface) |surface| { + if (surface.mapped) { + handleUnmap(&self.unmap); + } + handleDissociate(&self.dissociate); + } handleDestroy(&self.destroy, xwayland_surface); XwaylandOverrideRedirect.create(xwayland_surface) catch { -- cgit v1.2.3