wlr-output-management: simplify implementation
Notably, we no longer call both wlr_output_test and wlr_output_commit when applying an output config, which seems to fix or workaround an occasional crash since updating to wlroots 0.15.0.
This commit is contained in:
parent
745fe82947
commit
1e3ea826c0
@ -217,7 +217,8 @@ pub fn arrangeViews(self: *Self) void {
|
|||||||
|
|
||||||
const ArrangeLayersTarget = enum { mapped, unmapped };
|
const ArrangeLayersTarget = enum { mapped, unmapped };
|
||||||
|
|
||||||
/// Arrange all layer surfaces of this output and adjust the usable area
|
/// Arrange all layer surfaces of this output and adjust the usable area.
|
||||||
|
/// Will arrange views as well if the usable area changes.
|
||||||
/// If target is unmapped, this function is pure aside from the
|
/// If target is unmapped, this function is pure aside from the
|
||||||
/// wlr.LayerSurfaceV1.configure() calls made on umapped layer surfaces.
|
/// wlr.LayerSurfaceV1.configure() calls made on umapped layer surfaces.
|
||||||
pub fn arrangeLayers(self: *Self, target: ArrangeLayersTarget) void {
|
pub fn arrangeLayers(self: *Self, target: ArrangeLayersTarget) void {
|
||||||
|
167
river/Root.zig
167
river/Root.zig
@ -411,7 +411,7 @@ fn commitTransaction(self: *Self) void {
|
|||||||
fn handleLayoutChange(listener: *wl.Listener(*wlr.OutputLayout), _: *wlr.OutputLayout) void {
|
fn handleLayoutChange(listener: *wl.Listener(*wlr.OutputLayout), _: *wlr.OutputLayout) void {
|
||||||
const self = @fieldParentPtr(Self, "layout_change", listener);
|
const self = @fieldParentPtr(Self, "layout_change", listener);
|
||||||
|
|
||||||
const config = self.outputConfigFromCurrent() catch {
|
const config = self.currentOutputConfig() catch {
|
||||||
std.log.scoped(.output_manager).err("out of memory", .{});
|
std.log.scoped(.output_manager).err("out of memory", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -425,14 +425,10 @@ fn handleManagerApply(
|
|||||||
const self = @fieldParentPtr(Self, "manager_apply", listener);
|
const self = @fieldParentPtr(Self, "manager_apply", listener);
|
||||||
defer config.destroy();
|
defer config.destroy();
|
||||||
|
|
||||||
if (self.applyOutputConfig(config)) {
|
self.processOutputConfig(config, .apply);
|
||||||
config.sendSucceeded();
|
|
||||||
} else {
|
|
||||||
config.sendFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the config that was actually applied
|
// Send the config that was actually applied
|
||||||
const applied_config = self.outputConfigFromCurrent() catch {
|
const applied_config = self.currentOutputConfig() catch {
|
||||||
std.log.scoped(.output_manager).err("out of memory", .{});
|
std.log.scoped(.output_manager).err("out of memory", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -440,102 +436,34 @@ fn handleManagerApply(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handleManagerTest(
|
fn handleManagerTest(
|
||||||
_: *wl.Listener(*wlr.OutputConfigurationV1),
|
listener: *wl.Listener(*wlr.OutputConfigurationV1),
|
||||||
config: *wlr.OutputConfigurationV1,
|
config: *wlr.OutputConfigurationV1,
|
||||||
) void {
|
) void {
|
||||||
|
const self = @fieldParentPtr(Self, "manager_test", listener);
|
||||||
defer config.destroy();
|
defer config.destroy();
|
||||||
|
|
||||||
if (testOutputConfig(config, true)) {
|
self.processOutputConfig(config, .test_only);
|
||||||
config.sendSucceeded();
|
|
||||||
} else {
|
|
||||||
config.sendFailed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the given config, return false on faliure
|
fn processOutputConfig(
|
||||||
fn applyOutputConfig(self: *Self, config: *wlr.OutputConfigurationV1) bool {
|
self: *Self,
|
||||||
// Ignore layout change events while applying the config
|
config: *wlr.OutputConfigurationV1,
|
||||||
|
action: enum { test_only, apply },
|
||||||
|
) void {
|
||||||
|
// Ignore layout change events this function generates while applying the config
|
||||||
self.layout_change.link.remove();
|
self.layout_change.link.remove();
|
||||||
defer self.output_layout.events.change.add(&self.layout_change);
|
defer self.output_layout.events.change.add(&self.layout_change);
|
||||||
|
|
||||||
// Test if the config should apply cleanly
|
var success = true;
|
||||||
if (!testOutputConfig(config, false)) return false;
|
|
||||||
|
|
||||||
var it = config.heads.iterator(.forward);
|
|
||||||
while (it.next()) |head| {
|
|
||||||
const output = @intToPtr(*Output, head.state.output.data);
|
|
||||||
const disable = output.wlr_output.enabled and !head.state.enabled;
|
|
||||||
|
|
||||||
// Since we have done a successful test commit, this will only fail
|
|
||||||
// due to error in the output's backend implementation.
|
|
||||||
output.wlr_output.commit() catch
|
|
||||||
std.log.scoped(.output_manager).err("output commit failed for {s}", .{output.wlr_output.name});
|
|
||||||
|
|
||||||
if (output.wlr_output.enabled) {
|
|
||||||
// Moves the output if it is already in the layout
|
|
||||||
self.output_layout.add(output.wlr_output, head.state.x, head.state.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disable) {
|
|
||||||
self.removeOutput(output);
|
|
||||||
self.output_layout.remove(output.wlr_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arrange layers to adjust the usable_box
|
|
||||||
// We dont need to call arrangeViews() since arrangeLayers() will call
|
|
||||||
// it for us because the usable_box changed
|
|
||||||
output.arrangeLayers(.mapped);
|
|
||||||
self.startTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests the output configuration.
|
|
||||||
/// If rollback is false all changes are applied to the pending state of the affected outputs.
|
|
||||||
fn testOutputConfig(config: *wlr.OutputConfigurationV1, rollback: bool) bool {
|
|
||||||
var ok = true;
|
|
||||||
var it = config.heads.iterator(.forward);
|
var it = config.heads.iterator(.forward);
|
||||||
while (it.next()) |head| {
|
while (it.next()) |head| {
|
||||||
const wlr_output = head.state.output;
|
const wlr_output = head.state.output;
|
||||||
|
const output = @intToPtr(*Output, wlr_output.data);
|
||||||
|
|
||||||
const width = if (head.state.mode) |m| m.width else head.state.custom_mode.width;
|
|
||||||
const height = if (head.state.mode) |m| m.height else head.state.custom_mode.height;
|
|
||||||
const scale = head.state.scale;
|
|
||||||
|
|
||||||
const too_small = (@intToFloat(f32, width) / scale < min_size) or
|
|
||||||
(@intToFloat(f32, height) / scale < min_size);
|
|
||||||
|
|
||||||
if (too_small) {
|
|
||||||
std.log.scoped(.output_manager).info(
|
|
||||||
"The requested output resolution {}x{} scaled with {} for {s} would be too small.",
|
|
||||||
.{ width, height, scale, wlr_output.name },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyHeadToOutput(head, wlr_output);
|
|
||||||
ok = ok and !too_small and wlr_output.testCommit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rollback or !ok) {
|
|
||||||
// Rollback all changes
|
|
||||||
it = config.heads.iterator(.forward);
|
|
||||||
while (it.next()) |head| head.state.output.rollback();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn applyHeadToOutput(head: *wlr.OutputConfigurationV1.Head, wlr_output: *wlr.Output) void {
|
|
||||||
wlr_output.enable(head.state.enabled);
|
|
||||||
// The output must be enabled for the following properties to apply
|
|
||||||
if (head.state.enabled) {
|
if (head.state.enabled) {
|
||||||
// TODO(wlroots) Somehow on the drm backend setting the mode causes
|
wlr_output.enable(true);
|
||||||
// the commit in the rendering loop to fail. The commit that
|
|
||||||
// applies the mode works fine.
|
|
||||||
// We can just ignore this because nothing bad happens but it
|
|
||||||
// should be fixed in the future
|
|
||||||
// See https://github.com/swaywm/wlroots/issues/2492
|
|
||||||
if (head.state.mode) |mode| {
|
if (head.state.mode) |mode| {
|
||||||
wlr_output.setMode(mode);
|
wlr_output.setMode(mode);
|
||||||
} else {
|
} else {
|
||||||
@ -544,11 +472,59 @@ fn applyHeadToOutput(head: *wlr.OutputConfigurationV1.Head, wlr_output: *wlr.Out
|
|||||||
}
|
}
|
||||||
wlr_output.setScale(head.state.scale);
|
wlr_output.setScale(head.state.scale);
|
||||||
wlr_output.setTransform(head.state.transform);
|
wlr_output.setTransform(head.state.transform);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
.test_only => {
|
||||||
|
if (!output.wlr_output.testCommit()) success = false;
|
||||||
|
output.wlr_output.rollback();
|
||||||
|
},
|
||||||
|
.apply => {
|
||||||
|
if (output.wlr_output.commit()) {
|
||||||
|
// Just updates the output's position if it is already in the layout
|
||||||
|
self.output_layout.add(output.wlr_output, head.state.x, head.state.y);
|
||||||
|
output.arrangeLayers(.mapped);
|
||||||
|
} else |_| {
|
||||||
|
std.log.scoped(.output_manager).err("failed to apply config to output {s}", .{output.wlr_output.name});
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The output is already disabled, so there's nothing to do
|
||||||
|
if (!wlr_output.enabled) continue;
|
||||||
|
|
||||||
|
wlr_output.enable(false);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
.test_only => {
|
||||||
|
if (!output.wlr_output.testCommit()) success = false;
|
||||||
|
output.wlr_output.rollback();
|
||||||
|
},
|
||||||
|
.apply => {
|
||||||
|
if (output.wlr_output.commit()) {
|
||||||
|
self.removeOutput(output);
|
||||||
|
self.output_layout.remove(output.wlr_output);
|
||||||
|
} else |_| {
|
||||||
|
std.log.scoped(.output_manager).err("failed to apply config to output {s}", .{
|
||||||
|
output.wlr_output.name,
|
||||||
|
});
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the config describing the current configuration
|
if (action == .apply) self.startTransaction();
|
||||||
fn outputConfigFromCurrent(self: *Self) !*wlr.OutputConfigurationV1 {
|
|
||||||
|
if (success) {
|
||||||
|
config.sendSucceeded();
|
||||||
|
} else {
|
||||||
|
config.sendFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn currentOutputConfig(self: *Self) !*wlr.OutputConfigurationV1 {
|
||||||
// TODO there no real reason this needs to allocate memory every time it is called.
|
// TODO there no real reason this needs to allocate memory every time it is called.
|
||||||
// consider improving this wlroots api or reimplementing in zig-wlroots/river.
|
// consider improving this wlroots api or reimplementing in zig-wlroots/river.
|
||||||
const config = try wlr.OutputConfigurationV1.create();
|
const config = try wlr.OutputConfigurationV1.create();
|
||||||
@ -556,12 +532,8 @@ fn outputConfigFromCurrent(self: *Self) !*wlr.OutputConfigurationV1 {
|
|||||||
errdefer config.destroy();
|
errdefer config.destroy();
|
||||||
|
|
||||||
var it = self.all_outputs.first;
|
var it = self.all_outputs.first;
|
||||||
while (it) |node| : (it = node.next) try self.createHead(node.data, config);
|
while (it) |node| : (it = node.next) {
|
||||||
|
const output = node.data;
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn createHead(self: *Self, output: *Output, config: *wlr.OutputConfigurationV1) !void {
|
|
||||||
const head = try wlr.OutputConfigurationV1.Head.create(config, output.wlr_output);
|
const head = try wlr.OutputConfigurationV1.Head.create(config, output.wlr_output);
|
||||||
|
|
||||||
// If the output is not part of the layout (and thus disabled) we dont care
|
// If the output is not part of the layout (and thus disabled) we dont care
|
||||||
@ -572,6 +544,9 @@ fn createHead(self: *Self, output: *Output, config: *wlr.OutputConfigurationV1)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
fn handlePowerManagerSetMode(
|
fn handlePowerManagerSetMode(
|
||||||
_: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode),
|
_: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode),
|
||||||
event: *wlr.OutputPowerManagerV1.event.SetMode,
|
event: *wlr.OutputPowerManagerV1.event.SetMode,
|
||||||
|
Loading…
Reference in New Issue
Block a user