root: merge in OutputManager
These two files were primarily dealing with the same things. Merging them removes some indirection from the code and keeps things simpler.
This commit is contained in:
parent
c51f2176b3
commit
3438c05332
3 changed files with 252 additions and 294 deletions
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
252
river/Root.zig
252
river/Root.zig
|
@ -32,9 +32,26 @@ const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
|
const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
|
||||||
const DragIcon = @import("DragIcon.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,
|
server: *Server,
|
||||||
|
new_output: wl.Listener(*wlr.Output) = undefined,
|
||||||
|
|
||||||
output_layout: *wlr.OutputLayout,
|
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
|
/// A list of all outputs
|
||||||
all_outputs: std.TailQueue(*Output) = .{},
|
all_outputs: std.TailQueue(*Output) = .{},
|
||||||
|
@ -66,15 +83,34 @@ pub fn init(self: *Self, server: *Server) !void {
|
||||||
const output_layout = try wlr.OutputLayout.create();
|
const output_layout = try wlr.OutputLayout.create();
|
||||||
errdefer output_layout.destroy();
|
errdefer output_layout.destroy();
|
||||||
|
|
||||||
|
_ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout);
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.server = server,
|
.server = server,
|
||||||
.output_layout = output_layout,
|
.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),
|
.transaction_timer = try self.server.wl_server.getEventLoop().addTimer(*Self, handleTimeout, self),
|
||||||
.noop_output = undefined,
|
.noop_output = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const noop_wlr_output = try server.noop_backend.noopAddOutput();
|
const noop_wlr_output = try server.noop_backend.noopAddOutput();
|
||||||
try self.noop_output.init(self, noop_wlr_output);
|
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 {
|
pub fn deinit(self: *Self) void {
|
||||||
|
@ -90,6 +126,30 @@ pub fn deinit(self: *Self) void {
|
||||||
self.transaction_timer.remove();
|
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
|
/// Remove the output from self.outputs and evacuate views if it is a member of
|
||||||
/// the list. The node is not freed
|
/// the list. The node is not freed
|
||||||
pub fn removeOutput(self: *Self, output: *Output) void {
|
pub fn removeOutput(self: *Self, output: *Output) void {
|
||||||
|
@ -310,3 +370,195 @@ fn commitTransaction(self: *Self) void {
|
||||||
if (view_tags_changed) output.sendViewTags();
|
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});
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ const DecorationManager = @import("DecorationManager.zig");
|
||||||
const InputManager = @import("InputManager.zig");
|
const InputManager = @import("InputManager.zig");
|
||||||
const LayerSurface = @import("LayerSurface.zig");
|
const LayerSurface = @import("LayerSurface.zig");
|
||||||
const Output = @import("Output.zig");
|
const Output = @import("Output.zig");
|
||||||
const OutputManager = @import("OutputManager.zig");
|
|
||||||
const Root = @import("Root.zig");
|
const Root = @import("Root.zig");
|
||||||
const StatusManager = @import("StatusManager.zig");
|
const StatusManager = @import("StatusManager.zig");
|
||||||
const View = @import("View.zig");
|
const View = @import("View.zig");
|
||||||
|
@ -60,7 +59,6 @@ foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
|
||||||
|
|
||||||
decoration_manager: DecorationManager,
|
decoration_manager: DecorationManager,
|
||||||
input_manager: InputManager,
|
input_manager: InputManager,
|
||||||
output_manager: OutputManager,
|
|
||||||
root: Root,
|
root: Root,
|
||||||
config: Config,
|
config: Config,
|
||||||
control: Control,
|
control: Control,
|
||||||
|
@ -117,7 +115,6 @@ pub fn init(self: *Self) !void {
|
||||||
try self.input_manager.init(self);
|
try self.input_manager.init(self);
|
||||||
try self.control.init(self);
|
try self.control.init(self);
|
||||||
try self.status_manager.init(self);
|
try self.status_manager.init(self);
|
||||||
try self.output_manager.init(self);
|
|
||||||
|
|
||||||
// These all free themselves when the wl_server is destroyed
|
// These all free themselves when the wl_server is destroyed
|
||||||
_ = try wlr.DataDeviceManager.create(self.wl_server);
|
_ = try wlr.DataDeviceManager.create(self.wl_server);
|
||||||
|
|
Loading…
Reference in a new issue