diff --git a/river/Cursor.zig b/river/Cursor.zig index bce0c71..2692fd8 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -630,7 +630,9 @@ fn xwaylandUnmanagedSurfaceAt(lx: f64, ly: f64) ?SurfaceAtResult { } fn surfaceAtFilter(view: *View, filter_tags: u32) bool { - return !view.destroying and view.current.tags & filter_tags != 0; + // TODO(wlroots): we can remove this view.surface != null check as surfaceAt + // will start filtering by mapped views by default in 0.15.0 + return view.surface != null and view.current.tags & filter_tags != 0; } pub fn enterMode(self: *Self, mode: std.meta.Tag((Mode)), view: *View) void { diff --git a/river/Output.zig b/river/Output.zig index 2a6a1b3..bf2b1fb 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -168,7 +168,7 @@ pub fn sendUrgentTags(self: Self) void { } pub fn arrangeFilter(view: *View, filter_tags: u32) bool { - return !view.destroying and !view.pending.float and !view.pending.fullscreen and + return view.surface != null and !view.pending.float and !view.pending.fullscreen and view.pending.tags & filter_tags != 0; } diff --git a/river/OutputStatus.zig b/river/OutputStatus.zig index 1f088f8..0120ebd 100644 --- a/river/OutputStatus.zig +++ b/river/OutputStatus.zig @@ -74,7 +74,7 @@ pub fn sendViewTags(self: Self) void { var it = self.output.views.first; while (it) |node| : (it = node.next) { - if (node.view.destroying) continue; + if (node.view.surface == null) continue; view_tags.append(node.view.current.tags) catch { self.output_status.postNoMemory(); log.crit("out of memory", .{}); diff --git a/river/Root.zig b/river/Root.zig index bc6dbef..e1a556d 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -291,7 +291,7 @@ pub fn startTransaction(self: *Self) void { while (view_it) |view_node| : (view_it = view_node.next) { const view = &view_node.view; - if (view.destroying) continue; + if (view.surface == null) continue; if (view.shouldTrackConfigure()) { // Clear the serial in case this transaction is interrupting a prior one. @@ -386,10 +386,13 @@ fn commitTransaction(self: *Self) void { const view = &view_node.view; view_it = view_node.next; - if (view.destroying) { - view.destroy(); + if (view.surface == null) { + view.dropSavedBuffers(); + view.output.views.remove(view_node); + if (view.destroying) view.destroy(); continue; } + assert(!view.destroying); if (view.pending_serial != null and !view.shouldTrackConfigure()) continue; diff --git a/river/Seat.zig b/river/Seat.zig index 5cee706..966263d 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -183,7 +183,7 @@ pub fn focus(self: *Self, _target: ?*View) void { } fn pendingFilter(view: *View, filter_tags: u32) bool { - return !view.destroying and view.pending.tags & filter_tags != 0; + return view.surface != null and view.pending.tags & filter_tags != 0; } /// Switch focus to the target, handling unfocus and input inhibition diff --git a/river/View.zig b/river/View.zig index 347488c..72b8e3b 100644 --- a/river/View.zig +++ b/river/View.zig @@ -88,12 +88,11 @@ impl: Impl = undefined, /// The output this view is currently associated with output: *Output, -/// This is non-null from the point where the view is mapped until the -/// surface is destroyed by wlroots. +/// This is non-null exactly when the view is mapped surface: ?*wlr.Surface = null, -/// This View struct outlasts the wlroots object it wraps. This bool is set to -/// true when the backing wlr.XdgToplevel or equivalent has been destroyed. +/// This indicates that the view should be destroyed when the current +/// transaction completes. See View.destroy() destroying: bool = false, /// The double-buffered state of the view @@ -153,28 +152,22 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void { } else unreachable; } -/// Deinit the view, remove it from the view stack and free the memory. +/// If saved buffers of the view are currently in use by a transaction, +/// mark this view for destruction when the transaction completes. Otherwise +/// destroy immediately. pub fn destroy(self: *Self) void { - self.dropSavedBuffers(); - self.saved_buffers.deinit(); + assert(self.surface == null); + self.destroying = true; - if (self.foreign_toplevel_handle) |handle| { - self.foreign_activate.link.remove(); - self.foreign_fullscreen.link.remove(); - self.foreign_close.link.remove(); - handle.destroy(); + // If there are still saved buffers, then this view needs to be kept + // around until the current transaction completes. This function will be + // called again in Root.commitTransaction() + if (self.saved_buffers.items.len == 0) { + self.saved_buffers.deinit(); + + const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); + util.gpa.destroy(node); } - - switch (self.impl) { - .xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(), - .xwayland_view => |*xwayland_view| xwayland_view.deinit(), - } - - self.request_activate.link.remove(); - - 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 @@ -457,7 +450,8 @@ pub fn shouldTrackConfigure(self: Self) bool { pub fn map(self: *Self) !void { log.debug("view '{s}' mapped", .{self.getTitle()}); - if (self.foreign_toplevel_handle == null) { + { + assert(self.foreign_toplevel_handle == null); const handle = try wlr.ForeignToplevelHandleV1.create(server.foreign_toplevel_manager); self.foreign_toplevel_handle = handle; @@ -494,15 +488,24 @@ pub fn map(self: *Self) !void { pub fn unmap(self: *Self) void { log.debug("view '{s}' unmapped", .{self.getTitle()}); - assert(!self.destroying); - self.destroying = true; - if (self.saved_buffers.items.len == 0) self.saveBuffers(); + assert(self.surface != null); + self.surface = null; + // Inform all seats that the view has been unmapped so they can handle focus var it = server.input_manager.seats.first; while (it) |seat_node| : (it = seat_node.next) seat_node.data.handleViewUnmap(self); + assert(self.foreign_toplevel_handle != null); + self.foreign_activate.link.remove(); + self.foreign_fullscreen.link.remove(); + self.foreign_close.link.remove(); + self.foreign_toplevel_handle.?.destroy(); + self.foreign_toplevel_handle = null; + + self.request_activate.link.remove(); + self.output.sendViewTags(); // Still need to arrange if fullscreened from the layout diff --git a/river/VoidView.zig b/river/VoidView.zig index e0d6edd..641382a 100644 --- a/river/VoidView.zig +++ b/river/VoidView.zig @@ -23,10 +23,6 @@ const wlr = @import("wlroots"); 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 adbc7c7..e2a04fb 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -77,20 +77,6 @@ pub fn init(self: *Self, view: *View, xdg_surface: *wlr.XdgSurface) void { Subsurface.handleExisting(xdg_surface.surface, .{ .xdg_toplevel = self }); } -pub fn deinit(self: *Self) void { - if (self.view.surface != null) { - // 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.new_popup.link.remove(); - self.new_subsurface.link.remove(); - - Subsurface.destroySubsurfaces(self.xdg_surface.surface); - XdgPopup.destroyPopups(self.xdg_surface); - } -} - /// Returns true if a configure must be sent to ensure that the pending /// dimensions are applied. pub fn needsConfigure(self: Self) bool { @@ -168,8 +154,18 @@ pub fn getConstraints(self: Self) View.Constraints { fn handleDestroy(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { const self = @fieldParentPtr(Self, "destroy", listener); - self.deinit(); - self.view.surface = null; + + // 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.new_popup.link.remove(); + self.new_subsurface.link.remove(); + + Subsurface.destroySubsurfaces(self.xdg_surface.surface); + XdgPopup.destroyPopups(self.xdg_surface); + + self.view.destroy(); } /// Called when the xdg surface is mapped, or ready to display on-screen. diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 2b428e1..984b0f5 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -58,16 +58,6 @@ pub fn init(self: *Self, view: *View, xwayland_surface: *wlr.XwaylandSurface) vo xwayland_surface.events.request_configure.add(&self.request_configure); } -pub fn deinit(self: *Self) void { - if (self.view.surface != null) { - // 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.request_configure.link.remove(); - } -} - pub fn needsConfigure(self: Self) bool { const output = self.view.output; const output_box = server.root.output_layout.getBox(output.wlr_output).?; @@ -153,8 +143,14 @@ pub fn getConstraints(self: Self) View.Constraints { /// Called when the xwayland surface is destroyed fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "destroy", listener); - self.deinit(); - self.view.surface = null; + + // 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.request_configure.link.remove(); + + self.view.destroy(); } /// Called when the xwayland surface is mapped, or ready to display on-screen. diff --git a/river/command/focus_view.zig b/river/command/focus_view.zig index 140bdda..654ac9c 100644 --- a/river/command/focus_view.zig +++ b/river/command/focus_view.zig @@ -72,5 +72,5 @@ pub fn focusView( } fn filter(view: *View, filter_tags: u32) bool { - return !view.destroying and view.pending.tags & filter_tags != 0; + return view.surface != null and view.pending.tags & filter_tags != 0; } diff --git a/river/command/swap.zig b/river/command/swap.zig index 120bdef..f0db519 100644 --- a/river/command/swap.zig +++ b/river/command/swap.zig @@ -80,6 +80,6 @@ pub fn swap( } fn filter(view: *View, filter_tags: u32) bool { - return !view.destroying and !view.pending.float and + return view.surface != null and !view.pending.float and !view.pending.fullscreen and view.pending.tags & filter_tags != 0; } diff --git a/river/command/zoom.zig b/river/command/zoom.zig index 6993820..9ca87d4 100644 --- a/river/command/zoom.zig +++ b/river/command/zoom.zig @@ -61,6 +61,6 @@ pub fn zoom( } fn filter(view: *View, filter_tags: u32) bool { - return !view.destroying and !view.pending.float and + return view.surface != null and !view.pending.float and !view.pending.fullscreen and view.pending.tags & filter_tags != 0; }