From 13f01bcb4b46e14b96e724866fd8d4919737d173 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sun, 23 May 2021 17:10:26 +0200 Subject: [PATCH] render: do basic yes/no damage tracking --- deps/zig-pixman | 2 +- river/LayerSurface.zig | 31 ++++++----- river/Output.zig | 9 ++-- river/Root.zig | 2 + river/Subsurface.zig | 103 ++++++++++++++++++++++++++++++++++++ river/View.zig | 7 +-- river/XdgPopup.zig | 83 +++++++++++++++++++++-------- river/XdgToplevel.zig | 23 ++++---- river/XwaylandUnmanaged.zig | 10 ++++ river/XwaylandView.zig | 3 ++ river/render.zig | 14 ++++- 11 files changed, 233 insertions(+), 54 deletions(-) create mode 100644 river/Subsurface.zig diff --git a/deps/zig-pixman b/deps/zig-pixman index 9acac69..135f223 160000 --- a/deps/zig-pixman +++ b/deps/zig-pixman @@ -1 +1 @@ -Subproject commit 9acac698e073ff54b09a62fecb144de326f67626 +Subproject commit 135f22345671e0ae2d1bc4b27cfdee9e97b97dfc diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig index ea73fc9..6c165dd 100644 --- a/river/LayerSurface.zig +++ b/river/LayerSurface.zig @@ -26,6 +26,7 @@ const util = @import("util.zig"); const Box = @import("Box.zig"); const Output = @import("Output.zig"); +const Subsurface = @import("Subsurface.zig"); const XdgPopup = @import("XdgPopup.zig"); const log = std.log.scoped(.layer_shell); @@ -40,10 +41,11 @@ state: wlr.LayerSurfaceV1.State, 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), +new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), +new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface), // Listeners only active while the layer surface is mapped commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), -new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), pub fn init(self: *Self, output: *Output, wlr_layer_surface: *wlr.LayerSurfaceV1) void { self.* = .{ @@ -62,9 +64,11 @@ pub fn init(self: *Self, output: *Output, wlr_layer_surface: *wlr.LayerSurfaceV1 list.remove(node); // Set up listeners that are active for the entire lifetime of the layer surface - self.wlr_layer_surface.events.destroy.add(&self.destroy); - self.wlr_layer_surface.events.map.add(&self.map); - self.wlr_layer_surface.events.unmap.add(&self.unmap); + wlr_layer_surface.events.destroy.add(&self.destroy); + wlr_layer_surface.events.map.add(&self.map); + wlr_layer_surface.events.unmap.add(&self.unmap); + wlr_layer_surface.events.new_popup.add(&self.new_popup); + wlr_layer_surface.surface.events.new_subsurface.add(&self.new_subsurface); } fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void { @@ -76,6 +80,8 @@ fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: self.destroy.link.remove(); self.map.link.remove(); self.unmap.link.remove(); + self.new_popup.link.remove(); + self.new_subsurface.link.remove(); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); util.gpa.destroy(node); @@ -88,7 +94,6 @@ fn handleMap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wl // Add listeners that are only active while mapped wlr_layer_surface.surface.events.commit.add(&self.commit); - wlr_layer_surface.events.new_popup.add(&self.new_popup); wlr_layer_surface.surface.sendEnter(wlr_layer_surface.output.?); @@ -103,7 +108,6 @@ fn handleUnmap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: * // remove listeners only active while the layer surface is mapped self.commit.link.remove(); - self.new_popup.link.remove(); // Remove from the output's list of layer surfaces const self_node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); @@ -154,15 +158,16 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), wlr_surface: *wlr.Surface) self.output.arrangeLayers(); server.root.startTransaction(); } + + self.output.damage.addWhole(); } fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void { const self = @fieldParentPtr(Self, "new_popup", listener); - - // This will free itself on destroy - const xdg_popup = util.gpa.create(XdgPopup) catch { - wlr_xdg_popup.resource.postNoMemory(); - return; - }; - xdg_popup.init(self.output, &self.box, wlr_xdg_popup); + XdgPopup.create(wlr_xdg_popup, .{ .layer_surface = self }); +} + +fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void { + const self = @fieldParentPtr(Self, "new_subsurface", listener); + Subsurface.create(new_wlr_subsurface, .{ .layer_surface = self }); } diff --git a/river/Output.zig b/river/Output.zig index f66ffee..0c8bc1d 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -55,6 +55,7 @@ const State = struct { }; wlr_output: *wlr.Output, +damage: *wlr.OutputDamage, /// All layer surfaces on the output, indexed by the layer enum. layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} ** 4, @@ -93,8 +94,8 @@ status_trackers: std.SinglyLinkedList(OutputStatus) = .{}, destroy: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleDestroy), enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable), -frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame), mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode), +frame: wl.Listener(*wlr.OutputDamage) = wl.Listener(*wlr.OutputDamage).init(handleFrame), pub fn init(self: *Self, wlr_output: *wlr.Output) !void { // Some backends don't have modes. DRM+KMS does, and we need to set a mode @@ -110,15 +111,17 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void { self.* = .{ .wlr_output = wlr_output, + .damage = try wlr.OutputDamage.create(wlr_output), .usable_box = undefined, }; wlr_output.data = @ptrToInt(self); wlr_output.events.destroy.add(&self.destroy); wlr_output.events.enable.add(&self.enable); - wlr_output.events.frame.add(&self.frame); wlr_output.events.mode.add(&self.mode); + self.damage.events.frame.add(&self.frame); + if (wlr_output.isNoop()) { // A noop output is always 0 x 0 self.usable_box = .{ @@ -453,7 +456,7 @@ fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) vo if (wlr_output.enabled) server.root.addOutput(self); } -fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { +fn handleFrame(listener: *wl.Listener(*wlr.OutputDamage), wlr_output: *wlr.OutputDamage) void { // This function is called every time an output is ready to display a frame, // generally at the output's refresh rate (e.g. 60Hz). const self = @fieldParentPtr(Self, "frame", listener); diff --git a/river/Root.zig b/river/Root.zig index 37072f7..d0cc268 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -404,6 +404,8 @@ fn commitTransaction(self: *Self) void { } if (view_tags_changed) output.sendViewTags(); + + output.damage.addWhole(); } } diff --git a/river/Subsurface.zig b/river/Subsurface.zig new file mode 100644 index 0000000..33dd279 --- /dev/null +++ b/river/Subsurface.zig @@ -0,0 +1,103 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2021 The River Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const Subsurface = @This(); + +const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; + +const util = @import("util.zig"); + +const LayerSurface = @import("LayerSurface.zig"); +const View = @import("View.zig"); + +pub const Parent = union(enum) { + view: *View, + layer_surface: *LayerSurface, + + pub fn damageWholeOutput(parent: Parent) void { + switch (parent) { + .view => |view| view.output.damage.addWhole(), + .layer_surface => |layer_surface| layer_surface.output.damage.addWhole(), + } + } +}; + +/// The parent at the root of this surface tree +parent: Parent, +wlr_subsurface: *wlr.Subsurface, + +// Always active +destroy: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleDestroy), +map: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleMap), +unmap: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleUnmap), +new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface), + +// Only active while mapped +commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), + +pub fn create(wlr_subsurface: *wlr.Subsurface, parent: Parent) void { + const subsurface = util.gpa.create(Subsurface) catch { + std.log.crit("out of memory", .{}); + wlr_subsurface.resource.getClient().postNoMemory(); + return; + }; + subsurface.* = .{ .wlr_subsurface = wlr_subsurface, .parent = parent }; + + wlr_subsurface.events.destroy.add(&subsurface.destroy); + wlr_subsurface.events.map.add(&subsurface.map); + wlr_subsurface.events.unmap.add(&subsurface.unmap); + wlr_subsurface.surface.events.new_subsurface.add(&subsurface.new_subsurface); +} + +fn handleDestroy(listener: *wl.Listener(*wlr.Subsurface), wlr_subsurface: *wlr.Subsurface) void { + const subsurface = @fieldParentPtr(Subsurface, "destroy", listener); + + subsurface.destroy.link.remove(); + subsurface.map.link.remove(); + subsurface.unmap.link.remove(); + subsurface.new_subsurface.link.remove(); + + util.gpa.destroy(subsurface); +} + +fn handleMap(listener: *wl.Listener(*wlr.Subsurface), wlr_subsurface: *wlr.Subsurface) void { + const subsurface = @fieldParentPtr(Subsurface, "map", listener); + + wlr_subsurface.surface.events.commit.add(&subsurface.commit); + subsurface.parent.damageWholeOutput(); +} + +fn handleUnmap(listener: *wl.Listener(*wlr.Subsurface), wlr_subsurface: *wlr.Subsurface) void { + const subsurface = @fieldParentPtr(Subsurface, "unmap", listener); + + subsurface.commit.link.remove(); + subsurface.parent.damageWholeOutput(); +} + +fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { + const subsurface = @fieldParentPtr(Subsurface, "commit", listener); + + subsurface.parent.damageWholeOutput(); +} + +fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void { + const subsurface = @fieldParentPtr(Subsurface, "new_subsurface", listener); + + Subsurface.create(new_wlr_subsurface, subsurface.parent); +} diff --git a/river/View.zig b/river/View.zig index 394dde4..cc44133 100644 --- a/river/View.zig +++ b/river/View.zig @@ -278,13 +278,14 @@ pub fn saveBuffers(self: *Self) void { /// Otherwise, apply the pending state immediately. pub fn notifyConfiguredOrApplyPending(self: *Self) void { self.pending_serial = null; - if (self.shouldTrackConfigure()) - server.root.notifyConfigured() - else { + if (self.shouldTrackConfigure()) { + server.root.notifyConfigured(); + } else { const self_tags_changed = self.pending.tags != self.current.tags; self.current = self.pending; self.commitOpacityTransition(); if (self_tags_changed) self.output.sendViewTags(); + self.output.damage.addWhole(); } } diff --git a/river/XdgPopup.zig b/river/XdgPopup.zig index 02a5c94..0f5b97d 100644 --- a/river/XdgPopup.zig +++ b/river/XdgPopup.zig @@ -23,32 +23,44 @@ const wl = @import("wayland").server.wl; const util = @import("util.zig"); -const Box = @import("Box.zig"); -const Output = @import("Output.zig"); +const Subsurface = @import("Subsurface.zig"); +const Parent = Subsurface.Parent; -const log = std.log.scoped(.server); - -/// The output this popup is displayed on. -output: *Output, - -/// Box of the parent of this popup tree. Needed to unconstrain child popups. -parent_box: *const Box, - -/// The corresponding wlroots object +/// The parent at the root of this surface tree +parent: Parent, wlr_xdg_popup: *wlr.XdgPopup, +// Always active destroy: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleDestroy), +map: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleMap), +unmap: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleUnmap), new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), +new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface), -pub fn init(self: *Self, output: *Output, parent_box: *const Box, wlr_xdg_popup: *wlr.XdgPopup) void { +// Only active while mapped +commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), + +pub fn create(wlr_xdg_popup: *wlr.XdgPopup, parent: Parent) void { + const self = util.gpa.create(Self) catch { + std.log.crit("out of memory", .{}); + wlr_xdg_popup.resource.postNoMemory(); + return; + }; self.* = .{ - .output = output, - .parent_box = parent_box, + .parent = parent, .wlr_xdg_popup = wlr_xdg_popup, }; + const parent_box = switch (parent) { + .view => |view| &view.pending.box, + .layer_surface => |layer_surface| &layer_surface.box, + }; + const output_dimensions = switch (parent) { + .view => |view| view.output.getEffectiveResolution(), + .layer_surface => |layer_surface| layer_surface.output.getEffectiveResolution(), + }; + // The output box relative to the parent of the popup - const output_dimensions = output.getEffectiveResolution(); var box = wlr.Box{ .x = -parent_box.x, .y = -parent_box.y, @@ -58,27 +70,52 @@ pub fn init(self: *Self, output: *Output, parent_box: *const Box, wlr_xdg_popup: wlr_xdg_popup.unconstrainFromBox(&box); wlr_xdg_popup.base.events.destroy.add(&self.destroy); + wlr_xdg_popup.base.events.map.add(&self.map); + wlr_xdg_popup.base.events.unmap.add(&self.unmap); wlr_xdg_popup.base.events.new_popup.add(&self.new_popup); + wlr_xdg_popup.base.surface.events.new_subsurface.add(&self.new_subsurface); } fn handleDestroy(listener: *wl.Listener(*wlr.XdgSurface), wlr_xdg_surface: *wlr.XdgSurface) void { const self = @fieldParentPtr(Self, "destroy", listener); self.destroy.link.remove(); + self.map.link.remove(); + self.unmap.link.remove(); self.new_popup.link.remove(); + self.new_subsurface.link.remove(); util.gpa.destroy(self); } -/// Called when a new xdg popup is requested by the client +fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "map", listener); + + self.wlr_xdg_popup.base.surface.events.commit.add(&self.commit); + self.parent.damageWholeOutput(); +} + +fn handleUnmap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "unmap", listener); + + self.commit.link.remove(); + self.parent.damageWholeOutput(); +} + +fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { + const self = @fieldParentPtr(Self, "commit", listener); + + self.parent.damageWholeOutput(); +} + fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void { const self = @fieldParentPtr(Self, "new_popup", listener); - // This will free itself on destroy - const xdg_popup = util.gpa.create(Self) catch { - wlr_xdg_popup.resource.postNoMemory(); - log.crit("out of memory", .{}); - return; - }; - xdg_popup.init(self.output, self.parent_box, wlr_xdg_popup); + Self.create(wlr_xdg_popup, self.parent); +} + +fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void { + const self = @fieldParentPtr(Self, "new_subsurface", listener); + + Subsurface.create(new_wlr_subsurface, self.parent); } diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index d1fb414..0dfe0a8 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -26,6 +26,7 @@ const util = @import("util.zig"); const Box = @import("Box.zig"); const Seat = @import("Seat.zig"); +const Subsurface = @import("Subsurface.zig"); const View = @import("View.zig"); const ViewStack = @import("view_stack.zig").ViewStack; const XdgPopup = @import("XdgPopup.zig"); @@ -42,10 +43,11 @@ xdg_surface: *wlr.XdgSurface, destroy: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleDestroy), map: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleMap), unmap: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleUnmap), +new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), +new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface), // Listeners that are only active while the view is mapped commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), -new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), // zig fmt: off request_fullscreen: wl.Listener(*wlr.XdgToplevel.event.SetFullscreen) = wl.Listener(*wlr.XdgToplevel.event.SetFullscreen).init(handleRequestFullscreen), @@ -65,6 +67,8 @@ pub fn init(self: *Self, view: *View, xdg_surface: *wlr.XdgSurface) void { self.xdg_surface.events.destroy.add(&self.destroy); self.xdg_surface.events.map.add(&self.map); self.xdg_surface.events.unmap.add(&self.unmap); + self.xdg_surface.events.new_popup.add(&self.new_popup); + self.xdg_surface.surface.events.new_subsurface.add(&self.new_subsurface); } pub fn deinit(self: *Self) void { @@ -73,6 +77,8 @@ pub fn deinit(self: *Self) void { self.destroy.link.remove(); self.map.link.remove(); self.unmap.link.remove(); + self.new_popup.link.remove(); + self.new_subsurface.link.remove(); } } @@ -161,7 +167,6 @@ fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurfa // Add listeners that are only active while mapped self.xdg_surface.surface.events.commit.add(&self.commit); - self.xdg_surface.events.new_popup.add(&self.new_popup); toplevel.events.request_fullscreen.add(&self.request_fullscreen); toplevel.events.request_move.add(&self.request_move); toplevel.events.request_resize.add(&self.request_resize); @@ -229,7 +234,6 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSur // Remove listeners that are only active while mapped self.commit.link.remove(); - self.new_popup.link.remove(); self.request_fullscreen.link.remove(); self.request_move.link.remove(); self.request_resize.link.remove(); @@ -260,6 +264,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) voi view.sendFrameDone(); } } else { + view.output.damage.addWhole(); // TODO: handle unexpected change in dimensions if (!std.meta.eql(view.surface_box, new_box)) log.err("view changed size unexpectedly", .{}); @@ -267,16 +272,14 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) voi } } -/// Called when a new xdg popup is requested by the client fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void { const self = @fieldParentPtr(Self, "new_popup", listener); + XdgPopup.create(wlr_xdg_popup, .{ .view = self.view }); +} - // This will free itself on destroy - const xdg_popup = util.gpa.create(XdgPopup) catch { - wlr_xdg_popup.resource.postNoMemory(); - return; - }; - xdg_popup.init(self.view.output, &self.view.current.box, wlr_xdg_popup); +fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void { + const self = @fieldParentPtr(Self, "new_subsurface", listener); + Subsurface.create(new_wlr_subsurface, .{ .view = self.view }); } /// Called when the client asks to be fullscreened. We always honor the request diff --git a/river/XwaylandUnmanaged.zig b/river/XwaylandUnmanaged.zig index d7d3fdd..323ee8c 100644 --- a/river/XwaylandUnmanaged.zig +++ b/river/XwaylandUnmanaged.zig @@ -37,6 +37,7 @@ request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) = 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), +commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), pub fn init(self: *Self, xwayland_surface: *wlr.XwaylandSurface) void { self.* = .{ .xwayland_surface = xwayland_surface }; @@ -78,6 +79,8 @@ fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wl const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); server.root.xwayland_unmanaged_views.prepend(node); + xwayland_surface.surface.?.events.commit.add(&self.commit); + // TODO: handle keyboard focus // if (wlr_xwayland_or_surface_wants_focus(self.xwayland_surface)) { ... } @@ -89,4 +92,11 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: * // Remove self from the list of unmanged views in the root const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); server.root.xwayland_unmanaged_views.remove(node); + + self.commit.link.remove(); +} + +fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { + var it = server.root.outputs.first; + while (it) |node| : (it = node.next) node.data.damage.addWhole(); } diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index f5360c7..b8af5a8 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -232,6 +232,9 @@ fn handleRequestConfigure( /// TODO: check for unexpected change in size and react as needed fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { const self = @fieldParentPtr(Self, "commit", listener); + + self.view.output.damage.addWhole(); + self.view.surface_box = Box{ .x = 0, .y = 0, diff --git a/river/render.zig b/river/render.zig index 5c00240..c1157b9 100644 --- a/river/render.zig +++ b/river/render.zig @@ -53,7 +53,19 @@ pub fn renderOutput(output: *Output) void { var now: os.timespec = undefined; os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch unreachable; - output.wlr_output.attachRender(null) catch return; + var needs_frame: bool = undefined; + var damage_region: pixman.Region32 = undefined; + damage_region.init(); + defer damage_region.deinit(); + output.damage.attachRender(&needs_frame, &damage_region) catch { + log.err("failed to attach renderer", .{}); + return; + }; + + if (!needs_frame) { + output.wlr_output.rollback(); + return; + } renderer.begin(@intCast(u32, output.wlr_output.width), @intCast(u32, output.wlr_output.height));