aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--river/Cursor.zig2
-rw-r--r--river/Output.zig7
-rw-r--r--river/OutputStatus.zig2
-rw-r--r--river/Root.zig25
-rw-r--r--river/Seat.zig4
-rw-r--r--river/View.zig36
-rw-r--r--river/XdgToplevel.zig14
-rw-r--r--river/XwaylandOverrideRedirect.zig2
-rw-r--r--river/XwaylandView.zig20
-rw-r--r--river/command/focus_view.zig2
-rw-r--r--river/command/swap.zig2
-rw-r--r--river/command/zoom.zig2
-rw-r--r--river/render.zig399
13 files changed, 77 insertions, 440 deletions
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 5be5cdd..feca44f 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -885,7 +885,7 @@ fn xwaylandOverrideRedirectSurfaceAt(lx: f64, ly: f64) ?SurfaceAtResult {
}
fn surfaceAtFilter(view: *View, filter_tags: u32) bool {
- return view.surface != null and view.current.tags & filter_tags != 0;
+ return view.tree.node.enabled and view.current.tags & filter_tags != 0;
}
pub fn enterMode(self: *Self, mode: enum { move, resize }, view: *View) void {
diff --git a/river/Output.zig b/river/Output.zig
index 0c7b462..ca4ac29 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -64,6 +64,10 @@ layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} **
/// TODO: this should be part of the output's State
usable_box: wlr.Box,
+/// Scene node representing the entire output.
+/// Position must be updated when the output is moved in the layout.
+tree: *wlr.SceneTree,
+
/// The top of the stack is the "most important" view.
views: ViewStack(View) = .{},
@@ -144,6 +148,7 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void {
self.* = .{
.wlr_output = wlr_output,
+ .tree = try server.root.scene.tree.createSceneTree(),
.usable_box = undefined,
};
wlr_output.data = @ptrToInt(self);
@@ -208,7 +213,7 @@ pub fn sendLayoutNameClear(self: Self) void {
}
pub fn arrangeFilter(view: *View, filter_tags: u32) bool {
- return view.surface != null and !view.pending.float and !view.pending.fullscreen and
+ return view.tree.node.enabled 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 fc5b67e..8a22128 100644
--- a/river/OutputStatus.zig
+++ b/river/OutputStatus.zig
@@ -77,7 +77,7 @@ pub fn sendViewTags(self: Self) void {
var it = self.output.views.first;
while (it) |node| : (it = node.next) {
- if (node.view.surface == null) continue;
+ if (!node.view.tree.node.enabled) continue;
view_tags.append(node.view.current.tags) catch {
self.output_status.postNoMemory();
log.err("out of memory", .{});
diff --git a/river/Root.zig b/river/Root.zig
index b0229ad..2543f13 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -32,6 +32,8 @@ const ViewStack = @import("view_stack.zig").ViewStack;
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
const DragIcon = @import("DragIcon.zig");
+scene: *wlr.Scene,
+
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
output_layout: *wlr.OutputLayout,
@@ -77,6 +79,11 @@ pub fn init(self: *Self) !void {
const output_layout = try wlr.OutputLayout.create();
errdefer output_layout.destroy();
+ const scene = try wlr.Scene.create();
+ errdefer scene.tree.node.destroy();
+
+ try scene.attachOutputLayout(output_layout);
+
_ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout);
const event_loop = server.wl_server.getEventLoop();
@@ -85,12 +92,14 @@ pub fn init(self: *Self) !void {
const noop_wlr_output = try server.headless_backend.headlessAddOutput(1920, 1080);
self.* = .{
+ .scene = scene,
.output_layout = output_layout,
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
.transaction_timer = transaction_timer,
.noop_output = .{
.wlr_output = noop_wlr_output,
+ .tree = try scene.tree.createSceneTree(),
.usable_box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
},
};
@@ -104,6 +113,7 @@ pub fn init(self: *Self) !void {
}
pub fn deinit(self: *Self) void {
+ self.scene.tree.node.destroy();
self.output_layout.destroy();
self.transaction_timer.remove();
}
@@ -204,7 +214,11 @@ pub fn addOutput(self: *Self, output: *Output) void {
// This aarranges 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.
- self.output_layout.addAuto(node.data.wlr_output);
+ self.output_layout.addAuto(output.wlr_output);
+
+ const layout_output = self.output_layout.get(output.wlr_output).?;
+ output.tree.node.setEnabled(true);
+ output.tree.node.setPosition(layout_output.x, layout_output.y);
// If we previously had no real outputs, move focus from the noop output
// to the new one.
@@ -271,7 +285,7 @@ pub fn startTransaction(self: *Self) void {
while (view_it) |view_node| : (view_it = view_node.next) {
const view = &view_node.view;
- if (view.surface == null) continue;
+ if (!view.tree.node.enabled) continue;
if (view.shouldTrackConfigure()) {
// Clear the serial in case this transaction is interrupting a prior one.
@@ -366,7 +380,7 @@ fn commitTransaction(self: *Self) void {
const view = &view_node.view;
view_it = view_node.next;
- if (view.surface == null) {
+ if (!view.tree.node.enabled) {
view.dropSavedBuffers();
view.output.views.remove(view_node);
if (view.destroying) view.destroy();
@@ -383,6 +397,8 @@ fn commitTransaction(self: *Self) void {
if (view.pending.urgent and view_tags_changed) urgent_tags_dirty = true;
view.current = view.pending;
+ view.tree.node.setPosition(view.current.box.x, view.current.box.y);
+
view.dropSavedBuffers();
}
@@ -459,10 +475,13 @@ fn processOutputConfig(
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);
+ output.tree.node.setEnabled(true);
+ output.tree.node.setPosition(head.state.x, head.state.y);
output.arrangeLayers(.mapped);
} else {
self.removeOutput(output);
self.output_layout.remove(output.wlr_output);
+ output.tree.node.setEnabled(false);
}
} else {
std.log.scoped(.output_manager).err("failed to apply config to output {s}", .{
diff --git a/river/Seat.zig b/river/Seat.zig
index 861a9ab..6f206f6 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -210,7 +210,7 @@ pub fn focus(self: *Self, _target: ?*View) void {
}
fn pendingFilter(view: *View, filter_tags: u32) bool {
- return view.surface != null and view.pending.tags & filter_tags != 0;
+ return view.tree.node.enabled and view.pending.tags & filter_tags != 0;
}
/// Switch focus to the target, handling unfocus and input inhibition
@@ -222,7 +222,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// Obtain the target surface
const target_surface = switch (new_focus) {
- .view => |target_view| target_view.surface.?,
+ .view => |target_view| target_view.rootSurface(),
.xwayland_override_redirect => |target_or| target_or.xwayland_surface.surface,
.layer => |target_layer| target_layer.wlr_layer_surface.surface,
.lock_surface => |lock_surface| lock_surface.wlr_lock_surface.surface,
diff --git a/river/View.zig b/river/View.zig
index 8c8f421..5dc4ce2 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -48,9 +48,7 @@ const Impl = union(enum) {
};
const State = struct {
- /// The output-relative effective coordinates and effective dimensions of the view. The
- /// surface itself may have other dimensions which are stored in the
- /// surface_box member.
+ /// The output-relative coordinates of the view and dimensions requested by river.
box: wlr.Box = wlr.Box{ .x = 0, .y = 0, .width = 0, .height = 0 },
/// The tags of the view, as a bitmask
@@ -78,8 +76,7 @@ impl: Impl,
/// The output this view is currently associated with
output: *Output,
-/// This is non-null exactly when the view is mapped
-surface: ?*wlr.Surface = null,
+tree: *wlr.SceneTree,
/// This indicates that the view should be destroyed when the current
/// transaction completes. See View.destroy()
@@ -116,7 +113,7 @@ draw_borders: bool = true,
request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) =
wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate),
-pub fn init(self: *Self, output: *Output, impl: Impl) void {
+pub fn init(self: *Self, output: *Output, tree: *wlr.SceneTree, impl: Impl) void {
const initial_tags = blk: {
const tags = output.current.tags & server.config.spawn_tagmask;
break :blk if (tags != 0) tags else output.current.tags;
@@ -125,6 +122,7 @@ pub fn init(self: *Self, output: *Output, impl: Impl) void {
self.* = .{
.impl = impl,
.output = output,
+ .tree = tree,
.current = .{ .tags = initial_tags },
.pending = .{ .tags = initial_tags },
};
@@ -134,7 +132,6 @@ pub fn init(self: *Self, output: *Output, impl: Impl) void {
/// mark this view for destruction when the transaction completes. Otherwise
/// destroy immediately.
pub fn destroy(self: *Self) void {
- assert(self.surface == null);
self.destroying = true;
// If there are still saved buffers, then this view needs to be kept
@@ -210,10 +207,19 @@ fn lastSetFullscreenState(self: Self) bool {
};
}
+pub fn rootSurface(self: Self) *wlr.Surface {
+ assert(!self.destroying);
+ return switch (self.impl) {
+ .xdg_toplevel => |xdg_toplevel| xdg_toplevel.rootSurface(),
+ .xwayland_view => |xwayland_view| xwayland_view.rootSurface(),
+ };
+}
+
pub fn sendFrameDone(self: Self) void {
+ assert(!self.destroying);
var now: os.timespec = undefined;
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
- self.surface.?.sendFrameDone(&now);
+ self.rootSurface().sendFrameDone(&now);
}
pub fn dropSavedBuffers(self: *Self) void {
@@ -267,12 +273,6 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void {
destination_output.sendUrgentTags();
}
- // if the view is mapped send enter/leave events
- if (self.surface != null) {
- self.sendLeave(self.output);
- self.sendEnter(destination_output);
- }
-
self.output = destination_output;
var output_width: i32 = undefined;
@@ -365,9 +365,8 @@ pub inline fn forEachSurface(
.xdg_toplevel => |xdg_toplevel| {
xdg_toplevel.xdg_toplevel.base.forEachSurface(T, iterator, user_data);
},
- .xwayland_view => {
- assert(build_options.xwayland);
- self.surface.?.forEachSurface(T, iterator, user_data);
+ .xwayland_view => |xwayland_view| {
+ xwayland_view.xwayland_surface.surface.?.forEachSurface(T, iterator, user_data);
},
}
}
@@ -489,9 +488,6 @@ pub fn unmap(self: *Self) void {
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);
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 0e06e1f..9a5e077 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -62,7 +62,9 @@ pub fn create(output: *Output, xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory
const node = try util.gpa.create(ViewStack(View).Node);
const view = &node.view;
- view.init(output, .{ .xdg_toplevel = .{
+ const tree = try output.tree.createSceneXdgSurface(xdg_toplevel.base);
+
+ view.init(output, tree, .{ .xdg_toplevel = .{
.view = view,
.xdg_toplevel = xdg_toplevel,
} });
@@ -99,6 +101,10 @@ pub fn lastSetFullscreenState(self: Self) bool {
return self.xdg_toplevel.scheduled.fullscreen;
}
+pub fn rootSurface(self: Self) *wlr.Surface {
+ return self.xdg_toplevel.base.surface;
+}
+
/// Close the view. This will lead to the unmap and destroy events being sent
pub fn close(self: Self) void {
self.xdg_toplevel.sendClose();
@@ -190,9 +196,6 @@ fn handleMap(listener: *wl.Listener(void)) void {
self.xdg_toplevel.scheduled.width = initial_box.width;
self.xdg_toplevel.scheduled.height = initial_box.height;
- view.surface = self.xdg_toplevel.base.surface;
- view.surface_box = initial_box;
-
// Also use the view's "natural" size as the initial regular dimensions,
// for the case that it does not get arranged by a lyaout.
view.pending.box = view.float_box;
@@ -275,7 +278,10 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
const self_tags_changed = view.pending.tags != view.current.tags;
const urgent_tags_dirty = view.pending.urgent != view.current.urgent or
(view.pending.urgent and self_tags_changed);
+
view.current = view.pending;
+ view.tree.node.setPosition(view.current.box.x, view.current.box.y);
+
if (self_tags_changed) view.output.sendViewTags();
if (urgent_tags_dirty) view.output.sendUrgentTags();
diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig
index 1da6bf3..5a7c420 100644
--- a/river/XwaylandOverrideRedirect.zig
+++ b/river/XwaylandOverrideRedirect.zig
@@ -134,7 +134,7 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSur
focused.impl.xwayland_view.xwayland_surface.pid == self.xwayland_surface.pid and
seat.wlr_seat.keyboard_state.focused_surface == self.xwayland_surface.surface)
{
- seat.keyboardEnterOrLeave(focused.surface.?);
+ seat.keyboardEnterOrLeave(focused.rootSurface());
},
.xwayland_override_redirect => |focused| if (focused == self) seat.focus(null),
.layer, .lock_surface, .none => {},
diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig
index c1be61e..3b6d807 100644
--- a/river/XwaylandView.zig
+++ b/river/XwaylandView.zig
@@ -67,7 +67,10 @@ pub fn create(output: *Output, xwayland_surface: *wlr.XwaylandSurface) error{Out
const node = try util.gpa.create(ViewStack(View).Node);
const view = &node.view;
- view.init(output, .{ .xwayland_view = .{
+ // TODO actually render xwayland windows, not just an empty tree.
+ const tree = try output.tree.createSceneTree();
+
+ view.init(output, tree, .{ .xwayland_view = .{
.view = view,
.xwayland_surface = xwayland_surface,
} });
@@ -115,6 +118,11 @@ pub fn lastSetFullscreenState(self: Self) bool {
return self.last_set_fullscreen_state;
}
+pub fn rootSurface(self: Self) *wlr.Surface {
+ // TODO This is probably not OK, understand when xwayland surfaces can be null.
+ return self.xwayland_surface.surface.?;
+}
+
/// Close the view. This will lead to the unmap and destroy events being sent
pub fn close(self: Self) void {
self.xwayland_surface.close();
@@ -198,14 +206,6 @@ 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);
- view.surface = surface;
- self.view.surface_box = .{
- .x = 0,
- .y = 0,
- .width = surface.current.width,
- .height = surface.current.height,
- };
-
// Use the view's "natural" size centered on the output as the default
// floating dimensions
view.float_box = .{
@@ -257,7 +257,7 @@ fn handleRequestConfigure(
const self = @fieldParentPtr(Self, "request_configure", listener);
// If unmapped, let the client do whatever it wants
- if (self.view.surface == null) {
+ if (!self.xwayland_surface.mapped) {
self.xwayland_surface.configure(event.x, event.y, event.width, event.height);
return;
}
diff --git a/river/command/focus_view.zig b/river/command/focus_view.zig
index 4ca255a..4840f83 100644
--- a/river/command/focus_view.zig
+++ b/river/command/focus_view.zig
@@ -70,5 +70,5 @@ pub fn focusView(
}
fn filter(view: *View, filter_tags: u32) bool {
- return view.surface != null and view.pending.tags & filter_tags != 0;
+ return view.tree.node.enabled and view.pending.tags & filter_tags != 0;
}
diff --git a/river/command/swap.zig b/river/command/swap.zig
index 7345479..ed3118f 100644
--- a/river/command/swap.zig
+++ b/river/command/swap.zig
@@ -78,6 +78,6 @@ pub fn swap(
}
fn filter(view: *View, filter_tags: u32) bool {
- return view.surface != null and !view.pending.float and
+ return view.tree.node.enabled 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 ecd1dc5..853aeb0 100644
--- a/river/command/zoom.zig
+++ b/river/command/zoom.zig
@@ -59,6 +59,6 @@ pub fn zoom(
}
fn filter(view: *View, filter_tags: u32) bool {
- return view.surface != null and !view.pending.float and
+ return view.tree.node.enabled and !view.pending.float and
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
}
diff --git a/river/render.zig b/river/render.zig
index b992e84..b343aeb 100644
--- a/river/render.zig
+++ b/river/render.zig
@@ -14,411 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-const build_options = @import("build_options");
const std = @import("std");
const os = std.os;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const pixman = @import("pixman");
const server = &@import("main.zig").server;
-const util = @import("util.zig");
-const LayerSurface = @import("LayerSurface.zig");
const Output = @import("Output.zig");
-const Server = @import("Server.zig");
-const View = @import("View.zig");
-const ViewStack = @import("view_stack.zig").ViewStack;
const log = std.log.scoped(.render);
-const SurfaceRenderData = struct {
- output: *const Output,
-
- /// In output layout coordinates relative to the output
- output_x: i32,
- output_y: i32,
-
- when: *os.timespec,
-};
-
-/// The rendering order in this function must be kept in sync with Cursor.surfaceAt()
pub fn renderOutput(output: *Output) void {
- var now: os.timespec = undefined;
- os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
-
- output.wlr_output.attachRender(null) catch {
- log.err("failed to attach renderer", .{});
- return;
- };
-
- server.renderer.begin(@intCast(u32, output.wlr_output.width), @intCast(u32, output.wlr_output.height));
-
- // In order to avoid flashing a blank black screen as the session is locked
- // continue to render the unlocked session until either a lock surface is
- // created or waiting for lock surfaces times out.
- if (server.lock_manager.state == .locked or
- (server.lock_manager.state == .waiting_for_lock_surfaces and output.lock_surface != null) or
- server.lock_manager.state == .waiting_for_blank)
- {
- server.renderer.clear(&[_]f32{ 0, 0, 0, 1 }); // solid black
-
- // TODO: this isn't frame-perfect if the output mode is changed. We
- // could possibly delay rendering new frames after the mode change
- // until the surface commits a buffer of the correct size.
- if (output.lock_surface) |lock_surface| {
- var rdata = SurfaceRenderData{
- .output = output,
- .output_x = 0,
- .output_y = 0,
- .when = &now,
- };
- lock_surface.wlr_lock_surface.surface.forEachSurface(
- *SurfaceRenderData,
- renderSurfaceIterator,
- &rdata,
- );
- }
-
- renderDragIcons(output, &now);
-
- output.wlr_output.renderSoftwareCursors(null);
- server.renderer.end();
- output.wlr_output.commit() catch {
- log.err("output commit failed for {s}", .{output.wlr_output.name});
- return;
- };
-
- if (server.lock_manager.state == .locked) {
- switch (output.lock_render_state) {
- .unlocked, .pending_blank, .pending_lock_surface => unreachable,
- .blanked, .lock_surface => {},
- }
- } else {
- if (output.lock_surface == null) {
- output.lock_render_state = .pending_blank;
- } else {
- output.lock_render_state = .pending_lock_surface;
- }
- }
-
- return;
- }
- output.lock_render_state = .unlocked;
-
- // Find the first visible fullscreen view in the stack if there is one
- var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter);
- const fullscreen_view = while (it.next()) |view| {
- if (view.current.fullscreen) break view;
- } else null;
-
- // If we have a fullscreen view to render, render it.
- if (fullscreen_view) |view| {
- // Always clear with solid black for fullscreen
- server.renderer.clear(&[_]f32{ 0, 0, 0, 1 });
- renderView(output, view, &now);
- if (build_options.xwayland) renderXwaylandOverrideRedirect(output, &now);
- } else {
- // No fullscreen view, so render normal layers/views
- server.renderer.clear(&server.config.background_color);
-
- renderLayer(output, output.getLayer(.background).*, &now, .toplevels);
- renderLayer(output, output.getLayer(.bottom).*, &now, .toplevels);
-
- // The first view in the list is "on top" so always iterate in reverse.
-
- // non-focused, non-floating views
- it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
- while (it.next()) |view| {
- if (view.current.focus != 0 or view.current.float) continue;
- if (view.draw_borders) renderBorders(output, view);
- renderView(output, view, &now);
- }
-
- // focused, non-floating views
- it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
- while (it.next()) |view| {
- if (view.current.focus == 0 or view.current.float) continue;
- if (view.draw_borders) renderBorders(output, view);
- renderView(output, view, &now);
- }
-
- // non-focused, floating views
- it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
- while (it.next()) |view| {
- if (view.current.focus != 0 or !view.current.float) continue;
- if (view.draw_borders) renderBorders(output, view);
- renderView(output, view, &now);
- }
-
- // focused, floating views
- it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
- while (it.next()) |view| {
- if (view.current.focus == 0 or !view.current.float) continue;
- if (view.draw_borders) renderBorders(output, view);
- renderView(output, view, &now);
- }
-
- if (build_options.xwayland) renderXwaylandOverrideRedirect(output, &now);
-
- renderLayer(output, output.getLayer(.top).*, &now, .toplevels);
-
- renderLayer(output, output.getLayer(.background).*, &now, .popups);
- renderLayer(output, output.getLayer(.bottom).*, &now, .popups);
- renderLayer(output, output.getLayer(.top).*, &now, .popups);
- }
-
- // The overlay layer is rendered in both fullscreen and normal cases
- renderLayer(output, output.getLayer(.overlay).*, &now, .toplevels);
- renderLayer(output, output.getLayer(.overlay).*, &now, .popups);
-
- renderDragIcons(output, &now);
-
- output.wlr_output.renderSoftwareCursors(null);
-
- server.renderer.end();
-
- output.wlr_output.commit() catch
+ const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?;
+ if (!scene_output.commit()) {
log.err("output commit failed for {s}", .{output.wlr_output.name});
-}
-
-fn renderFilter(view: *View, filter_tags: u32) bool {
- // This check prevents a race condition when a frame is requested
- // between mapping of a view and the first configure being handled.
- if (view.current.box.width == 0 or view.current.box.height == 0)
- return false;
- return view.current.tags & filter_tags != 0;
-}
-
-/// Render all surfaces on the passed layer
-fn renderLayer(
- output: *const Output,
- layer: std.TailQueue(LayerSurface),
- now: *os.timespec,
- role: enum { toplevels, popups },
-) void {
- var it = layer.first;
- while (it) |node| : (it = node.next) {
- const layer_surface = &node.data;
- var rdata = SurfaceRenderData{
- .output = output,
- .output_x = layer_surface.box.x,
- .output_y = layer_surface.box.y,
- .when = now,
- };
- switch (role) {
- .toplevels => layer_surface.wlr_layer_surface.surface.forEachSurface(
- *SurfaceRenderData,
- renderSurfaceIterator,
- &rdata,
- ),
- .popups => layer_surface.wlr_layer_surface.forEachPopupSurface(
- *SurfaceRenderData,
- renderSurfaceIterator,
- &rdata,
- ),
- }
}
-}
-/// Render all surfaces in the view's surface tree, including subsurfaces and popups
-fn renderView(output: *const Output, view: *View, now: *os.timespec) void {
- // If we have saved buffers, we are in the middle of a transaction
- // and need to render those buffers until the transaction is complete.
- if (view.saved_buffers.items.len != 0) {
- for (view.saved_buffers.items) |saved_buffer| {
- const texture = saved_buffer.client_buffer.texture orelse continue;
- renderTexture(
- output,
- texture,
- .{
- .x = saved_buffer.surface_box.x + view.current.box.x - view.saved_surface_box.x,
- .y = saved_buffer.surface_box.y + view.current.box.y - view.saved_surface_box.y,
- .width = saved_buffer.surface_box.width,
- .height = saved_buffer.surface_box.height,
- },
- &saved_buffer.source_box,
- saved_buffer.transform,
- );
- }
- } else {
- // Since there are no stashed buffers, we are not in the middle of
- // a transaction and may simply render the most recent buffers provided
- // by the client.
- var rdata = SurfaceRenderData{
- .output = output,
- .output_x = view.current.box.x - view.surface_box.x,
- .output_y = view.current.box.y - view.surface_box.y,
- .when = now,
- };
- view.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
- }
-}
-
-fn renderDragIcons(output: *const Output, now: *os.timespec) void {
- var output_box: wlr.Box = undefined;
- server.root.output_layout.getBox(output.wlr_output, &output_box);
-
- var it = server.input_manager.seats.first;
- while (it) |node| : (it = node.next) {
- const icon = node.data.drag_icon orelse continue;
-
- var lx: f64 = undefined;
- var ly: f64 = undefined;
- switch (icon.wlr_drag_icon.drag.grab_type) {
- .keyboard_pointer => {
- lx = icon.seat.cursor.wlr_cursor.x;
- ly = icon.seat.cursor.wlr_cursor.y;
- },
- .keyboard_touch => {
- const touch_id = icon.wlr_drag_icon.drag.touch_id;
- const point = icon.seat.cursor.touch_points.get(touch_id) orelse continue;
- lx = point.lx;
- ly = point.ly;
- },
- .keyboard => unreachable,
- }
-
- var rdata = SurfaceRenderData{
- .output = output,
- .output_x = @floatToInt(i32, lx) + icon.sx - output_box.x,
- .output_y = @floatToInt(i32, ly) + icon.sy - output_box.y,
- .when = now,
- };
- icon.wlr_drag_icon.surface.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
- }
-}
-
-/// Render all override redirect xwayland windows that appear on the output
-fn renderXwaylandOverrideRedirect(output: *const Output, now: *os.timespec) void {
- var output_box: wlr.Box = undefined;
- server.root.output_layout.getBox(output.wlr_output, &output_box);
-
- var it = server.root.xwayland_override_redirect_views.last;
- while (it) |node| : (it = node.prev) {
- const xwayland_surface = node.data.xwayland_surface;
-
- var rdata = SurfaceRenderData{
- .output = output,
- .output_x = xwayland_surface.x - output_box.x,
- .output_y = xwayland_surface.y - output_box.y,
- .when = now,
- };
- xwayland_surface.surface.?.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
- }
-}
-
-/// This function is passed to wlroots to render each surface during iteration
-fn renderSurfaceIterator(
- surface: *wlr.Surface,
- surface_x: c_int,
- surface_y: c_int,
- rdata: *SurfaceRenderData,
-) void {
- const texture = surface.getTexture() orelse return;
-
- var source_box: wlr.FBox = undefined;
- surface.getBufferSourceBox(&source_box);
-
- renderTexture(
- rdata.output,
- texture,
- .{
- .x = rdata.output_x + surface_x,
- .y = rdata.output_y + surface_y,
- .width = surface.current.width,
- .height = surface.current.height,
- },
- &source_box,
- surface.current.transform,
- );
-
- surface.sendFrameDone(rdata.when);
-}
-
-/// Render the given texture at the given box, taking the scale and transform
-/// of the output into account.
-fn renderTexture(
- output: *const Output,
- texture: *wlr.Texture,
- dest_box: wlr.Box,
- source_box: *const wlr.FBox,
- transform: wl.Output.Transform,
-) void {
- var box = dest_box;
-
- // Scale the box to the output's current scaling factor
- scaleBox(&box, output.wlr_output.scale);
-
- // wlr_matrix_project_box is a helper which takes a box with a desired
- // x, y coordinates, width and height, and an output geometry, then
- // prepares an orthographic projection and multiplies the necessary
- // transforms to produce a model-view-projection matrix.
- var matrix: [9]f32 = undefined;
- const inverted = wlr.Output.transformInvert(transform);
- wlr.matrix.projectBox(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix);
-
- // This takes our matrix, the texture, and an alpha, and performs the actual
- // rendering on the GPU.
- server.renderer.renderSubtextureWithMatrix(texture, source_box, &matrix, 1.0) catch return;
-}
-
-fn renderBorders(output: *const Output, view: *View) void {
- const config = &server.config;
- const color = blk: {
- if (view.current.urgent) break :blk &config.border_color_urgent;
- if (view.current.focus != 0) break :blk &config.border_color_focused;
- break :blk &config.border_color_unfocused;
- };
- const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box;
-
- var border: wlr.Box = undefined;
-
- // left and right, covering the corners as well
- border.y = view.current.box.y - config.border_width;
- border.width = config.border_width;
- border.height = actual_box.height + config.border_width * 2;
-
- // left
- border.x = view.current.box.x - config.border_width;
- renderRect(output, border, color);
-
- // right
- border.x = view.current.box.x + actual_box.width;
- renderRect(output, border, color);
-
- // top and bottom
- border.x = view.current.box.x;
- border.width = actual_box.width;
- border.height = config.border_width;
-
- // top
- border.y = view.current.box.y - config.border_width;
- renderRect(output, border, color);
-
- // bottom border
- border.y = view.current.box.y + actual_box.height;
- renderRect(output, border, color);
-}
-
-fn renderRect(output: *const Output, box: wlr.Box, color: *const [4]f32) void {
- var scaled = box;
- scaleBox(&scaled, output.wlr_output.scale);
- server.renderer.renderRect(&scaled, color, &output.wlr_output.transform_matrix);
-}
-
-/// Scale a wlr_box, taking the possibility of fractional scaling into account.
-fn scaleBox(box: *wlr.Box, scale: f64) void {
- box.width = scaleLength(box.width, box.x, scale);
- box.height = scaleLength(box.height, box.y, scale);
- box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale));
- box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale));
-}
-
-/// Scales a width/height.
-///
-/// This might seem overly complex, but it needs to work for fractional scaling.
-fn scaleLength(length: c_int, offset: c_int, scale: f64) c_int {
- return @floatToInt(c_int, @round(@intToFloat(f64, offset + length) * scale) -
- @round(@intToFloat(f64, offset) * scale));
+ var now: os.timespec = undefined;
+ os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
+ scene_output.sendFrameDone(&now);
}