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 . -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 { + 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}); + } + 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 - 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)); + scene_output.sendFrameDone(&now); }