tearing-control-v1: implement
Implement the wp-tearing-control-v1 protocol allowing window to hint the compositor that they prefer async "tearing" page flips. Add tearing/no-tearing rules to allow the user to manually enabled/disable tearing for a window. Use async "tearing" page flips when a window that should be allowed to tear is fullscreen. This still requires several kernel patches to work with the wlroots atomic DRM backend. For now, either set WLR_DRM_NO_ATOMIC=1 or use a custom kernel that includes the unmerged patches (such as CachyOS). Closes: https://codeberg.org/river/river/issues/1094
This commit is contained in:
parent
db7de8151c
commit
066baa5753
@ -94,6 +94,7 @@ pub fn build(b: *Build) !void {
|
||||
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
|
||||
scanner.addSystemProtocol("staging/cursor-shape/cursor-shape-v1.xml");
|
||||
scanner.addSystemProtocol("staging/ext-session-lock/ext-session-lock-v1.xml");
|
||||
scanner.addSystemProtocol("staging/tearing-control/tearing-control-v1.xml");
|
||||
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
|
||||
scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
|
||||
scanner.addSystemProtocol("unstable/tablet/tablet-unstable-v2.xml");
|
||||
@ -124,6 +125,7 @@ pub fn build(b: *Build) !void {
|
||||
scanner.generate("zxdg_decoration_manager_v1", 1);
|
||||
scanner.generate("ext_session_lock_manager_v1", 1);
|
||||
scanner.generate("wp_cursor_shape_manager_v1", 1);
|
||||
scanner.generate("wp_tearing_control_manager_v1", 1);
|
||||
|
||||
scanner.generate("zriver_control_v1", 1);
|
||||
scanner.generate("zriver_status_manager_v1", 4);
|
||||
|
@ -12,8 +12,8 @@
|
||||
.hash = "1220687c8c47a48ba285d26a05600f8700d37fc637e223ced3aa8324f3650bf52242",
|
||||
},
|
||||
.@"zig-wlroots" = .{
|
||||
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/ae6151f22ceb4ccd7efb1291dea573785918a7ec.tar.gz",
|
||||
.hash = "12204d99aebfbf88f1ff3ab197362937b3d4bef4f45fde9c4ee0d569e095a2a25889",
|
||||
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/e486223799648d27e8b91c5fe0ea4c088b74b707.tar.gz",
|
||||
.hash = "1220aeb3317e16c38583839961c9d695fa60d23a3d506c8275fb0e8fa9849844f2f7",
|
||||
},
|
||||
.@"zig-xkbcommon" = .{
|
||||
.url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.2.0.tar.gz",
|
||||
|
@ -307,11 +307,17 @@ matches everything while _\*\*_ and the empty string are invalid.
|
||||
- *fullscreen*: Make the view fullscreen. Applies only to new views.
|
||||
- *no-fullscreen*: Don't make the view fullscreen. Applies only to
|
||||
new views.
|
||||
- *tearing*: Enable tearing for view when fullscreen regardless of the
|
||||
supplied tearing hint. Note that tearing additionally requires setting the
|
||||
*allow-tearing* server option. Applies to new and existing views.
|
||||
- *no-tearing*: Disable tearing for view regardless of the supplied
|
||||
tearing hint. Applies to new and existing views.
|
||||
|
||||
Both *float* and *no-float* rules are added to the same list,
|
||||
which means that adding a *no-float* rule with the same arguments
|
||||
as a *float* rule will overwrite it. The same holds for *ssd* and
|
||||
*csd*, *fullscreen* and *no-fullscreen* rules.
|
||||
*csd*, *fullscreen* and *no-fullscreen*, *tearing* and
|
||||
*no-tearing* rules.
|
||||
|
||||
If multiple rules in a list match a given view the most specific
|
||||
rule will be applied. For example with the following rules
|
||||
@ -364,6 +370,9 @@ matches everything while _\*\*_ and the empty string are invalid.
|
||||
Set the attach mode of the currently focused output, overriding the value of
|
||||
default-attach-mode if any.
|
||||
|
||||
*allow-tearing* *enabled*|*disabled*
|
||||
Allow windows to tear if requested by either the program or the user.
|
||||
|
||||
*background-color* _0xRRGGBB_|_0xRRGGBBAA_
|
||||
Set the background color.
|
||||
|
||||
|
@ -31,6 +31,11 @@ const Mode = @import("Mode.zig");
|
||||
const RuleList = @import("rule_list.zig").RuleList;
|
||||
const View = @import("View.zig");
|
||||
|
||||
pub const AllowTearing = enum {
|
||||
disabled,
|
||||
enabled,
|
||||
};
|
||||
|
||||
pub const AttachMode = union(enum) {
|
||||
top,
|
||||
bottom,
|
||||
@ -68,6 +73,9 @@ pub const Dimensions = struct {
|
||||
height: u31,
|
||||
};
|
||||
|
||||
/// Whether to allow tearing page flips if a view requests it.
|
||||
allow_tearing: AllowTearing = .disabled,
|
||||
|
||||
/// Color of background in RGBA with premultiplied alpha (alpha should only affect nested sessions)
|
||||
background_color: [4]f32 = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }, // Solarized base03
|
||||
|
||||
@ -98,6 +106,7 @@ rules: struct {
|
||||
position: RuleList(Position) = .{},
|
||||
dimensions: RuleList(Dimensions) = .{},
|
||||
fullscreen: RuleList(bool) = .{},
|
||||
tearing: RuleList(bool) = .{},
|
||||
} = .{},
|
||||
|
||||
/// The selected focus_follows_cursor mode
|
||||
|
@ -538,10 +538,12 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
|
||||
}
|
||||
|
||||
fn renderAndCommit(output: *Output, scene_output: *wlr.SceneOutput) !void {
|
||||
if (output.gamma_dirty) {
|
||||
var state = wlr.Output.State.init();
|
||||
defer state.finish();
|
||||
|
||||
if (!scene_output.buildState(&state, null)) return error.CommitFailed;
|
||||
|
||||
if (output.gamma_dirty) {
|
||||
const control = server.root.gamma_control_manager.getControl(output.wlr_output);
|
||||
if (!wlr.GammaControlV1.apply(control, &state)) return error.OutOfMemory;
|
||||
|
||||
@ -553,15 +555,20 @@ fn renderAndCommit(output: *Output, scene_output: *wlr.SceneOutput) !void {
|
||||
// has a null LUT. The wayland backend for example has this behavior.
|
||||
state.committed.gamma_lut = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!scene_output.buildState(&state, null)) return error.CommitFailed;
|
||||
if (output.allowTearing() and server.config.allow_tearing == .enabled) {
|
||||
state.tearing_page_flip = true;
|
||||
|
||||
if (!output.wlr_output.testState(&state)) {
|
||||
log.debug("tearing page flip test failed for {s}, retrying without tearing", .{output.wlr_output.name});
|
||||
state.tearing_page_flip = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!output.wlr_output.commitState(&state)) return error.CommitFailed;
|
||||
|
||||
output.gamma_dirty = false;
|
||||
} else {
|
||||
if (!scene_output.commit(null)) return error.CommitFailed;
|
||||
}
|
||||
if (output.gamma_dirty) output.gamma_dirty = false;
|
||||
|
||||
if (server.lock_manager.state == .locked or
|
||||
(server.lock_manager.state == .waiting_for_lock_surfaces and output.locked_content.node.enabled) or
|
||||
@ -635,6 +642,14 @@ fn setTitle(output: Output) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn allowTearing(output: *Output) bool {
|
||||
if (output.current.fullscreen) |fullscreen_view| {
|
||||
return fullscreen_view.allowTearing();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn handleLayoutNamespaceChange(output: *Output) void {
|
||||
// The user changed the layout namespace of this output. Try to find a
|
||||
// matching layout.
|
||||
|
@ -84,6 +84,8 @@ screencopy_manager: *wlr.ScreencopyManagerV1,
|
||||
|
||||
foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
|
||||
|
||||
tearing_control_manager: *wlr.TearingControlManagerV1,
|
||||
|
||||
input_manager: InputManager,
|
||||
root: Root,
|
||||
config: Config,
|
||||
@ -159,6 +161,8 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
|
||||
|
||||
.foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(wl_server),
|
||||
|
||||
.tearing_control_manager = try wlr.TearingControlManagerV1.create(wl_server, 1),
|
||||
|
||||
.config = try Config.init(),
|
||||
|
||||
.root = undefined,
|
||||
|
@ -23,6 +23,7 @@ const math = std.math;
|
||||
const posix = std.posix;
|
||||
const wlr = @import("wlroots");
|
||||
const wl = @import("wayland").server.wl;
|
||||
const wp = @import("wayland").server.wp;
|
||||
|
||||
const server = &@import("main.zig").server;
|
||||
const util = @import("util.zig");
|
||||
@ -59,6 +60,12 @@ const AttachRelativeMode = enum {
|
||||
below,
|
||||
};
|
||||
|
||||
const TearingMode = enum {
|
||||
override_false,
|
||||
override_true,
|
||||
window_hint,
|
||||
};
|
||||
|
||||
pub const State = struct {
|
||||
/// The output the view is currently assigned to.
|
||||
/// May be null if there are no outputs or for newly created views.
|
||||
@ -177,6 +184,9 @@ foreign_toplevel_handle: ForeignToplevelHandle = .{},
|
||||
/// Connector name of the output this view occupied before an evacuation.
|
||||
output_before_evac: ?[]const u8 = null,
|
||||
|
||||
// Current tearing mode of the view. Defaults to using the window tearing hint.
|
||||
tearing_mode: TearingMode = .window_hint,
|
||||
|
||||
pub fn create(impl: Impl) error{OutOfMemory}!*View {
|
||||
assert(impl != .none);
|
||||
|
||||
@ -572,6 +582,17 @@ pub fn getAppId(view: View) ?[*:0]const u8 {
|
||||
};
|
||||
}
|
||||
|
||||
/// Return true if the view can currently tear.
|
||||
pub fn allowTearing(view: *View) bool {
|
||||
switch (view.tearing_mode) {
|
||||
TearingMode.override_false => return false,
|
||||
TearingMode.override_true => return true,
|
||||
TearingMode.window_hint => if (view.rootSurface()) |root_surface| {
|
||||
return server.tearing_control_manager.hintFromSurface(root_surface) == .@"async";
|
||||
} else return false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clamp the width/height of the box to the constraints of the view
|
||||
pub fn applyConstraints(view: *View, box: *wlr.Box) void {
|
||||
box.width = math.clamp(box.width, view.constraints.min_width, view.constraints.max_width);
|
||||
@ -640,6 +661,10 @@ pub fn map(view: *View) !void {
|
||||
view.pending.ssd = ssd;
|
||||
}
|
||||
|
||||
if (server.config.rules.tearing.match(view)) |tearing| {
|
||||
view.tearing_mode = if (tearing) .override_true else .override_false;
|
||||
}
|
||||
|
||||
if (server.config.rules.dimensions.match(view)) |dimensions| {
|
||||
view.pending.box.width = dimensions.width;
|
||||
view.pending.box.height = dimensions.height;
|
||||
|
@ -41,6 +41,7 @@ const command_impls = std.StaticStringMap(
|
||||
).initComptime(
|
||||
.{
|
||||
// zig fmt: off
|
||||
.{ "allow-tearing", @import("command/config.zig").allowTearing },
|
||||
.{ "attach-mode", @import("command/attach_mode.zig").defaultAttachMode },
|
||||
.{ "background-color", @import("command/config.zig").backgroundColor },
|
||||
.{ "border-color-focused", @import("command/config.zig").borderColorFocused },
|
||||
|
@ -24,6 +24,17 @@ const Error = @import("../command.zig").Error;
|
||||
const Seat = @import("../Seat.zig");
|
||||
const Config = @import("../Config.zig");
|
||||
|
||||
pub fn allowTearing(
|
||||
_: *Seat,
|
||||
args: []const [:0]const u8,
|
||||
_: *?[]const u8,
|
||||
) Error!void {
|
||||
if (args.len < 2) return Error.NotEnoughArguments;
|
||||
if (args.len > 2) return Error.TooManyArguments;
|
||||
server.config.allow_tearing = std.meta.stringToEnum(Config.AllowTearing, args[1]) orelse
|
||||
return Error.UnknownOption;
|
||||
}
|
||||
|
||||
pub fn borderWidth(
|
||||
_: *Seat,
|
||||
args: []const [:0]const u8,
|
||||
|
@ -38,6 +38,8 @@ const Action = enum {
|
||||
dimensions,
|
||||
fullscreen,
|
||||
@"no-fullscreen",
|
||||
tearing,
|
||||
@"no-tearing",
|
||||
};
|
||||
|
||||
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
|
||||
@ -53,7 +55,7 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
||||
const action = std.meta.stringToEnum(Action, result.args[0]) orelse return Error.UnknownOption;
|
||||
|
||||
const positional_arguments_count: u8 = switch (action) {
|
||||
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen" => 1,
|
||||
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing" => 1,
|
||||
.tags, .output => 2,
|
||||
.position, .dimensions => 3,
|
||||
};
|
||||
@ -83,6 +85,14 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
||||
apply_ssd_rules();
|
||||
server.root.applyPending();
|
||||
},
|
||||
.tearing, .@"no-tearing" => {
|
||||
try server.config.rules.tearing.add(.{
|
||||
.app_id_glob = app_id_glob,
|
||||
.title_glob = title_glob,
|
||||
.value = (action == .tearing),
|
||||
});
|
||||
apply_tearing_rules();
|
||||
},
|
||||
.tags => {
|
||||
const tags = try fmt.parseInt(u32, result.args[1], 10);
|
||||
try server.config.rules.tags.add(.{
|
||||
@ -177,6 +187,10 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
|
||||
.fullscreen, .@"no-fullscreen" => {
|
||||
_ = server.config.rules.fullscreen.del(rule);
|
||||
},
|
||||
.tearing, .@"no-tearing" => {
|
||||
_ = server.config.rules.tearing.del(rule);
|
||||
apply_tearing_rules();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,6 +205,17 @@ fn apply_ssd_rules() void {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_tearing_rules() void {
|
||||
var it = server.root.views.iterator(.forward);
|
||||
while (it.next()) |view| {
|
||||
if (view.destroying) continue;
|
||||
|
||||
if (server.config.rules.tearing.match(view)) |tearing| {
|
||||
view.tearing_mode = if (tearing) .override_true else .override_false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!void {
|
||||
if (args.len < 2) return error.NotEnoughArguments;
|
||||
if (args.len > 2) return error.TooManyArguments;
|
||||
@ -203,6 +228,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
|
||||
position,
|
||||
dimensions,
|
||||
fullscreen,
|
||||
tearing,
|
||||
}, args[1]) orelse return Error.UnknownOption;
|
||||
const max_glob_len = switch (rule_list) {
|
||||
inline else => |list| @field(server.config.rules, @tagName(list)).getMaxGlobLen(),
|
||||
@ -218,12 +244,13 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
|
||||
try writer.writeAll("action\n");
|
||||
|
||||
switch (rule_list) {
|
||||
inline .float, .ssd, .output, .fullscreen => |list| {
|
||||
inline .float, .ssd, .output, .fullscreen, .tearing => |list| {
|
||||
const rules = switch (list) {
|
||||
.float => server.config.rules.float.rules.items,
|
||||
.ssd => server.config.rules.ssd.rules.items,
|
||||
.output => server.config.rules.output.rules.items,
|
||||
.fullscreen => server.config.rules.fullscreen.rules.items,
|
||||
.tearing => server.config.rules.tearing.rules.items,
|
||||
else => unreachable,
|
||||
};
|
||||
for (rules) |rule| {
|
||||
@ -234,6 +261,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
|
||||
.ssd => if (rule.value) "ssd" else "csd",
|
||||
.output => rule.value,
|
||||
.fullscreen => if (rule.value) "fullscreen" else "no-fullscreen",
|
||||
.tearing => if (rule.value) "tearing" else "no-tearing",
|
||||
else => unreachable,
|
||||
}});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user