Implement wlr_output_management_unstable_v1

This commit is contained in:
Marten Ringwelski 2020-11-23 14:58:33 +01:00 committed by Isaac Freund
parent 4b7246685f
commit c3b8986054
4 changed files with 223 additions and 3 deletions

View file

@ -78,6 +78,7 @@ active: bool = false,
// All listeners for this output, in alphabetical order // All listeners for this output, in alphabetical order
listen_destroy: c.wl_listener = undefined, listen_destroy: c.wl_listener = undefined,
listen_enable: c.wl_listener = undefined,
listen_frame: c.wl_listener = undefined, listen_frame: c.wl_listener = undefined,
listen_mode: c.wl_listener = undefined, listen_mode: c.wl_listener = undefined,
@ -108,6 +109,9 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
self.listen_destroy.notify = handleDestroy; self.listen_destroy.notify = handleDestroy;
c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy); c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy);
self.listen_enable.notify = handleEnable;
c.wl_signal_add(&wlr_output.events.enable, &self.listen_enable);
self.listen_frame.notify = handleFrame; self.listen_frame.notify = handleFrame;
c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame); c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame);
@ -523,6 +527,7 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// Remove all listeners // Remove all listeners
c.wl_list_remove(&self.listen_destroy.link); c.wl_list_remove(&self.listen_destroy.link);
c.wl_list_remove(&self.listen_enable.link);
c.wl_list_remove(&self.listen_frame.link); c.wl_list_remove(&self.listen_frame.link);
c.wl_list_remove(&self.listen_mode.link); c.wl_list_remove(&self.listen_mode.link);
@ -534,6 +539,15 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
util.gpa.destroy(node); util.gpa.destroy(node);
} }
fn handleEnable(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_enable", listener.?);
if (self.wlr_output.enabled and !self.active) {
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
self.root.addOutput(node);
}
}
fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// This function is called every time an output is ready to display a frame, // This function is called every time an output is ready to display a frame,
// generally at the output's refresh rate (e.g. 60Hz). // generally at the output's refresh rate (e.g. 60Hz).

View file

@ -28,15 +28,33 @@ const Output = @import("Output.zig");
const Root = @import("Root.zig"); const Root = @import("Root.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");
// Minimum effective width/height for outputs.
// This is needed, to prevent integer overflows caused by the output effective
// resolution beeing too small to fit clients that can't get scaled more and
// thus will be bigger than the output resolution.
// The value is totally arbitrary and low enough, that it should never be
// encountered during normal usage.
const min_size = 50;
root: *Root, root: *Root,
listen_new_output: c.wl_listener = undefined, listen_new_output: c.wl_listener = undefined,
listen_output_layout_change: c.wl_listener = undefined,
wlr_output_manager: *c.wlr_output_manager_v1,
listen_output_manager_apply: c.wl_listener = undefined,
listen_output_manager_test: c.wl_listener = undefined,
wlr_output_power_manager: *c.wlr_output_power_manager_v1, wlr_output_power_manager: *c.wlr_output_power_manager_v1,
listen_output_power_manager_set_mode: c.wl_listener = undefined, listen_output_power_manager_set_mode: c.wl_listener = undefined,
/// True if and only if we are currently applying an output config
output_config_pending: bool = false,
pub fn init(self: *Self, server: *Server) !void { pub fn init(self: *Self, server: *Server) !void {
self.* = .{ self.* = .{
.wlr_output_manager = c.wlr_output_manager_v1_create(server.wl_display) orelse
return error.OutOfMemory,
.wlr_output_power_manager = c.wlr_output_power_manager_v1_create(server.wl_display) orelse .wlr_output_power_manager = c.wlr_output_power_manager_v1_create(server.wl_display) orelse
return error.OutOfMemory, return error.OutOfMemory,
.root = &server.root, .root = &server.root,
@ -45,6 +63,16 @@ pub fn init(self: *Self, server: *Server) !void {
self.listen_new_output.notify = handleNewOutput; self.listen_new_output.notify = handleNewOutput;
c.wl_signal_add(&server.wlr_backend.events.new_output, &self.listen_new_output); c.wl_signal_add(&server.wlr_backend.events.new_output, &self.listen_new_output);
// Set up wlr_output_management
self.listen_output_manager_apply.notify = handleOutputManagerApply;
c.wl_signal_add(&self.wlr_output_manager.events.apply, &self.listen_output_manager_apply);
self.listen_output_manager_test.notify = handleOutputManagerTest;
c.wl_signal_add(&self.wlr_output_manager.events.@"test", &self.listen_output_manager_test);
// Listen for changes in the output layout to send them to the clients of wlr_output_manager
self.listen_output_layout_change.notify = handleOutputLayoutChange;
c.wl_signal_add(&self.root.wlr_output_layout.events.change, &self.listen_output_layout_change);
// Set up output power manager // Set up output power manager
self.listen_output_power_manager_set_mode.notify = handleOutputPowerManagementSetMode; self.listen_output_power_manager_set_mode.notify = handleOutputPowerManagementSetMode;
c.wl_signal_add(&self.wlr_output_power_manager.events.set_mode, &self.listen_output_power_manager_set_mode); c.wl_signal_add(&self.wlr_output_power_manager.events.set_mode, &self.listen_output_power_manager_set_mode);
@ -77,6 +105,50 @@ fn handleNewOutput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void
self.root.addOutput(node); self.root.addOutput(node);
} }
/// Sends the new output configuration to all clients of wlr_output_manager
fn handleOutputLayoutChange(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_output_layout_change", listener.?);
// Dont do anything if the layout change is coming from applying a config
if (self.output_config_pending) return;
const config = self.createOutputConfigurationFromCurrent() catch {
log.err(.output_manager, "Could not create output configuration", .{});
return;
};
c.wlr_output_manager_v1_set_configuration(self.wlr_output_manager, config);
}
fn handleOutputManagerApply(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_output_manager_apply", listener.?);
const config = util.voidCast(c.wlr_output_configuration_v1, data.?);
defer c.wlr_output_configuration_v1_destroy(config);
if (self.applyOutputConfig(config)) {
c.wlr_output_configuration_v1_send_succeeded(config);
} else {
c.wlr_output_configuration_v1_send_failed(config);
}
// Now send the config that actually was applied
const actualConfig = self.createOutputConfigurationFromCurrent() catch {
log.err(.output_manager, "Could not create output configuration", .{});
return;
};
c.wlr_output_manager_v1_set_configuration(self.wlr_output_manager, actualConfig);
}
fn handleOutputManagerTest(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_output_manager_test", listener.?);
const config = util.voidCast(c.wlr_output_configuration_v1, data.?);
defer c.wlr_output_configuration_v1_destroy(config);
if (testOutputConfig(config, true)) {
c.wlr_output_configuration_v1_send_succeeded(config);
} else {
c.wlr_output_configuration_v1_send_failed(config);
}
}
fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_output_power_manager_set_mode", listener.?); const self = @fieldParentPtr(Self, "listen_output_power_manager_set_mode", listener.?);
const mode_event = util.voidCast(c.wlr_output_power_v1_set_mode_event, data.?); const mode_event = util.voidCast(c.wlr_output_power_v1_set_mode_event, data.?);
@ -88,15 +160,148 @@ fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void)
log.debug( log.debug(
.output_manager, .output_manager,
"{} dpms for output {}", "{} dpms for output {}",
.{log_text, wlr_output.name}, .{ log_text, wlr_output.name },
); );
c.wlr_output_enable(wlr_output, enable); c.wlr_output_enable(wlr_output, enable);
if (!c.wlr_output_commit(wlr_output)) { if (!c.wlr_output_commit(wlr_output)) {
log.err( log.err(
.server, .output_manager,
"wlr_output_commit failed for {}", "wlr_output_commit failed for {}",
.{wlr_output.name}, .{wlr_output.name},
); );
} }
} }
/// Applies an output config
fn applyOutputConfig(self: *Self, config: *c.wlr_output_configuration_v1) bool {
// We need to store whether a config is pending because we listen to wlr_output_layout.change
// and this event can be triggered by applying the config
self.output_config_pending = true;
defer self.output_config_pending = false;
// Test if the config should apply cleanly
if (!testOutputConfig(config, false)) return false;
const list_head: *c.wl_list = &config.heads;
var it: *c.wl_list = list_head.next;
while (it != list_head) : (it = it.next) {
const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
const output = util.voidCast(Output, @as(*c.wlr_output, head.state.output).data.?);
const disable = output.wlr_output.enabled and !head.state.enabled;
// This commit will only fail due to runtime errors.
// We choose to ignore this error
if (!c.wlr_output_commit(output.wlr_output)) {
log.err(.output_manager, "wlr_output_commit failed for {}", .{output.wlr_output.name});
}
if (output.wlr_output.enabled) {
// Moves the output if it is already in the layout
c.wlr_output_layout_add(self.root.wlr_output_layout, output.wlr_output, head.state.x, head.state.y);
}
if (disable) {
const node = @fieldParentPtr(std.TailQueue(Output).Node, "data", output);
self.root.removeOutput(node);
c.wlr_output_layout_remove(self.root.wlr_output_layout, 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();
self.root.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: *c.wlr_output_configuration_v1, rollback: bool) bool {
var ok = true;
const list_head: *c.wl_list = &config.heads;
var it: *c.wl_list = list_head.next;
while (it != list_head) : (it = it.next) {
const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
const wlr_output = @as(*c.wlr_output, head.state.output);
const width = if (@as(?*c.wlr_output_mode, head.state.mode)) |m| m.width else head.state.custom_mode.width;
const height = if (@as(?*c.wlr_output_mode, 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) {
log.info(
.output_manager,
"The requested output resolution {}x{} scaled with {} for {} would be too small.",
.{ width, height, scale, wlr_output.name },
);
}
applyHeadToOutput(head, wlr_output);
ok = ok and !too_small and c.wlr_output_test(wlr_output);
}
if (rollback or !ok) {
// Rollback all changes
it = list_head.next;
while (it != list_head) : (it = it.next) {
const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
const wlr_output = @as(*c.wlr_output, head.state.output);
c.wlr_output_rollback(wlr_output);
}
}
return ok;
}
fn applyHeadToOutput(head: *c.wlr_output_configuration_head_v1, wlr_output: *c.wlr_output) void {
c.wlr_output_enable(wlr_output, head.state.enabled);
// The output must be enabled for the following properties to apply
if (head.state.enabled) {
// TODO(wlroots) Somehow on the drm backend setting the mode causes
// 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 != null) {
c.wlr_output_set_mode(wlr_output, head.state.mode);
} else {
const custom_mode = &head.state.custom_mode;
c.wlr_output_set_custom_mode(wlr_output, custom_mode.width, custom_mode.height, custom_mode.refresh);
}
// TODO(wlroots) Figure out if this conversion is needed or if that is a bug in wlroots
c.wlr_output_set_scale(wlr_output, @floatCast(f32, head.state.scale));
c.wlr_output_set_transform(wlr_output, head.state.transform);
}
}
/// Creates an wlr_output_configuration from the current configuration
fn createOutputConfigurationFromCurrent(self: *Self) !*c.wlr_output_configuration_v1 {
var config = c.wlr_output_configuration_v1_create() orelse return error.OutOfMemory;
errdefer c.wlr_output_configuration_v1_destroy(config);
var it = self.root.all_outputs.first;
while (it) |node| : (it = node.next) {
try self.createHead(node.data, config);
}
return config;
}
fn createHead(self: *Self, output: *Output, config: *c.wlr_output_configuration_v1) !void {
const wlr_output = output.wlr_output;
const head: *c.wlr_output_configuration_head_v1 = c.wlr_output_configuration_head_v1_create(config, wlr_output) orelse
return error.OutOfMemory;
// If the output is not part of the layout (and thus disabled) we dont care about the position
const box = @as(?*c.wlr_box, c.wlr_output_layout_get_box(self.root.wlr_output_layout, wlr_output));
if (box) |b| {
head.state.x = b.x;
head.state.y = b.y;
}
}

View file

@ -52,7 +52,7 @@ const Impl = union(enum) {
}; };
const State = struct { const State = struct {
/// The output-relative coordinates and dimensions of the view. The /// The output-relative effective coordinates and effective dimensions of the view. The
/// surface itself may have other dimensions which are stored in the /// surface itself may have other dimensions which are stored in the
/// surface_box member. /// surface_box member.
box: Box = Box{ .x = 0, .y = 0, .width = 0, .height = 0 }, box: Box = Box{ .x = 0, .y = 0, .width = 0, .height = 0 },

View file

@ -46,6 +46,7 @@ pub usingnamespace @cImport({
@cInclude("wlr/types/wlr_matrix.h"); @cInclude("wlr/types/wlr_matrix.h");
@cInclude("wlr/types/wlr_output.h"); @cInclude("wlr/types/wlr_output.h");
@cInclude("wlr/types/wlr_output_layout.h"); @cInclude("wlr/types/wlr_output_layout.h");
@cInclude("wlr/types/wlr_output_management_v1.h");
@cInclude("wlr/types/wlr_output_power_management_v1.h"); @cInclude("wlr/types/wlr_output_power_management_v1.h");
@cInclude("wlr/types/wlr_pointer.h"); @cInclude("wlr/types/wlr_pointer.h");
@cInclude("wlr/types/wlr_primary_selection.h"); @cInclude("wlr/types/wlr_primary_selection.h");