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
8 changed files with 49 additions and 54 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue