aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--river/Output.zig14
-rw-r--r--river/OutputManager.zig209
-rw-r--r--river/View.zig2
-rw-r--r--river/c.zig1
4 files changed, 223 insertions, 3 deletions
diff --git a/river/Output.zig b/river/Output.zig
index 7e24008..244c1a5 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -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).
diff --git a/river/OutputManager.zig b/river/OutputManager.zig
index 208a2e6..009ef78 100644
--- a/river/OutputManager.zig
+++ b/river/OutputManager.zig
@@ -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;
+ }
+}
diff --git a/river/View.zig b/river/View.zig
index 6992049..f049895 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -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 },
diff --git a/river/c.zig b/river/c.zig
index 26363dc..306ae25 100644
--- a/river/c.zig
+++ b/river/c.zig
@@ -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");