From fa08d85c58af4fbd3c8f07b2fd35dcf45831f351 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Thu, 20 Aug 2020 14:35:19 +0200 Subject: [PATCH] view: implement frame-perfect destroy river's View objects may now outlive their wlroots counterparts so that we can continue to render a destroyed view until the transaction is completed. --- river/Output.zig | 4 ++-- river/Root.zig | 17 +++++++++++++++-- river/View.zig | 33 ++++++++++++++++++--------------- river/VoidView.zig | 4 ++++ river/XdgToplevel.zig | 19 +++++++++++-------- river/XwaylandView.zig | 18 +++++++++++------- 6 files changed, 61 insertions(+), 34 deletions(-) diff --git a/river/Output.zig b/river/Output.zig index 777d831..a05518b 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -288,7 +288,7 @@ fn layoutExternal(self: *Self, visible_count: u32) !void { var view_it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); while (view_it.next()) |node| { const view = &node.view; - if (!view.pending.float and !view.pending.fullscreen) { + if (!view.pending.float and !view.pending.fullscreen and !view.destroying) { view.pending.box = view_boxen.items[i]; view.applyConstraints(); i += 1; @@ -309,7 +309,7 @@ pub fn arrangeViews(self: *Self) void { var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags); while (it.next()) |node| { const view = &node.view; - if (!view.pending.float and !view.pending.fullscreen) layout_count += 1; + if (!view.pending.float and !view.pending.fullscreen and !view.destroying) layout_count += 1; } // If the usable area has a zero dimension, trying to arrange the layout diff --git a/river/Root.zig b/river/Root.zig index 03ab9b3..6e7e99e 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -133,6 +133,11 @@ pub fn startTransaction(self: *Self) void { while (view_it.next()) |view_node| { const view = &view_node.view; + if (view.destroying) { + if (view.saved_buffers.items.len == 0) view.saveBuffers(); + continue; + } + if (view.shouldTrackConfigure()) { // Clear the serial in case this transaction is interrupting a prior one. view.pending_serial = null; @@ -226,9 +231,17 @@ fn commitTransaction(self: *Self) void { var view_tags_changed = false; - var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32)); - while (view_it.next()) |view_node| { + var view_it = output.views.first; + while (view_it) |view_node| { const view = &view_node.view; + + if (view.destroying) { + view_it = view_node.next; + view.destroy(); + continue; + } + defer view_it = view_node.next; + if (!view.shouldTrackConfigure() and view.pending_serial != null) continue; // Apply pending state of the view diff --git a/river/View.zig b/river/View.zig index 2686b2e..1d1208c 100644 --- a/river/View.zig +++ b/river/View.zig @@ -79,9 +79,14 @@ impl: Impl, /// The output this view is currently associated with output: *Output, -/// This is non-null exactly when the view is mapped +/// This is from the point where the view is mapped until the surface +/// is destroyed by wlroots. wlr_surface: ?*c.wlr_surface, +/// This View struct outlasts the wlroots object it wraps. This bool is set to +/// true when the backing wlr_xdg_toplevel or equivalent has been destroyed. +destroying: bool, + /// The double-buffered state of the view current: State, pending: State, @@ -110,6 +115,7 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void { self.output = output; self.wlr_surface = null; + self.destroying = false; self.current = .{ .box = .{ @@ -140,9 +146,17 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void { } else unreachable; } -pub fn deinit(self: Self) void { +/// Deinit the view, remove it from the view stack and free the memory. +pub fn destroy(self: *Self) void { for (self.saved_buffers.items) |buffer| c.wlr_buffer_unlock(&buffer.wlr_client_buffer.*.base); self.saved_buffers.deinit(); + switch (self.impl) { + .xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(), + .xwayland_view => |*xwayland_view| xwayland_view.deinit(), + } + const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); + self.output.views.remove(node); + util.gpa.destroy(node); } /// Handle changes to pending state and start a transaction to apply them @@ -362,6 +376,8 @@ pub fn unmap(self: *Self) void { log.debug(.server, "view '{}' unmapped", .{self.getTitle()}); + self.destroying = true; + // Inform all seats that the view has been unmapped so they can handle focus var it = root.server.input_manager.seats.first; while (it) |node| : (it = node.next) { @@ -369,12 +385,6 @@ pub fn unmap(self: *Self) void { seat.handleViewUnmap(self); } - self.wlr_surface = null; - - // Remove the view from its output's stack - const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); - self.output.views.remove(node); - self.output.sendViewTags(); // Still need to arrange if fullscreened from the layout @@ -382,10 +392,3 @@ pub fn unmap(self: *Self) void { root.startTransaction(); } - -/// Destory the view and free the ViewStack node holding it. -pub fn destroy(self: *const Self) void { - self.deinit(); - const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); - util.gpa.destroy(node); -} diff --git a/river/VoidView.zig b/river/VoidView.zig index f1f1c7a..5b1175c 100644 --- a/river/VoidView.zig +++ b/river/VoidView.zig @@ -24,6 +24,10 @@ const c = @import("c.zig"); const Box = @import("Box.zig"); const View = @import("View.zig"); +pub fn deinit(self: *Self) void { + unreachable; +} + pub fn needsConfigure(self: Self) bool { unreachable; } diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index 8d4c7b3..69f9ff7 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -60,6 +60,15 @@ pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void c.wl_signal_add(&self.wlr_xdg_surface.events.unmap, &self.listen_unmap); } +pub fn deinit(self: *Self) void { + if (self.view.wlr_surface != null) { + // Remove listeners that are active for the entire lifetime of the view + c.wl_list_remove(&self.listen_destroy.link); + c.wl_list_remove(&self.listen_map.link); + c.wl_list_remove(&self.listen_unmap.link); + } +} + /// Returns true if a configure must be sent to ensure the dimensions of the /// pending_box are applied. pub fn needsConfigure(self: Self) bool { @@ -139,14 +148,8 @@ pub fn getConstraints(self: Self) View.Constraints { /// Called when the xdg surface is destroyed fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - const output = self.view.output; - - // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); - - self.view.destroy(); + self.deinit(); + self.view.wlr_surface = null; } /// Called when the xdg surface is mapped, or ready to display on-screen. diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index d8292a1..0be1c21 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -56,6 +56,15 @@ pub fn init(self: *Self, view: *View, wlr_xwayland_surface: *c.wlr_xwayland_surf c.wl_signal_add(&self.wlr_xwayland_surface.events.unmap, &self.listen_unmap); } +pub fn deinit(self: *Self) void { + if (self.view.wlr_surface != null) { + // Remove listeners that are active for the entire lifetime of the view + c.wl_list_remove(&self.listen_destroy.link); + c.wl_list_remove(&self.listen_map.link); + c.wl_list_remove(&self.listen_unmap.link); + } +} + pub fn needsConfigure(self: Self) bool { return self.wlr_xwayland_surface.x != self.view.pending.box.x or self.wlr_xwayland_surface.y != self.view.pending.box.y or @@ -132,13 +141,8 @@ pub fn getConstraints(self: Self) View.Constraints { /// Called when the xwayland surface is destroyed fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - - // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); - - self.view.destroy(); + self.deinit(); + self.view.wlr_surface = null; } /// Called when the xwayland surface is mapped, or ready to display on-screen.