view: send activated/fullscreen configures immediately

The transaction system exists to coordinate size changes of all views
in a layout in order to achieve frame perfection. Since many clients
do not need to commit a new buffer in response to a activated state
change alone, this breaks things when such a configure event is tracked
by the transaction system. Instead, simply send activated and fullscreen
configures right away but still track this state in a double-buffered
way so that e.g. border color changes based on focus are frame-perfect.

This also fixes a related issue with the transaction system where views
that did not need to commit in response to our first configure were not
rendered until their next frame.
This commit is contained in:
Isaac Freund 2021-06-08 04:38:08 +00:00
parent 021fd8f376
commit e90474657f
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
5 changed files with 72 additions and 27 deletions

View file

@ -196,29 +196,30 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// still clear the focus.
if (if (target_surface) |wlr_surface| server.input_manager.inputAllowed(wlr_surface) else true) {
// First clear the current focus
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)
self.focused.view.impl.xwayland_view.xwayland_surface.activate(false);
if (self.focused.view.pending.focus == 0 and !self.focused.view.pending.fullscreen) {
self.focused.view.pending.target_opacity = server.config.opacity.unfocused;
}
switch (self.focused) {
.view => |view| {
view.pending.focus -= 1;
if (view.pending.focus == 0) {
view.setActivated(false);
if (!view.pending.fullscreen) {
view.pending.target_opacity = server.config.opacity.unfocused;
}
}
},
.layer, .none => {},
}
// Set the new focus
switch (new_focus) {
.view => |target_view| {
std.debug.assert(self.focused_output == target_view.output);
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)
target_view.impl.xwayland_view.xwayland_surface.activate(true);
if (!target_view.pending.fullscreen) {
target_view.pending.target_opacity = server.config.opacity.focused;
if (target_view.pending.focus == 0) {
target_view.setActivated(true);
if (!target_view.pending.fullscreen) {
target_view.pending.target_opacity = server.config.opacity.focused;
}
}
target_view.pending.focus += 1;
},
.layer => |target_layer| std.debug.assert(self.focused_output == target_layer.output),
.none => {},

View file

@ -209,6 +209,7 @@ pub fn applyPending(self: *Self) void {
// If switching to fullscreen set the dimensions to the full area of the output
// and turn the view fully opaque
if (!self.current.fullscreen and self.pending.fullscreen) {
self.setFullscreen(true);
self.post_fullscreen_box = self.current.box;
self.pending.target_opacity = 1.0;
@ -222,6 +223,7 @@ pub fn applyPending(self: *Self) void {
}
if (self.current.fullscreen and !self.pending.fullscreen) {
self.setFullscreen(false);
self.pending.box = self.post_fullscreen_box;
// Restore configured opacity
@ -244,10 +246,6 @@ pub fn needsConfigure(self: Self) bool {
}
pub fn configure(self: Self) void {
if (self.foreign_toplevel_handle) |handle| {
handle.setActivated(self.pending.focus != 0);
handle.setFullscreen(self.pending.fullscreen);
}
switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(),
.xwayland_view => |xwayland_view| xwayland_view.configure(),
@ -322,6 +320,23 @@ pub fn close(self: Self) void {
.xwayland_view => |xwayland_view| xwayland_view.close(),
}
}
pub fn setActivated(self: Self, activated: bool) void {
if (self.foreign_toplevel_handle) |handle| handle.setActivated(activated);
switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.setActivated(activated),
.xwayland_view => |xwayland_view| xwayland_view.setActivated(activated),
}
}
pub fn setFullscreen(self: Self, fullscreen: bool) void {
if (self.foreign_toplevel_handle) |handle| handle.setFullscreen(fullscreen);
switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.setFullscreen(fullscreen),
.xwayland_view => |xwayland_view| xwayland_view.setFullscreen(fullscreen),
}
}
pub inline fn forEachPopupSurface(
self: Self,
comptime T: type,

View file

@ -39,6 +39,14 @@ pub fn close(self: Self) void {
unreachable;
}
pub fn setActivated(self: Self, activated: bool) void {
unreachable;
}
pub fn setFullscreen(self: Self, fullscreen: bool) void {
unreachable;
}
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
unreachable;
}

View file

@ -85,8 +85,8 @@ pub fn deinit(self: *Self) void {
}
}
/// Returns true if a configure must be sent to ensure the dimensions of the
/// pending_box are applied.
/// Returns true if a configure must be sent to ensure that the pending
/// dimensions are applied.
pub fn needsConfigure(self: Self) bool {
const server_pending = &self.xdg_surface.role_data.toplevel.server_pending;
const state = &self.view.pending;
@ -95,8 +95,10 @@ pub fn needsConfigure(self: Self) bool {
// 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 (state.focus != 0) != server_pending.activated or
state.box.width != server_pending.width or
// Furthermore, we avoid a special case for newly mapped views which we
// have not yet configured by setting server_pending.width/height to the
// initial width/height of the view in handleMap().
return state.box.width != server_pending.width or
state.box.height != server_pending.height;
}
@ -104,8 +106,6 @@ pub fn needsConfigure(self: Self) bool {
pub fn configure(self: Self) void {
const toplevel = self.xdg_surface.role_data.toplevel;
const state = &self.view.pending;
_ = toplevel.setActivated(state.focus != 0);
_ = toplevel.setFullscreen(state.fullscreen);
self.view.pending_serial = toplevel.setSize(state.box.width, state.box.height);
}
@ -113,6 +113,15 @@ pub fn configure(self: Self) void {
pub fn close(self: Self) void {
self.xdg_surface.role_data.toplevel.sendClose();
}
pub fn setActivated(self: Self, activated: bool) void {
_ = self.xdg_surface.role_data.toplevel.setActivated(activated);
}
pub fn setFullscreen(self: Self, fullscreen: bool) void {
_ = self.xdg_surface.role_data.toplevel.setFullscreen(fullscreen);
}
pub inline fn forEachPopupSurface(
self: Self,
comptime T: type,
@ -189,6 +198,11 @@ fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurfa
view.float_box.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
@intCast(i32, view.float_box.height), 2));
// We initialize these to avoid special-casing newly mapped views in
// the check preformed in needsConfigure().
toplevel.server_pending.width = @intCast(u32, initial_box.width);
toplevel.server_pending.height = @intCast(u32, initial_box.height);
// 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;

View file

@ -84,7 +84,6 @@ pub fn configure(self: Self) void {
const output_box = server.root.output_layout.getBox(output.wlr_output).?;
const state = &self.view.pending;
self.xwayland_surface.setFullscreen(state.fullscreen);
self.xwayland_surface.configure(
@intCast(i16, state.box.x + output_box.x),
@intCast(i16, state.box.y + output_box.y),
@ -98,6 +97,14 @@ pub fn close(self: Self) void {
self.xwayland_surface.close();
}
pub fn setActivated(self: Self, activated: bool) void {
self.xwayland_surface.activate(activated);
}
pub fn setFullscreen(self: Self, fullscreen: bool) void {
self.xwayland_surface.setFullscreen(fullscreen);
}
/// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface.
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {