Implement wlr_output_management_unstable_v1
This commit is contained in:
parent
4b7246685f
commit
c3b8986054
4 changed files with 223 additions and 3 deletions
|
@ -78,6 +78,7 @@ active: bool = false,
|
|||
|
||||
// All listeners for this output, in alphabetical order
|
||||
listen_destroy: c.wl_listener = undefined,
|
||||
listen_enable: c.wl_listener = undefined,
|
||||
listen_frame: 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;
|
||||
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;
|
||||
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
|
||||
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_mode.link);
|
||||
|
||||
|
@ -534,6 +539,15 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|||
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 {
|
||||
// This function is called every time an output is ready to display a frame,
|
||||
// generally at the output's refresh rate (e.g. 60Hz).
|
||||
|
|
|
@ -28,15 +28,33 @@ const Output = @import("Output.zig");
|
|||
const Root = @import("Root.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,
|
||||
|
||||
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,
|
||||
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 {
|
||||
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
|
||||
return error.OutOfMemory,
|
||||
.root = &server.root,
|
||||
|
@ -45,6 +63,16 @@ pub fn init(self: *Self, server: *Server) !void {
|
|||
self.listen_new_output.notify = handleNewOutput;
|
||||
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
|
||||
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);
|
||||
|
@ -77,6 +105,50 @@ fn handleNewOutput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void
|
|||
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 {
|
||||
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.?);
|
||||
|
@ -88,15 +160,148 @@ fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void)
|
|||
log.debug(
|
||||
.output_manager,
|
||||
"{} dpms for output {}",
|
||||
.{log_text, wlr_output.name},
|
||||
.{ log_text, wlr_output.name },
|
||||
);
|
||||
|
||||
c.wlr_output_enable(wlr_output, enable);
|
||||
if (!c.wlr_output_commit(wlr_output)) {
|
||||
log.err(
|
||||
.server,
|
||||
.output_manager,
|
||||
"wlr_output_commit failed for {}",
|
||||
.{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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ const Impl = union(enum) {
|
|||
};
|
||||
|
||||
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_box member.
|
||||
box: Box = Box{ .x = 0, .y = 0, .width = 0, .height = 0 },
|
||||
|
|
|
@ -46,6 +46,7 @@ pub usingnamespace @cImport({
|
|||
@cInclude("wlr/types/wlr_matrix.h");
|
||||
@cInclude("wlr/types/wlr_output.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_pointer.h");
|
||||
@cInclude("wlr/types/wlr_primary_selection.h");
|
||||
|
|
Loading…
Reference in a new issue