From 96a91fd2f7a20f56b87a9d8f2883356e4ff00449 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 3 Aug 2020 15:00:04 +0200 Subject: [PATCH] view: double buffer focus, use counter not bool - Double buffering focus state ensures that border color is kept in sync with the transaction state of views in the layout. - Using a counter instead of a bool will allow for proper handling of multiple seats. This is done in the same commit to avoid more churn in the future. --- river/Cursor.zig | 2 +- river/Root.zig | 2 +- river/Seat.zig | 18 ++++++++++++++++-- river/View.zig | 27 ++++++++------------------- river/VoidView.zig | 6 +----- river/XdgToplevel.zig | 24 ++++++++++++------------ river/XwaylandView.zig | 18 +++++++----------- river/render.zig | 6 +++--- 8 files changed, 49 insertions(+), 54 deletions(-) diff --git a/river/Cursor.zig b/river/Cursor.zig index 8d549f1..535b9f5 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -555,7 +555,7 @@ fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_s // Focused views are rendered on top, so look for them first. var it = ViewStack(View).iterator(output.views.first, output.current.tags); while (it.next()) |node| { - if (!node.view.focused) continue; + if (node.view.current.focus == 0) continue; if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found; } diff --git a/river/Root.zig b/river/Root.zig index c286c90..b556a42 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -141,7 +141,7 @@ fn startTransaction(self: *Self) void { if (view.needsConfigure()) { view.configure(); - self.pending_configures += 1; + if (!view.pending.float) self.pending_configures += 1; // Send a frame done that the client will commit a new frame // with the dimensions we sent in the configure. Normally this diff --git a/river/Seat.zig b/river/Seat.zig index 886cacc..52aae7c 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -17,6 +17,7 @@ const Self = @This(); +const build_options = @import("build_options"); const std = @import("std"); const c = @import("c.zig"); @@ -182,14 +183,24 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { // still clear the focus. if (if (target_wlr_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) { // First clear the current focus - if (self.focused == .view) self.focused.view.setFocused(false); + if (self.focused == .view) { + self.focused.view.pending.focus -= 1; + // This is needed because xwayland views don't double buffer + // activated state. + if (build_options.xwayland and self.focused.view.impl == .xwayland_view) + c.wlr_xwayland_surface_activate(self.focused.view.impl.xwayland_view.wlr_xwayland_surface, false); + } c.wlr_seat_keyboard_clear_focus(self.wlr_seat); // Set the new focus switch (new_focus) { .view => |target_view| { std.debug.assert(self.focused_output == target_view.output); - target_view.setFocused(true); + target_view.pending.focus += 1; + // This is needed because xwayland views don't double buffer + // activated state. + if (build_options.xwayland and target_view.impl == .xwayland_view) + c.wlr_xwayland_surface_activate(target_view.impl.xwayland_view.wlr_xwayland_surface, true); }, .layer => |target_layer| std.debug.assert(self.focused_output == target_layer.output), .none => {}, @@ -212,6 +223,9 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { // Inform any clients tracking status of the change var it = self.status_trackers.first; while (it) |node| : (it = node.next) node.data.sendFocusedView(); + + // Start a transaction to apply the pending focus state + self.input_manager.server.root.startTransaction(); } /// Focus the given output, notifying any listening clients of the change. diff --git a/river/View.zig b/river/View.zig index 2154829..fd21e08 100644 --- a/river/View.zig +++ b/river/View.zig @@ -60,6 +60,9 @@ const State = struct { /// The tags of the view, as a bitmask tags: u32, + /// Number of seats currently focusing the view + focus: u32, + float: bool, fullscreen: bool, }; @@ -79,9 +82,6 @@ output: *Output, /// This is non-null exactly when the view is mapped wlr_surface: ?*c.wlr_surface, -/// True if the view is currently focused by at least one seat -focused: bool, - /// The double-buffered state of the view current: State, pending: State, @@ -111,8 +111,6 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void { self.wlr_surface = null; - self.focused = false; - self.current = .{ .box = .{ .x = 0, @@ -121,6 +119,7 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void { .width = 0, }, .tags = tags, + .focus = 0, .float = false, .fullscreen = false, }; @@ -155,8 +154,8 @@ pub fn needsConfigure(self: Self) bool { pub fn configure(self: Self) void { switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(self.pending.box), - .xwayland_view => |xwayland_view| xwayland_view.configure(self.pending.box), + .xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(), + .xwayland_view => |xwayland_view| xwayland_view.configure(), } } @@ -205,16 +204,6 @@ fn saveBuffersIterator( } } -/// Set the focused bool and the active state of the view if it is a toplevel -/// TODO: This is insufficient for multi-seat, probably need a focus counter. -pub fn setFocused(self: *Self, focused: bool) void { - self.focused = focused; - switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.setActivated(focused), - .xwayland_view => |xwayland_view| xwayland_view.setActivated(focused), - } -} - /// Set the pending state, set the size, and inform the client. pub fn setFullscreen(self: *Self, fullscreen: bool) void { self.pending.fullscreen = fullscreen; @@ -358,8 +347,6 @@ pub fn unmap(self: *Self) void { log.debug(.server, "view '{}' unmapped", .{self.getTitle()}); - self.wlr_surface = null; - // Inform all seats that the view has been unmapped so they can handle focus var it = root.server.input_manager.seats.first; while (it) |node| : (it = node.next) { @@ -367,6 +354,8 @@ pub fn unmap(self: *Self) void { seat.handleViewUnmap(self); } + self.wlr_surface = null; + // Remove the view from its output's stack const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); self.output.views.remove(node); diff --git a/river/VoidView.zig b/river/VoidView.zig index 784b9f0..b212dd6 100644 --- a/river/VoidView.zig +++ b/river/VoidView.zig @@ -28,11 +28,7 @@ pub fn needsConfigure(self: Self) bool { unreachable; } -pub fn configure(self: Self, pending_box: Box) void { - unreachable; -} - -pub fn setActivated(self: Self, activated: bool) void { +pub fn configure(self: Self) void { unreachable; } diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index 12c5034..af44cf3 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -63,32 +63,32 @@ pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void /// Returns true if a configure must be sent to ensure the dimensions of the /// pending_box are applied. pub fn needsConfigure(self: Self) bool { - const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field( + const server_pending = &@field( self.wlr_xdg_surface, c.wlr_xdg_surface_union, - ).toplevel; + ).toplevel.*.server_pending; + const state = &self.view.pending; // Checking server_pending is sufficient here since it will be either in // sync with the current dimensions or be the dimensions sent with the // most recent configure. In both cases server_pending has the values we // want to check against. - return self.view.pending.box.width != wlr_xdg_toplevel.server_pending.width or - self.view.pending.box.height != wlr_xdg_toplevel.server_pending.height; + return (state.focus != 0) != server_pending.activated or + state.box.width != server_pending.width or + state.box.height != server_pending.height; } -/// Send a configure event, applying the width/height of the pending box. -pub fn configure(self: Self, pending_box: Box) void { +/// Send a configure event, applying the pending state of the view. +pub fn configure(self: Self) void { + const state = &self.view.pending; + _ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, state.focus != 0); self.view.pending_serial = c.wlr_xdg_toplevel_set_size( self.wlr_xdg_surface, - pending_box.width, - pending_box.height, + state.box.width, + state.box.height, ); } -pub fn setActivated(self: Self, activated: bool) void { - _ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, activated); -} - pub fn setFullscreen(self: Self, fullscreen: bool) void { _ = c.wlr_xdg_toplevel_set_fullscreen(self.wlr_xdg_surface, fullscreen); } diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 7ae248b..80fb7e3 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -63,14 +63,15 @@ pub fn needsConfigure(self: Self) bool { self.wlr_xwayland_surface.height != self.view.pending.box.height; } -/// Tell the client to take a new size -pub fn configure(self: Self, pending_box: Box) void { +/// Apply pending state +pub fn configure(self: Self) void { + const state = &self.view.pending; c.wlr_xwayland_surface_configure( self.wlr_xwayland_surface, - @intCast(i16, pending_box.x), - @intCast(i16, pending_box.y), - @intCast(u16, pending_box.width), - @intCast(u16, pending_box.height), + @intCast(i16, state.box.x), + @intCast(i16, state.box.y), + @intCast(u16, state.box.width), + @intCast(u16, state.box.height), ); // Xwayland surfaces don't use serials, so we will just assume they have // configured the next time they commit. Set pending serial to a dummy @@ -80,11 +81,6 @@ pub fn configure(self: Self, pending_box: Box) void { self.view.pending_serial = 0x66666666; } -/// Inform the xwayland surface that it has gained focus -pub fn setActivated(self: Self, activated: bool) void { - c.wlr_xwayland_surface_activate(self.wlr_xwayland_surface, activated); -} - pub fn setFullscreen(self: Self, fullscreen: bool) void { c.wlr_xwayland_surface_set_fullscreen(self.wlr_xwayland_surface, fullscreen); } diff --git a/river/render.zig b/river/render.zig index fc72266..76492ab 100644 --- a/river/render.zig +++ b/river/render.zig @@ -85,7 +85,7 @@ pub fn renderOutput(output: *Output) void { if (view.current.box.width == 0 or view.current.box.height == 0) continue; // Focused views are rendered on top of normal views, skip them for now - if (view.focused) continue; + if (view.current.focus != 0) continue; renderView(output.*, view, &now); if (view.draw_borders) renderBorders(output.*, view, &now); @@ -101,7 +101,7 @@ pub fn renderOutput(output: *Output) void { if (view.current.box.width == 0 or view.current.box.height == 0) continue; // Skip unfocused views since we already rendered them - if (!view.focused) continue; + if (view.current.focus == 0) continue; renderView(output.*, view, &now); if (view.draw_borders) renderBorders(output.*, view, &now); @@ -254,7 +254,7 @@ fn renderTexture( fn renderBorders(output: Output, view: *View, now: *c.timespec) void { const config = &output.root.server.config; - const color = if (view.focused) &config.border_color_focused else &config.border_color_unfocused; + const color = if (view.current.focus != 0) &config.border_color_focused else &config.border_color_unfocused; const border_width = config.border_width; const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box;