aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--river/OutputManager.zig291
-rw-r--r--river/Root.zig252
-rw-r--r--river/Server.zig3
3 files changed, 252 insertions, 294 deletions
diff --git a/river/OutputManager.zig b/river/OutputManager.zig
index 9913b6b..8b13789 100644
--- a/river/OutputManager.zig
+++ b/river/OutputManager.zig
@@ -1,292 +1 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2020 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-const Self = @This();
-
-const build_options = @import("build_options");
-const std = @import("std");
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const log = @import("log.zig");
-const util = @import("util.zig");
-
-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,
-
-new_output: wl.Listener(*wlr.Output) = undefined,
-
-wlr_output_manager: *wlr.OutputManagerV1,
-manager_apply: wl.Listener(*wlr.OutputConfigurationV1) = undefined,
-manager_test: wl.Listener(*wlr.OutputConfigurationV1) = undefined,
-layout_change: wl.Listener(*wlr.OutputLayout) = undefined,
-
-power_manager: *wlr.OutputPowerManagerV1,
-power_manager_set_mode: wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode) = undefined,
-
-pub fn init(self: *Self, server: *Server) !void {
- self.* = .{
- .root = &server.root,
- .wlr_output_manager = try wlr.OutputManagerV1.create(server.wl_server),
- .power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
- };
-
- self.new_output.setNotify(handleNewOutput);
- server.backend.events.new_output.add(&self.new_output);
-
- self.manager_apply.setNotify(handleOutputManagerApply);
- self.wlr_output_manager.events.apply.add(&self.manager_apply);
-
- self.manager_test.setNotify(handleOutputManagerTest);
- self.wlr_output_manager.events.@"test".add(&self.manager_test);
-
- self.layout_change.setNotify(handleOutputLayoutChange);
- self.root.output_layout.events.change.add(&self.layout_change);
-
- self.power_manager_set_mode.setNotify(handleOutputPowerManagementSetMode);
- self.power_manager.events.set_mode.add(&self.power_manager_set_mode);
-
- _ = try wlr.XdgOutputManagerV1.create(server.wl_server, self.root.output_layout);
-}
-
-fn handleNewOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
- const self = @fieldParentPtr(Self, "new_output", listener);
- log.debug(.output_manager, "new output {}", .{wlr_output.name});
-
- const node = util.gpa.create(std.TailQueue(Output).Node) catch {
- wlr_output.destroy();
- return;
- };
- node.data.init(self.root, wlr_output) catch {
- wlr_output.destroy();
- util.gpa.destroy(node);
- return;
- };
- const ptr_node = util.gpa.create(std.TailQueue(*Output).Node) catch {
- wlr_output.destroy();
- util.gpa.destroy(node);
- return;
- };
- ptr_node.data = &node.data;
-
- self.root.all_outputs.append(ptr_node);
- self.root.addOutput(&node.data);
-}
-
-/// Send the new output configuration to all wlr-output-manager clients
-fn handleOutputLayoutChange(
- listener: *wl.Listener(*wlr.OutputLayout),
- output_layout: *wlr.OutputLayout,
-) void {
- const self = @fieldParentPtr(Self, "layout_change", listener);
-
- const config = self.ouputConfigFromCurrent() catch {
- log.crit(.output_manager, "out of memory", .{});
- return;
- };
- self.wlr_output_manager.setConfiguration(config);
-}
-
-fn handleOutputManagerApply(
- listener: *wl.Listener(*wlr.OutputConfigurationV1),
- config: *wlr.OutputConfigurationV1,
-) void {
- const self = @fieldParentPtr(Self, "manager_apply", listener);
- defer config.destroy();
-
- if (self.applyOutputConfig(config)) {
- config.sendSucceeded();
- } else {
- config.sendFailed();
- }
-
- // Send the config that was actually applied
- const applied_config = self.ouputConfigFromCurrent() catch {
- log.crit(.output_manager, "out of memory", .{});
- return;
- };
- self.wlr_output_manager.setConfiguration(applied_config);
-}
-
-fn handleOutputManagerTest(
- listener: *wl.Listener(*wlr.OutputConfigurationV1),
- config: *wlr.OutputConfigurationV1,
-) void {
- const self = @fieldParentPtr(Self, "manager_test", listener);
- defer config.destroy();
-
- if (testOutputConfig(config, true)) {
- config.sendSucceeded();
- } else {
- config.sendFailed();
- }
-}
-
-fn handleOutputPowerManagementSetMode(
- listener: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode),
- event: *wlr.OutputPowerManagerV1.event.SetMode,
-) void {
- const self = @fieldParentPtr(Self, "power_manager_set_mode", listener);
-
- const enable = event.mode == .on;
-
- const log_text = if (enable) "Enabling" else "Disabling";
- log.debug(
- .output_manager,
- "{} dpms for output {}",
- .{ log_text, event.output.name },
- );
-
- event.output.enable(enable);
- event.output.commit() catch
- log.err(.server, "output commit failed for {}", .{event.output.name});
-}
-
-/// Apply the given config, return false on faliure
-fn applyOutputConfig(self: *Self, config: *wlr.OutputConfigurationV1) bool {
- // Ignore layout change events while applying the config
- self.layout_change.link.remove();
- defer self.root.output_layout.events.change.add(&self.layout_change);
-
- // Test if the config should apply cleanly
- 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
- log.err(.output_manager, "output commit failed for {}", .{output.wlr_output.name});
-
- if (output.wlr_output.enabled) {
- // Moves the output if it is already in the layout
- self.root.output_layout.add(output.wlr_output, head.state.x, head.state.y);
- }
-
- if (disable) {
- self.root.removeOutput(output);
- self.root.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();
- 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: *wlr.OutputConfigurationV1, rollback: bool) bool {
- var ok = true;
- var it = config.heads.iterator(.forward);
- while (it.next()) |head| {
- const wlr_output = head.state.output;
-
- 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) {
- 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 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) {
- // 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) |mode| {
- wlr_output.setMode(mode);
- } else {
- log.info(.output_manager, "custom modes are not supported until the next wlroots release: ignoring", .{});
- // TODO(wlroots) uncomment the following lines when wlroots 0.13.0 is released
- // See https://github.com/swaywm/wlroots/pull/2517
- //const custom_mode = &head.state.custom_mode;
- //wlr_output.setCustomMode(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
- wlr_output.setScale(@floatCast(f32, head.state.scale));
- wlr_output.setTransform(head.state.transform);
- }
-}
-
-/// Create the config describing the current configuration
-fn ouputConfigFromCurrent(self: *Self) !*wlr.OutputConfigurationV1 {
- const config = try wlr.OutputConfigurationV1.create();
- // this destroys all associated config heads as well
- errdefer config.destroy();
-
- 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: *wlr.OutputConfigurationV1) !void {
- const wlr_output = output.wlr_output;
- const head = try wlr.OutputConfigurationV1.Head.create(config, wlr_output);
-
- // If the output is not part of the layout (and thus disabled) we dont care
- // about the position
- if (output.root.output_layout.getBox(wlr_output)) |box| {
- head.state.x = box.x;
- head.state.y = box.y;
- }
-}
diff --git a/river/Root.zig b/river/Root.zig
index 270db31..0d42f96 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -32,9 +32,26 @@ const ViewStack = @import("view_stack.zig").ViewStack;
const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
const DragIcon = @import("DragIcon.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;
+
server: *Server,
+new_output: wl.Listener(*wlr.Output) = undefined,
output_layout: *wlr.OutputLayout,
+layout_change: wl.Listener(*wlr.OutputLayout) = undefined,
+
+output_manager: *wlr.OutputManagerV1,
+manager_apply: wl.Listener(*wlr.OutputConfigurationV1) = undefined,
+manager_test: wl.Listener(*wlr.OutputConfigurationV1) = undefined,
+
+power_manager: *wlr.OutputPowerManagerV1,
+power_manager_set_mode: wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode) = undefined,
/// A list of all outputs
all_outputs: std.TailQueue(*Output) = .{},
@@ -66,15 +83,34 @@ pub fn init(self: *Self, server: *Server) !void {
const output_layout = try wlr.OutputLayout.create();
errdefer output_layout.destroy();
+ _ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout);
+
self.* = .{
.server = server,
.output_layout = output_layout,
+ .output_manager = try wlr.OutputManagerV1.create(server.wl_server),
+ .power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
.transaction_timer = try self.server.wl_server.getEventLoop().addTimer(*Self, handleTimeout, self),
.noop_output = undefined,
};
const noop_wlr_output = try server.noop_backend.noopAddOutput();
try self.noop_output.init(self, noop_wlr_output);
+
+ self.new_output.setNotify(handleNewOutput);
+ server.backend.events.new_output.add(&self.new_output);
+
+ self.manager_apply.setNotify(handleOutputManagerApply);
+ self.output_manager.events.apply.add(&self.manager_apply);
+
+ self.manager_test.setNotify(handleOutputManagerTest);
+ self.output_manager.events.@"test".add(&self.manager_test);
+
+ self.layout_change.setNotify(handleOutputLayoutChange);
+ self.output_layout.events.change.add(&self.layout_change);
+
+ self.power_manager_set_mode.setNotify(handleOutputPowerManagementSetMode);
+ self.power_manager.events.set_mode.add(&self.power_manager_set_mode);
}
pub fn deinit(self: *Self) void {
@@ -90,6 +126,30 @@ pub fn deinit(self: *Self) void {
self.transaction_timer.remove();
}
+fn handleNewOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
+ const self = @fieldParentPtr(Self, "new_output", listener);
+ log.debug(.output_manager, "new output {}", .{wlr_output.name});
+
+ const node = util.gpa.create(std.TailQueue(Output).Node) catch {
+ wlr_output.destroy();
+ return;
+ };
+ node.data.init(self, wlr_output) catch {
+ wlr_output.destroy();
+ util.gpa.destroy(node);
+ return;
+ };
+ const ptr_node = util.gpa.create(std.TailQueue(*Output).Node) catch {
+ wlr_output.destroy();
+ util.gpa.destroy(node);
+ return;
+ };
+ ptr_node.data = &node.data;
+
+ self.all_outputs.append(ptr_node);
+ self.addOutput(&node.data);
+}
+
/// Remove the output from self.outputs and evacuate views if it is a member of
/// the list. The node is not freed
pub fn removeOutput(self: *Self, output: *Output) void {
@@ -310,3 +370,195 @@ fn commitTransaction(self: *Self) void {
if (view_tags_changed) output.sendViewTags();
}
}
+
+/// Send the new output configuration to all wlr-output-manager clients
+fn handleOutputLayoutChange(
+ listener: *wl.Listener(*wlr.OutputLayout),
+ output_layout: *wlr.OutputLayout,
+) void {
+ const self = @fieldParentPtr(Self, "layout_change", listener);
+
+ const config = self.ouputConfigFromCurrent() catch {
+ log.crit(.output_manager, "out of memory", .{});
+ return;
+ };
+ self.output_manager.setConfiguration(config);
+}
+
+fn handleOutputManagerApply(
+ listener: *wl.Listener(*wlr.OutputConfigurationV1),
+ config: *wlr.OutputConfigurationV1,
+) void {
+ const self = @fieldParentPtr(Self, "manager_apply", listener);
+ defer config.destroy();
+
+ if (self.applyOutputConfig(config)) {
+ config.sendSucceeded();
+ } else {
+ config.sendFailed();
+ }
+
+ // Send the config that was actually applied
+ const applied_config = self.ouputConfigFromCurrent() catch {
+ log.crit(.output_manager, "out of memory", .{});
+ return;
+ };
+ self.output_manager.setConfiguration(applied_config);
+}
+
+fn handleOutputManagerTest(
+ listener: *wl.Listener(*wlr.OutputConfigurationV1),
+ config: *wlr.OutputConfigurationV1,
+) void {
+ const self = @fieldParentPtr(Self, "manager_test", listener);
+ defer config.destroy();
+
+ if (testOutputConfig(config, true)) {
+ config.sendSucceeded();
+ } else {
+ config.sendFailed();
+ }
+}
+
+/// Apply the given config, return false on faliure
+fn applyOutputConfig(self: *Self, config: *wlr.OutputConfigurationV1) bool {
+ // Ignore layout change events while applying the config
+ self.layout_change.link.remove();
+ defer self.output_layout.events.change.add(&self.layout_change);
+
+ // Test if the config should apply cleanly
+ 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
+ log.err(.output_manager, "output commit failed for {}", .{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();
+ 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);
+ while (it.next()) |head| {
+ const wlr_output = head.state.output;
+
+ 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) {
+ 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 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) {
+ // 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) |mode| {
+ wlr_output.setMode(mode);
+ } else {
+ log.info(.output_manager, "custom modes are not supported until the next wlroots release: ignoring", .{});
+ // TODO(wlroots) uncomment the following lines when wlroots 0.13.0 is released
+ // See https://github.com/swaywm/wlroots/pull/2517
+ //const custom_mode = &head.state.custom_mode;
+ //wlr_output.setCustomMode(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
+ wlr_output.setScale(@floatCast(f32, head.state.scale));
+ wlr_output.setTransform(head.state.transform);
+ }
+}
+
+/// Create the config describing the current configuration
+fn ouputConfigFromCurrent(self: *Self) !*wlr.OutputConfigurationV1 {
+ const config = try wlr.OutputConfigurationV1.create();
+ // this destroys all associated config heads as well
+ errdefer config.destroy();
+
+ var it = self.all_outputs.first;
+ while (it) |node| : (it = node.next) try self.createHead(node.data, config);
+
+ return config;
+}
+
+fn createHead(self: *Self, output: *Output, config: *wlr.OutputConfigurationV1) !void {
+ 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
+ // about the position
+ if (self.output_layout.getBox(output.wlr_output)) |box| {
+ head.state.x = box.x;
+ head.state.y = box.y;
+ }
+}
+
+fn handleOutputPowerManagementSetMode(
+ listener: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode),
+ event: *wlr.OutputPowerManagerV1.event.SetMode,
+) void {
+ const self = @fieldParentPtr(Self, "power_manager_set_mode", listener);
+
+ const enable = event.mode == .on;
+
+ const log_text = if (enable) "Enabling" else "Disabling";
+ log.debug(
+ .output_manager,
+ "{} dpms for output {}",
+ .{ log_text, event.output.name },
+ );
+
+ event.output.enable(enable);
+ event.output.commit() catch
+ log.err(.server, "output commit failed for {}", .{event.output.name});
+}
diff --git a/river/Server.zig b/river/Server.zig
index b4e5a20..bcebe01 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -32,7 +32,6 @@ const DecorationManager = @import("DecorationManager.zig");
const InputManager = @import("InputManager.zig");
const LayerSurface = @import("LayerSurface.zig");
const Output = @import("Output.zig");
-const OutputManager = @import("OutputManager.zig");
const Root = @import("Root.zig");
const StatusManager = @import("StatusManager.zig");
const View = @import("View.zig");
@@ -60,7 +59,6 @@ foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
decoration_manager: DecorationManager,
input_manager: InputManager,
-output_manager: OutputManager,
root: Root,
config: Config,
control: Control,
@@ -117,7 +115,6 @@ pub fn init(self: *Self) !void {
try self.input_manager.init(self);
try self.control.init(self);
try self.status_manager.init(self);
- try self.output_manager.init(self);
// These all free themselves when the wl_server is destroyed
_ = try wlr.DataDeviceManager.create(self.wl_server);