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.
This commit is contained in:
Isaac Freund 2020-08-03 15:00:04 +02:00
parent 7d77160fe3
commit 96a91fd2f7
8 changed files with 49 additions and 54 deletions

View file

@ -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. // Focused views are rendered on top, so look for them first.
var it = ViewStack(View).iterator(output.views.first, output.current.tags); var it = ViewStack(View).iterator(output.views.first, output.current.tags);
while (it.next()) |node| { 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; if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found;
} }

View file

@ -141,7 +141,7 @@ fn startTransaction(self: *Self) void {
if (view.needsConfigure()) { if (view.needsConfigure()) {
view.configure(); 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 // Send a frame done that the client will commit a new frame
// with the dimensions we sent in the configure. Normally this // with the dimensions we sent in the configure. Normally this

View file

@ -17,6 +17,7 @@
const Self = @This(); const Self = @This();
const build_options = @import("build_options");
const std = @import("std"); const std = @import("std");
const c = @import("c.zig"); const c = @import("c.zig");
@ -182,14 +183,24 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// still clear the focus. // still clear the focus.
if (if (target_wlr_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) { if (if (target_wlr_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) {
// First clear the current focus // 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); c.wlr_seat_keyboard_clear_focus(self.wlr_seat);
// Set the new focus // Set the new focus
switch (new_focus) { switch (new_focus) {
.view => |target_view| { .view => |target_view| {
std.debug.assert(self.focused_output == target_view.output); 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), .layer => |target_layer| std.debug.assert(self.focused_output == target_layer.output),
.none => {}, .none => {},
@ -212,6 +223,9 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// Inform any clients tracking status of the change // Inform any clients tracking status of the change
var it = self.status_trackers.first; var it = self.status_trackers.first;
while (it) |node| : (it = node.next) node.data.sendFocusedView(); 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. /// Focus the given output, notifying any listening clients of the change.

View file

@ -60,6 +60,9 @@ const State = struct {
/// The tags of the view, as a bitmask /// The tags of the view, as a bitmask
tags: u32, tags: u32,
/// Number of seats currently focusing the view
focus: u32,
float: bool, float: bool,
fullscreen: bool, fullscreen: bool,
}; };
@ -79,9 +82,6 @@ output: *Output,
/// This is non-null exactly when the view is mapped /// This is non-null exactly when the view is mapped
wlr_surface: ?*c.wlr_surface, 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 /// The double-buffered state of the view
current: State, current: State,
pending: State, pending: State,
@ -111,8 +111,6 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void {
self.wlr_surface = null; self.wlr_surface = null;
self.focused = false;
self.current = .{ self.current = .{
.box = .{ .box = .{
.x = 0, .x = 0,
@ -121,6 +119,7 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void {
.width = 0, .width = 0,
}, },
.tags = tags, .tags = tags,
.focus = 0,
.float = false, .float = false,
.fullscreen = false, .fullscreen = false,
}; };
@ -155,8 +154,8 @@ pub fn needsConfigure(self: Self) bool {
pub fn configure(self: Self) void { pub fn configure(self: Self) void {
switch (self.impl) { switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(self.pending.box), .xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(),
.xwayland_view => |xwayland_view| xwayland_view.configure(self.pending.box), .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. /// Set the pending state, set the size, and inform the client.
pub fn setFullscreen(self: *Self, fullscreen: bool) void { pub fn setFullscreen(self: *Self, fullscreen: bool) void {
self.pending.fullscreen = fullscreen; self.pending.fullscreen = fullscreen;
@ -358,8 +347,6 @@ pub fn unmap(self: *Self) void {
log.debug(.server, "view '{}' unmapped", .{self.getTitle()}); 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 // Inform all seats that the view has been unmapped so they can handle focus
var it = root.server.input_manager.seats.first; var it = root.server.input_manager.seats.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
@ -367,6 +354,8 @@ pub fn unmap(self: *Self) void {
seat.handleViewUnmap(self); seat.handleViewUnmap(self);
} }
self.wlr_surface = null;
// Remove the view from its output's stack // Remove the view from its output's stack
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
self.output.views.remove(node); self.output.views.remove(node);

View file

@ -28,11 +28,7 @@ pub fn needsConfigure(self: Self) bool {
unreachable; unreachable;
} }
pub fn configure(self: Self, pending_box: Box) void { pub fn configure(self: Self) void {
unreachable;
}
pub fn setActivated(self: Self, activated: bool) void {
unreachable; unreachable;
} }

View file

@ -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 /// Returns true if a configure must be sent to ensure the dimensions of the
/// pending_box are applied. /// pending_box are applied.
pub fn needsConfigure(self: Self) bool { pub fn needsConfigure(self: Self) bool {
const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field( const server_pending = &@field(
self.wlr_xdg_surface, self.wlr_xdg_surface,
c.wlr_xdg_surface_union, 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 // Checking server_pending is sufficient here since it will be either in
// sync with the current dimensions or be the dimensions sent with the // sync with the current dimensions or be the dimensions sent with the
// most recent configure. In both cases server_pending has the values we // most recent configure. In both cases server_pending has the values we
// want to check against. // want to check against.
return self.view.pending.box.width != wlr_xdg_toplevel.server_pending.width or return (state.focus != 0) != server_pending.activated or
self.view.pending.box.height != wlr_xdg_toplevel.server_pending.height; 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. /// Send a configure event, applying the pending state of the view.
pub fn configure(self: Self, pending_box: Box) void { 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.view.pending_serial = c.wlr_xdg_toplevel_set_size(
self.wlr_xdg_surface, self.wlr_xdg_surface,
pending_box.width, state.box.width,
pending_box.height, 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 { pub fn setFullscreen(self: Self, fullscreen: bool) void {
_ = c.wlr_xdg_toplevel_set_fullscreen(self.wlr_xdg_surface, fullscreen); _ = c.wlr_xdg_toplevel_set_fullscreen(self.wlr_xdg_surface, fullscreen);
} }

View file

@ -63,14 +63,15 @@ pub fn needsConfigure(self: Self) bool {
self.wlr_xwayland_surface.height != self.view.pending.box.height; self.wlr_xwayland_surface.height != self.view.pending.box.height;
} }
/// Tell the client to take a new size /// Apply pending state
pub fn configure(self: Self, pending_box: Box) void { pub fn configure(self: Self) void {
const state = &self.view.pending;
c.wlr_xwayland_surface_configure( c.wlr_xwayland_surface_configure(
self.wlr_xwayland_surface, self.wlr_xwayland_surface,
@intCast(i16, pending_box.x), @intCast(i16, state.box.x),
@intCast(i16, pending_box.y), @intCast(i16, state.box.y),
@intCast(u16, pending_box.width), @intCast(u16, state.box.width),
@intCast(u16, pending_box.height), @intCast(u16, state.box.height),
); );
// Xwayland surfaces don't use serials, so we will just assume they have // 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 // 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; 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 { pub fn setFullscreen(self: Self, fullscreen: bool) void {
c.wlr_xwayland_surface_set_fullscreen(self.wlr_xwayland_surface, fullscreen); c.wlr_xwayland_surface_set_fullscreen(self.wlr_xwayland_surface, fullscreen);
} }

View file

@ -85,7 +85,7 @@ pub fn renderOutput(output: *Output) void {
if (view.current.box.width == 0 or view.current.box.height == 0) continue; 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 // 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); renderView(output.*, view, &now);
if (view.draw_borders) renderBorders(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; if (view.current.box.width == 0 or view.current.box.height == 0) continue;
// Skip unfocused views since we already rendered them // Skip unfocused views since we already rendered them
if (!view.focused) continue; if (view.current.focus == 0) continue;
renderView(output.*, view, &now); renderView(output.*, view, &now);
if (view.draw_borders) renderBorders(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 { fn renderBorders(output: Output, view: *View, now: *c.timespec) void {
const config = &output.root.server.config; 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 border_width = config.border_width;
const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box; const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box;