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:
parent
7d77160fe3
commit
96a91fd2f7
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user