2020-05-02 10:21:10 -07:00
|
|
|
// This file is part of river, a dynamic tiling wayland compositor.
|
|
|
|
//
|
2020-11-11 11:30:21 -08:00
|
|
|
// Copyright 2020 The River Developers
|
2020-05-02 10:21:10 -07:00
|
|
|
//
|
|
|
|
// 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
|
2022-01-31 10:33:22 -08:00
|
|
|
// the Free Software Foundation, version 3.
|
2020-05-02 10:21:10 -07:00
|
|
|
//
|
|
|
|
// 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/>.
|
|
|
|
|
2020-05-02 07:44:46 -07:00
|
|
|
const Self = @This();
|
|
|
|
|
2020-05-11 04:46:29 -07:00
|
|
|
const build_options = @import("build_options");
|
2020-11-03 15:23:21 -08:00
|
|
|
const std = @import("std");
|
2020-10-02 06:53:08 -07:00
|
|
|
const assert = std.debug.assert;
|
2023-01-24 04:55:40 -08:00
|
|
|
const mem = std.mem;
|
2020-11-03 15:23:21 -08:00
|
|
|
const wlr = @import("wlroots");
|
|
|
|
const wl = @import("wayland").server.wl;
|
2023-02-11 03:23:07 -08:00
|
|
|
const zwlr = @import("wayland").server.zwlr;
|
2020-05-02 07:44:46 -07:00
|
|
|
|
2021-05-13 05:26:27 -07:00
|
|
|
const server = &@import("main.zig").server;
|
2020-06-16 11:54:05 -07:00
|
|
|
const util = @import("util.zig");
|
2020-03-25 07:59:24 -07:00
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
const DragIcon = @import("DragIcon.zig");
|
|
|
|
const LayerSurface = @import("LayerSurface.zig");
|
|
|
|
const LockSurface = @import("LockSurface.zig");
|
2020-05-02 14:11:56 -07:00
|
|
|
const Output = @import("Output.zig");
|
2023-01-29 03:03:41 -08:00
|
|
|
const SceneNodeData = @import("SceneNodeData.zig");
|
2020-05-02 14:11:56 -07:00
|
|
|
const View = @import("View.zig");
|
2022-05-29 16:08:09 -07:00
|
|
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
2020-03-25 07:59:24 -07:00
|
|
|
|
2023-01-27 13:09:35 -08:00
|
|
|
scene: *wlr.Scene,
|
2023-02-20 09:01:24 -08:00
|
|
|
/// All windows, status bars, drowdown menus, etc. that can recieve pointer events and similar.
|
|
|
|
interactive_content: *wlr.SceneTree,
|
|
|
|
/// Drag icons, which cannot recieve e.g. pointer events and are therefore kept in a separate tree.
|
|
|
|
drag_icons: *wlr.SceneTree,
|
|
|
|
|
|
|
|
/// All direct children of the interactive_content scene node
|
2023-02-16 07:54:53 -08:00
|
|
|
layers: struct {
|
|
|
|
/// Parent tree for output trees which have their position updated when
|
|
|
|
/// outputs are moved in the layout.
|
|
|
|
outputs: *wlr.SceneTree,
|
2023-02-20 09:01:24 -08:00
|
|
|
/// Xwayland override redirect windows are a legacy wart that decide where
|
|
|
|
/// to place themselves in layout coordinates. Unfortunately this is how
|
|
|
|
/// X11 decided to make dropdown menus and the like possible.
|
|
|
|
xwayland_override_redirect: if (build_options.xwayland) *wlr.SceneTree else void,
|
2023-02-16 07:54:53 -08:00
|
|
|
},
|
2023-01-27 13:09:35 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
/// This is kind of like an imaginary output where views start and end their life.
|
|
|
|
/// It is also used to store views and tags when no actual outputs are available.
|
|
|
|
hidden: struct {
|
|
|
|
/// This tree is always disabled.
|
|
|
|
tree: *wlr.SceneTree,
|
|
|
|
|
|
|
|
tags: u32 = 1 << 0,
|
|
|
|
|
|
|
|
pending: struct {
|
|
|
|
focus_stack: wl.list.Head(View, .pending_focus_stack_link),
|
|
|
|
wm_stack: wl.list.Head(View, .pending_wm_stack_link),
|
|
|
|
},
|
|
|
|
|
|
|
|
inflight: struct {
|
|
|
|
focus_stack: wl.list.Head(View, .inflight_focus_stack_link),
|
|
|
|
wm_stack: wl.list.Head(View, .inflight_wm_stack_link),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-12-31 06:35:35 -08:00
|
|
|
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
|
2020-03-25 07:59:24 -07:00
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
output_layout: *wlr.OutputLayout,
|
2020-12-31 06:35:35 -08:00
|
|
|
layout_change: wl.Listener(*wlr.OutputLayout) = wl.Listener(*wlr.OutputLayout).init(handleLayoutChange),
|
2020-12-27 06:24:20 -08:00
|
|
|
|
|
|
|
output_manager: *wlr.OutputManagerV1,
|
2020-12-31 06:35:35 -08:00
|
|
|
manager_apply: wl.Listener(*wlr.OutputConfigurationV1) =
|
|
|
|
wl.Listener(*wlr.OutputConfigurationV1).init(handleManagerApply),
|
|
|
|
manager_test: wl.Listener(*wlr.OutputConfigurationV1) =
|
|
|
|
wl.Listener(*wlr.OutputConfigurationV1).init(handleManagerTest),
|
2020-12-27 06:24:20 -08:00
|
|
|
|
|
|
|
power_manager: *wlr.OutputPowerManagerV1,
|
2020-12-31 06:35:35 -08:00
|
|
|
power_manager_set_mode: wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode) =
|
|
|
|
wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode).init(handlePowerManagerSetMode),
|
2020-11-16 00:14:50 -08:00
|
|
|
|
|
|
|
/// A list of all outputs
|
|
|
|
all_outputs: std.TailQueue(*Output) = .{},
|
|
|
|
|
|
|
|
/// A list of all active outputs. See Output.active
|
2020-08-31 06:23:01 -07:00
|
|
|
outputs: std.TailQueue(Output) = .{},
|
2020-03-25 07:59:24 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
/// Number of layout demands before sending configures to clients.
|
|
|
|
inflight_layout_demands: u32 = 0,
|
|
|
|
/// Number of inflight configures sent in the current transaction.
|
|
|
|
inflight_configures: u32 = 0,
|
|
|
|
transaction_timeout: *wl.EventSource,
|
|
|
|
/// Set to true if applyPending() is called while a transaction is inflight.
|
|
|
|
/// If true when a transaction completes will cause applyPending() to be called again.
|
|
|
|
pending_state_dirty: bool = false,
|
2020-03-25 07:59:24 -07:00
|
|
|
|
2021-05-13 05:26:27 -07:00
|
|
|
pub fn init(self: *Self) !void {
|
2020-11-03 15:23:21 -08:00
|
|
|
const output_layout = try wlr.OutputLayout.create();
|
|
|
|
errdefer output_layout.destroy();
|
|
|
|
|
2023-01-27 13:09:35 -08:00
|
|
|
const scene = try wlr.Scene.create();
|
|
|
|
errdefer scene.tree.node.destroy();
|
|
|
|
|
2023-02-20 09:01:24 -08:00
|
|
|
const interactive_content = try scene.tree.createSceneTree();
|
|
|
|
const drag_icons = try scene.tree.createSceneTree();
|
2023-02-24 10:28:37 -08:00
|
|
|
const hidden_tree = try scene.tree.createSceneTree();
|
|
|
|
hidden_tree.node.setEnabled(false);
|
2023-02-20 09:01:24 -08:00
|
|
|
|
|
|
|
const outputs = try interactive_content.createSceneTree();
|
|
|
|
const xwayland_override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree();
|
|
|
|
|
2023-01-27 13:09:35 -08:00
|
|
|
try scene.attachOutputLayout(output_layout);
|
|
|
|
|
2020-12-27 06:24:20 -08:00
|
|
|
_ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout);
|
|
|
|
|
2020-10-02 06:53:08 -07:00
|
|
|
const event_loop = server.wl_server.getEventLoop();
|
2023-02-24 10:28:37 -08:00
|
|
|
const transaction_timeout = try event_loop.addTimer(*Self, handleTransactionTimeout, self);
|
|
|
|
errdefer transaction_timeout.remove();
|
2023-02-16 07:54:53 -08:00
|
|
|
|
2020-08-21 11:37:05 -07:00
|
|
|
self.* = .{
|
2023-01-27 13:09:35 -08:00
|
|
|
.scene = scene,
|
2023-02-20 09:01:24 -08:00
|
|
|
.interactive_content = interactive_content,
|
|
|
|
.drag_icons = drag_icons,
|
2023-02-16 07:54:53 -08:00
|
|
|
.layers = .{
|
|
|
|
.outputs = outputs,
|
2023-02-20 09:01:24 -08:00
|
|
|
.xwayland_override_redirect = xwayland_override_redirect,
|
2023-02-16 07:54:53 -08:00
|
|
|
},
|
2023-02-24 10:28:37 -08:00
|
|
|
.hidden = .{
|
|
|
|
.tree = hidden_tree,
|
|
|
|
.pending = .{
|
|
|
|
.focus_stack = undefined,
|
|
|
|
.wm_stack = undefined,
|
|
|
|
},
|
|
|
|
.inflight = .{
|
|
|
|
.focus_stack = undefined,
|
|
|
|
.wm_stack = undefined,
|
|
|
|
},
|
|
|
|
},
|
2020-11-03 15:23:21 -08:00
|
|
|
.output_layout = output_layout,
|
2020-12-27 06:24:20 -08:00
|
|
|
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
|
|
|
|
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
|
2023-02-24 10:28:37 -08:00
|
|
|
.transaction_timeout = transaction_timeout,
|
2020-08-21 11:37:05 -07:00
|
|
|
};
|
2023-02-24 10:28:37 -08:00
|
|
|
self.hidden.pending.focus_stack.init();
|
|
|
|
self.hidden.pending.wm_stack.init();
|
|
|
|
self.hidden.inflight.focus_stack.init();
|
|
|
|
self.hidden.inflight.wm_stack.init();
|
2020-12-27 06:24:20 -08:00
|
|
|
|
|
|
|
server.backend.events.new_output.add(&self.new_output);
|
|
|
|
self.output_manager.events.apply.add(&self.manager_apply);
|
|
|
|
self.output_manager.events.@"test".add(&self.manager_test);
|
|
|
|
self.output_layout.events.change.add(&self.layout_change);
|
|
|
|
self.power_manager.events.set_mode.add(&self.power_manager_set_mode);
|
2020-05-02 07:44:46 -07:00
|
|
|
}
|
2020-04-01 08:50:49 -07:00
|
|
|
|
2020-05-02 07:44:46 -07:00
|
|
|
pub fn deinit(self: *Self) void {
|
2023-01-27 13:09:35 -08:00
|
|
|
self.scene.tree.node.destroy();
|
2020-11-03 15:23:21 -08:00
|
|
|
self.output_layout.destroy();
|
2023-02-24 10:28:37 -08:00
|
|
|
self.transaction_timeout.remove();
|
2020-05-02 07:44:46 -07:00
|
|
|
}
|
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
pub const AtResult = struct {
|
|
|
|
surface: ?*wlr.Surface,
|
|
|
|
sx: f64,
|
|
|
|
sy: f64,
|
|
|
|
node: union(enum) {
|
|
|
|
view: *View,
|
|
|
|
layer_surface: *LayerSurface,
|
|
|
|
lock_surface: *LockSurface,
|
|
|
|
xwayland_override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2023-02-20 09:01:24 -08:00
|
|
|
/// Return information about what is currently rendered in the interactive_content
|
|
|
|
/// tree at the given layout coordinates, taking surface input regions into account.
|
2023-01-29 03:03:41 -08:00
|
|
|
pub fn at(self: Self, lx: f64, ly: f64) ?AtResult {
|
|
|
|
var sx: f64 = undefined;
|
|
|
|
var sy: f64 = undefined;
|
2023-02-20 09:01:24 -08:00
|
|
|
const node_at = self.interactive_content.node.at(lx, ly, &sx, &sy) orelse return null;
|
2023-01-29 03:03:41 -08:00
|
|
|
|
|
|
|
const surface: ?*wlr.Surface = blk: {
|
|
|
|
if (node_at.type == .buffer) {
|
|
|
|
const scene_buffer = wlr.SceneBuffer.fromNode(node_at);
|
|
|
|
if (wlr.SceneSurface.fromBuffer(scene_buffer)) |scene_surface| {
|
|
|
|
break :blk scene_surface.surface;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break :blk null;
|
|
|
|
};
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
if (SceneNodeData.get(node_at)) |scene_node_data| {
|
|
|
|
return .{
|
|
|
|
.surface = surface,
|
|
|
|
.sx = sx,
|
|
|
|
.sy = sy,
|
|
|
|
.node = switch (scene_node_data.data) {
|
|
|
|
.view => |view| .{ .view = view },
|
|
|
|
.layer_surface => |layer_surface| .{ .layer_surface = layer_surface },
|
|
|
|
.lock_surface => |lock_surface| .{ .lock_surface = lock_surface },
|
|
|
|
.xwayland_override_redirect => |xwayland_override_redirect| .{
|
|
|
|
.xwayland_override_redirect = xwayland_override_redirect,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return null;
|
2023-01-29 03:03:41 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-31 06:47:19 -08:00
|
|
|
fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
|
|
|
|
const log = std.log.scoped(.output_manager);
|
2020-12-27 06:24:20 -08:00
|
|
|
|
2023-01-31 06:47:19 -08:00
|
|
|
log.debug("new output {s}", .{wlr_output.name});
|
|
|
|
|
|
|
|
Output.create(wlr_output) catch |err| {
|
|
|
|
switch (err) {
|
|
|
|
error.OutOfMemory => log.err("out of memory", .{}),
|
|
|
|
error.InitRenderFailed => log.err("failed to initialize renderer for output {s}", .{wlr_output.name}),
|
|
|
|
}
|
2020-12-27 06:24:20 -08:00
|
|
|
wlr_output.destroy();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-12-21 07:19:44 -08:00
|
|
|
/// Remove the output from self.outputs and evacuate views if it is a member of
|
|
|
|
/// the list. The node is not freed
|
2023-02-24 10:28:37 -08:00
|
|
|
pub fn removeOutput(root: *Self, output: *Output) void {
|
|
|
|
{
|
|
|
|
const node = @fieldParentPtr(std.TailQueue(Output).Node, "data", output);
|
2020-12-21 07:19:44 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
// If the node has already been removed, do nothing
|
|
|
|
var output_it = root.outputs.first;
|
|
|
|
while (output_it) |n| : (output_it = n.next) {
|
|
|
|
if (n == node) break;
|
|
|
|
} else return;
|
2020-12-21 07:19:44 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
root.outputs.remove(node);
|
|
|
|
}
|
2020-11-16 00:14:50 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
if (output.inflight.layout_demand) |layout_demand| {
|
|
|
|
layout_demand.deinit();
|
|
|
|
output.inflight.layout_demand = null;
|
|
|
|
}
|
|
|
|
while (output.layouts.first) |node| node.data.destroy();
|
2020-11-16 00:14:50 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
{
|
|
|
|
var it = output.inflight.focus_stack.iterator(.forward);
|
|
|
|
while (it.next()) |view| {
|
|
|
|
view.inflight.output = null;
|
|
|
|
view.current.output = null;
|
|
|
|
view.tree.node.reparent(root.hidden.tree);
|
|
|
|
view.popup_tree.node.reparent(root.hidden.tree);
|
|
|
|
}
|
|
|
|
root.hidden.inflight.focus_stack.prependList(&output.inflight.focus_stack);
|
|
|
|
root.hidden.inflight.wm_stack.prependList(&output.inflight.wm_stack);
|
|
|
|
}
|
|
|
|
// Use the first output in the list as fallback. If the last real output
|
|
|
|
// is being removed store the views in Root.hidden.
|
|
|
|
const fallback_output = if (root.outputs.first) |node| &node.data else null;
|
|
|
|
if (fallback_output) |fallback| {
|
|
|
|
var it = output.pending.focus_stack.safeIterator(.reverse);
|
|
|
|
while (it.next()) |view| view.setPendingOutput(fallback);
|
|
|
|
} else {
|
|
|
|
var it = output.pending.focus_stack.iterator(.forward);
|
|
|
|
while (it.next()) |view| view.pending.output = null;
|
|
|
|
root.hidden.pending.focus_stack.prependList(&output.pending.focus_stack);
|
|
|
|
root.hidden.pending.wm_stack.prependList(&output.pending.wm_stack);
|
|
|
|
// Store the focused output tags if we are hotplugged down to
|
|
|
|
// 0 real outputs so they can be restored on gaining a new output.
|
|
|
|
root.hidden.tags = output.pending.tags;
|
2020-11-16 00:14:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close all layer surfaces on the removed output
|
2023-02-11 03:23:07 -08:00
|
|
|
for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top, .bottom, .background }) |layer| {
|
|
|
|
const tree = output.layerSurfaceTree(layer);
|
|
|
|
var it = tree.children.safeIterator(.forward);
|
|
|
|
while (it.next()) |scene_node| {
|
|
|
|
assert(scene_node.type == .tree);
|
|
|
|
if (@intToPtr(?*SceneNodeData, scene_node.data)) |node_data| {
|
2023-02-28 09:19:37 -08:00
|
|
|
node_data.data.layer_surface.wlr_layer_surface.destroy();
|
2023-02-11 03:23:07 -08:00
|
|
|
}
|
|
|
|
}
|
2020-11-16 00:14:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// If any seat has the removed output focused, focus the fallback one
|
2021-05-13 05:26:27 -07:00
|
|
|
var seat_it = server.input_manager.seats.first;
|
2020-11-16 00:14:50 -08:00
|
|
|
while (seat_it) |seat_node| : (seat_it = seat_node.next) {
|
|
|
|
const seat = &seat_node.data;
|
|
|
|
if (seat.focused_output == output) {
|
|
|
|
seat.focusOutput(fallback_output);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-28 08:45:08 -08:00
|
|
|
output.status.deinit();
|
2021-08-12 13:38:08 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
root.applyPending();
|
2020-11-16 00:14:50 -08:00
|
|
|
}
|
|
|
|
|
2020-12-21 07:19:44 -08:00
|
|
|
/// Add the output to self.outputs and the output layout if it has not
|
|
|
|
/// already been added.
|
2023-02-24 10:28:37 -08:00
|
|
|
pub fn addOutput(root: *Self, output: *Output) void {
|
2020-12-21 07:19:44 -08:00
|
|
|
const node = @fieldParentPtr(std.TailQueue(Output).Node, "data", output);
|
|
|
|
|
|
|
|
// If we have already added the output, do nothing and return
|
2023-02-24 10:28:37 -08:00
|
|
|
var output_it = root.outputs.first;
|
2020-12-21 07:19:44 -08:00
|
|
|
while (output_it) |n| : (output_it = n.next) if (n == node) return;
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
root.outputs.append(node);
|
2020-05-02 07:44:46 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
// This arranges outputs from left-to-right in the order they appear. The
|
2020-12-30 04:19:40 -08:00
|
|
|
// wlr-output-management protocol may be used to modify this arrangement.
|
|
|
|
// This also creates a wl_output global which is advertised to clients.
|
2023-02-24 10:28:37 -08:00
|
|
|
root.output_layout.addAuto(output.wlr_output);
|
2023-01-27 13:09:35 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
const layout_output = root.output_layout.get(output.wlr_output).?;
|
2023-01-27 13:09:35 -08:00
|
|
|
output.tree.node.setEnabled(true);
|
|
|
|
output.tree.node.setPosition(layout_output.x, layout_output.y);
|
2020-11-01 05:49:01 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
// If we previously had no outputs move all views to the new output and focus it.
|
|
|
|
if (root.outputs.len == 1) {
|
|
|
|
output.pending.tags = root.hidden.tags;
|
|
|
|
{
|
|
|
|
var it = root.hidden.pending.focus_stack.safeIterator(.reverse);
|
|
|
|
while (it.next()) |view| view.setPendingOutput(output);
|
|
|
|
assert(root.hidden.pending.focus_stack.empty());
|
|
|
|
assert(root.hidden.pending.wm_stack.empty());
|
|
|
|
assert(root.hidden.inflight.focus_stack.empty());
|
|
|
|
assert(root.hidden.inflight.wm_stack.empty());
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Focus the new output with all seats
|
|
|
|
var it = server.input_manager.seats.first;
|
|
|
|
while (it) |seat_node| : (it = seat_node.next) {
|
|
|
|
const seat = &seat_node.data;
|
|
|
|
seat.focusOutput(output);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
root.applyPending();
|
|
|
|
}
|
|
|
|
}
|
2020-12-30 04:19:40 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
/// Trigger asynchronous application of pending state for all outputs and views.
|
|
|
|
/// Changes will not be applied to the scene graph until the layout generator
|
|
|
|
/// generates a new layout for all outputs and all affected clients ack a
|
|
|
|
/// configure and commit a new buffer.
|
|
|
|
pub fn applyPending(root: *Self) void {
|
2023-03-01 01:49:44 -08:00
|
|
|
{
|
|
|
|
// Changes to the pending state may require a focus update to keep
|
|
|
|
// state consistent. Instead of having focus(null) calls spread all
|
|
|
|
// around the codebase and risk forgetting one, always ensure focus
|
|
|
|
// state is synchronized here.
|
|
|
|
var it = server.input_manager.seats.first;
|
|
|
|
while (it) |node| : (it = node.next) node.data.focus(null);
|
|
|
|
}
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
// If there is already a transaction inflight, wait until it completes.
|
|
|
|
if (root.inflight_layout_demands > 0 or root.inflight_configures > 0) {
|
|
|
|
root.pending_state_dirty = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
root.pending_state_dirty = false;
|
2020-12-30 04:19:40 -08:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
{
|
|
|
|
var it = root.hidden.pending.focus_stack.iterator(.forward);
|
|
|
|
while (it.next()) |view| {
|
|
|
|
assert(view.pending.output == null);
|
|
|
|
view.inflight.output = null;
|
|
|
|
view.inflight_focus_stack_link.remove();
|
|
|
|
root.hidden.inflight.focus_stack.append(view);
|
2020-04-18 03:21:43 -07:00
|
|
|
}
|
2020-03-25 07:59:24 -07:00
|
|
|
}
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
{
|
|
|
|
var it = root.hidden.pending.wm_stack.iterator(.forward);
|
|
|
|
while (it.next()) |view| {
|
|
|
|
view.inflight_wm_stack_link.remove();
|
|
|
|
root.hidden.inflight.wm_stack.append(view);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-28 13:56:12 -08:00
|
|
|
{
|
|
|
|
var output_it = root.outputs.first;
|
|
|
|
while (output_it) |node| : (output_it = node.next) {
|
|
|
|
const output = &node.data;
|
2023-02-24 10:28:37 -08:00
|
|
|
|
2023-02-28 13:56:12 -08:00
|
|
|
// Iterate the focus stack in order to ensure the currently focused/most
|
|
|
|
// recently focused view that requests fullscreen is given fullscreen.
|
2023-03-03 04:40:44 -08:00
|
|
|
output.inflight.fullscreen = null;
|
2023-02-28 13:56:12 -08:00
|
|
|
{
|
|
|
|
var it = output.pending.focus_stack.iterator(.forward);
|
|
|
|
while (it.next()) |view| {
|
|
|
|
assert(view.pending.output == output);
|
|
|
|
|
|
|
|
if (view.current.float and !view.pending.float) {
|
|
|
|
// If switching from float to non-float, save the dimensions.
|
|
|
|
view.float_box = view.current.box;
|
|
|
|
} else if (!view.current.float and view.pending.float) {
|
|
|
|
// If switching from non-float to float, apply the saved float dimensions.
|
|
|
|
view.pending.box = view.float_box;
|
2023-03-01 08:08:54 -08:00
|
|
|
view.pending.clampToOutput();
|
2023-02-28 13:56:12 -08:00
|
|
|
}
|
2023-02-24 10:28:37 -08:00
|
|
|
|
2023-03-03 04:40:44 -08:00
|
|
|
if (!view.current.fullscreen and view.pending.fullscreen) {
|
|
|
|
view.post_fullscreen_box = view.pending.box;
|
|
|
|
view.pending.box = .{ .x = 0, .y = 0, .width = undefined, .height = undefined };
|
|
|
|
output.wlr_output.effectiveResolution(&view.pending.box.width, &view.pending.box.height);
|
|
|
|
} else if (view.current.fullscreen and !view.pending.fullscreen) {
|
|
|
|
view.pending.box = view.post_fullscreen_box;
|
|
|
|
view.pending.clampToOutput();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output.inflight.fullscreen == null and view.pending.fullscreen and
|
2023-02-28 13:56:12 -08:00
|
|
|
view.pending.tags & output.pending.tags != 0)
|
|
|
|
{
|
2023-03-03 04:40:44 -08:00
|
|
|
output.inflight.fullscreen = view;
|
2023-02-24 10:28:37 -08:00
|
|
|
}
|
|
|
|
|
2023-02-28 13:56:12 -08:00
|
|
|
view.inflight_focus_stack_link.remove();
|
|
|
|
output.inflight.focus_stack.append(view);
|
2023-02-24 10:28:37 -08:00
|
|
|
|
2023-02-28 13:56:12 -08:00
|
|
|
view.inflight = view.pending;
|
|
|
|
}
|
2023-02-24 10:28:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2023-02-28 13:56:12 -08:00
|
|
|
var it = output.pending.wm_stack.iterator(.forward);
|
2023-02-24 10:28:37 -08:00
|
|
|
while (it.next()) |view| {
|
2023-02-28 13:56:12 -08:00
|
|
|
view.inflight_wm_stack_link.remove();
|
|
|
|
output.inflight.wm_stack.append(view);
|
2023-02-24 10:28:37 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-28 13:56:12 -08:00
|
|
|
output.inflight.tags = output.pending.tags;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// Layout demands can't be sent until after the inflight stacks of
|
|
|
|
// all outputs have been updated.
|
|
|
|
var output_it = root.outputs.first;
|
|
|
|
while (output_it) |node| : (output_it = node.next) {
|
|
|
|
const output = &node.data;
|
|
|
|
assert(output.inflight.layout_demand == null);
|
|
|
|
if (output.layout) |layout| {
|
|
|
|
var layout_count: u32 = 0;
|
|
|
|
{
|
|
|
|
var it = output.inflight.wm_stack.iterator(.forward);
|
|
|
|
while (it.next()) |view| {
|
|
|
|
if (!view.inflight.float and !view.inflight.fullscreen and
|
|
|
|
view.inflight.tags & output.inflight.tags != 0)
|
|
|
|
{
|
|
|
|
layout_count += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (layout_count > 0) {
|
|
|
|
// TODO don't do this if the count has not changed
|
|
|
|
layout.startLayoutDemand(layout_count);
|
|
|
|
}
|
2023-02-24 10:28:37 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (root.inflight_layout_demands == 0) {
|
|
|
|
root.sendConfigures();
|
2020-10-02 06:53:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This function is used to inform the transaction system that a layout demand
|
|
|
|
/// has either been completed or timed out. If it was the last pending layout
|
|
|
|
/// demand in the current sequence, a transaction is started.
|
2023-02-24 10:28:37 -08:00
|
|
|
pub fn notifyLayoutDemandDone(root: *Self) void {
|
|
|
|
root.inflight_layout_demands -= 1;
|
|
|
|
if (root.inflight_layout_demands == 0) {
|
|
|
|
root.sendConfigures();
|
|
|
|
}
|
2020-10-02 06:53:08 -07:00
|
|
|
}
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
fn sendConfigures(root: *Self) void {
|
|
|
|
assert(root.inflight_layout_demands == 0);
|
|
|
|
assert(root.inflight_configures == 0);
|
2020-05-02 07:44:46 -07:00
|
|
|
|
2020-08-21 06:08:28 -07:00
|
|
|
// Iterate over all views of all outputs
|
2023-02-24 10:28:37 -08:00
|
|
|
var output_it = root.outputs.first;
|
2020-08-21 06:08:28 -07:00
|
|
|
while (output_it) |output_node| : (output_it = output_node.next) {
|
2023-02-24 10:28:37 -08:00
|
|
|
const output = &output_node.data;
|
2020-08-11 10:04:37 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
var focus_stack_it = output.inflight.focus_stack.iterator(.forward);
|
|
|
|
while (focus_stack_it.next()) |view| {
|
2023-03-02 07:42:16 -08:00
|
|
|
// This can happen if a view is unmapped while a layout demand including it is inflight
|
|
|
|
if (!view.mapped) continue;
|
|
|
|
|
2023-03-04 06:51:58 -08:00
|
|
|
if (view.configure()) {
|
|
|
|
root.inflight_configures += 1;
|
|
|
|
view.saveSurfaceTree();
|
|
|
|
view.sendFrameDone();
|
2020-06-09 08:04:38 -07:00
|
|
|
}
|
2020-04-03 09:53:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
if (root.inflight_configures > 0) {
|
2021-06-08 08:18:45 -07:00
|
|
|
std.log.scoped(.transaction).debug("started transaction with {} pending configure(s)", .{
|
2023-02-24 10:28:37 -08:00
|
|
|
root.inflight_configures,
|
2021-06-08 08:18:45 -07:00
|
|
|
});
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
root.transaction_timeout.timerUpdate(200) catch {
|
|
|
|
std.log.scoped(.transaction).err("failed to update timer", .{});
|
|
|
|
root.commitTransaction();
|
|
|
|
};
|
2020-05-02 07:44:46 -07:00
|
|
|
} else {
|
2023-02-24 10:28:37 -08:00
|
|
|
root.commitTransaction();
|
2020-03-26 13:32:30 -07:00
|
|
|
}
|
2020-05-02 07:44:46 -07:00
|
|
|
}
|
2020-03-26 13:32:30 -07:00
|
|
|
|
2022-11-04 16:29:51 -07:00
|
|
|
fn handleTransactionTimeout(self: *Self) c_int {
|
2023-02-24 10:28:37 -08:00
|
|
|
assert(self.inflight_layout_demands == 0);
|
|
|
|
|
2021-02-05 00:46:18 -08:00
|
|
|
std.log.scoped(.transaction).err("timeout occurred, some imperfect frames may be shown", .{});
|
2020-04-17 10:37:50 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
self.inflight_configures = 0;
|
2020-05-02 07:44:46 -07:00
|
|
|
self.commitTransaction();
|
2020-04-17 10:37:50 -07:00
|
|
|
|
2020-05-02 07:44:46 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn notifyConfigured(self: *Self) void {
|
2023-02-24 10:28:37 -08:00
|
|
|
assert(self.inflight_layout_demands == 0);
|
|
|
|
|
|
|
|
self.inflight_configures -= 1;
|
|
|
|
if (self.inflight_configures == 0) {
|
2020-06-01 15:18:28 -07:00
|
|
|
// Disarm the timer, as we didn't timeout
|
2023-02-24 10:28:37 -08:00
|
|
|
self.transaction_timeout.timerUpdate(0) catch std.log.scoped(.transaction).err("error disarming timer", .{});
|
2020-05-02 07:44:46 -07:00
|
|
|
self.commitTransaction();
|
2020-03-29 07:56:30 -07:00
|
|
|
}
|
2020-05-02 07:44:46 -07:00
|
|
|
}
|
2020-03-29 07:56:30 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
/// Apply the inflight state and drop stashed buffers. This means that
|
2020-05-02 07:44:46 -07:00
|
|
|
/// the next frame drawn will be the post-transaction state of the
|
|
|
|
/// layout. Should only be called after all clients have configured for
|
|
|
|
/// the new layout. If called early imperfect frames may be drawn.
|
2023-02-24 10:28:37 -08:00
|
|
|
fn commitTransaction(root: *Self) void {
|
|
|
|
assert(root.inflight_layout_demands == 0);
|
|
|
|
assert(root.inflight_configures == 0);
|
2020-04-04 07:48:21 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
{
|
|
|
|
var it = root.hidden.inflight.focus_stack.safeIterator(.forward);
|
|
|
|
while (it.next()) |view| {
|
|
|
|
assert(view.inflight.output == null);
|
|
|
|
view.current.output = null;
|
|
|
|
|
|
|
|
view.tree.node.reparent(root.hidden.tree);
|
|
|
|
view.popup_tree.node.reparent(root.hidden.tree);
|
|
|
|
|
|
|
|
view.updateCurrent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var output_it = root.outputs.first;
|
2020-05-02 07:44:46 -07:00
|
|
|
while (output_it) |output_node| : (output_it = output_node.next) {
|
|
|
|
const output = &output_node.data;
|
2020-03-29 07:56:30 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
if (output.inflight.tags != output.current.tags) {
|
2021-02-05 00:46:18 -08:00
|
|
|
std.log.scoped(.output).debug(
|
2020-05-02 07:44:46 -07:00
|
|
|
"changing current focus: {b:0>10} to {b:0>10}",
|
2023-02-24 10:28:37 -08:00
|
|
|
.{ output.current.tags, output.inflight.tags },
|
2020-05-02 07:44:46 -07:00
|
|
|
);
|
2023-02-24 10:28:37 -08:00
|
|
|
}
|
2023-02-28 08:45:08 -08:00
|
|
|
output.current.tags = output.inflight.tags;
|
2023-02-24 10:28:37 -08:00
|
|
|
|
|
|
|
var focus_stack_it = output.inflight.focus_stack.iterator(.forward);
|
|
|
|
while (focus_stack_it.next()) |view| {
|
|
|
|
assert(view.inflight.output == output);
|
2020-08-20 05:35:19 -07:00
|
|
|
|
2023-03-02 06:10:10 -08:00
|
|
|
if (view.current.output != view.inflight.output or
|
2023-02-28 13:56:12 -08:00
|
|
|
(output.current.fullscreen == view and output.inflight.fullscreen != view))
|
|
|
|
{
|
2023-03-02 06:10:10 -08:00
|
|
|
if (view.inflight.float) {
|
|
|
|
view.tree.node.reparent(output.layers.float);
|
|
|
|
} else {
|
|
|
|
view.tree.node.reparent(output.layers.layout);
|
|
|
|
}
|
2023-02-24 10:28:37 -08:00
|
|
|
view.popup_tree.node.reparent(output.layers.popups);
|
|
|
|
}
|
2023-02-28 08:45:08 -08:00
|
|
|
|
2023-03-02 06:10:10 -08:00
|
|
|
if (view.current.float != view.inflight.float) {
|
|
|
|
if (view.inflight.float) {
|
|
|
|
view.tree.node.reparent(output.layers.float);
|
|
|
|
} else {
|
|
|
|
view.tree.node.reparent(output.layers.layout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-01 10:03:16 -08:00
|
|
|
view.updateCurrent();
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
const enabled = view.current.tags & output.current.tags != 0;
|
|
|
|
view.tree.node.setEnabled(enabled);
|
|
|
|
view.popup_tree.node.setEnabled(enabled);
|
2023-02-28 13:56:12 -08:00
|
|
|
if (output.inflight.fullscreen != view) {
|
|
|
|
// TODO this approach for syncing the order will likely cause over-damaging.
|
|
|
|
view.tree.node.lowerToBottom();
|
|
|
|
}
|
2020-04-13 12:00:18 -07:00
|
|
|
}
|
2020-06-04 07:56:58 -07:00
|
|
|
|
2023-02-28 13:56:12 -08:00
|
|
|
if (output.inflight.fullscreen != output.current.fullscreen) {
|
|
|
|
if (output.inflight.fullscreen) |view| {
|
|
|
|
assert(view.inflight.output == output);
|
|
|
|
assert(view.current.output == output);
|
|
|
|
view.tree.node.reparent(output.layers.fullscreen);
|
|
|
|
}
|
|
|
|
output.current.fullscreen = output.inflight.fullscreen;
|
|
|
|
output.layers.fullscreen.node.setEnabled(output.current.fullscreen != null);
|
|
|
|
}
|
|
|
|
|
2023-02-28 08:45:08 -08:00
|
|
|
output.status.handleTransactionCommit(output);
|
2020-03-26 13:32:30 -07:00
|
|
|
}
|
2023-02-24 10:28:37 -08:00
|
|
|
|
|
|
|
{
|
|
|
|
var it = server.input_manager.seats.first;
|
|
|
|
while (it) |node| : (it = node.next) node.data.cursor.updateState();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// This must be done after updating cursor state in case the view was the target of move/resize.
|
|
|
|
var it = root.hidden.inflight.focus_stack.safeIterator(.forward);
|
|
|
|
while (it.next()) |view| {
|
|
|
|
if (view.destroying) view.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-11 05:14:01 -07:00
|
|
|
server.idle_inhibitor_manager.idleInhibitCheckActive();
|
2023-02-24 10:28:37 -08:00
|
|
|
|
|
|
|
if (root.pending_state_dirty) {
|
|
|
|
root.applyPending();
|
|
|
|
}
|
2020-05-02 07:44:46 -07:00
|
|
|
}
|
2020-12-27 06:24:20 -08:00
|
|
|
|
|
|
|
/// Send the new output configuration to all wlr-output-manager clients
|
2021-10-11 03:44:46 -07:00
|
|
|
fn handleLayoutChange(listener: *wl.Listener(*wlr.OutputLayout), _: *wlr.OutputLayout) void {
|
2020-12-27 06:24:20 -08:00
|
|
|
const self = @fieldParentPtr(Self, "layout_change", listener);
|
|
|
|
|
2022-01-28 14:28:00 -08:00
|
|
|
const config = self.currentOutputConfig() catch {
|
2021-10-11 03:44:46 -07:00
|
|
|
std.log.scoped(.output_manager).err("out of memory", .{});
|
2020-12-27 06:24:20 -08:00
|
|
|
return;
|
|
|
|
};
|
|
|
|
self.output_manager.setConfiguration(config);
|
|
|
|
}
|
|
|
|
|
2020-12-31 06:35:35 -08:00
|
|
|
fn handleManagerApply(
|
2020-12-27 06:24:20 -08:00
|
|
|
listener: *wl.Listener(*wlr.OutputConfigurationV1),
|
|
|
|
config: *wlr.OutputConfigurationV1,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "manager_apply", listener);
|
|
|
|
defer config.destroy();
|
|
|
|
|
2022-01-28 14:28:00 -08:00
|
|
|
self.processOutputConfig(config, .apply);
|
2020-12-27 06:24:20 -08:00
|
|
|
|
|
|
|
// Send the config that was actually applied
|
2022-01-28 14:28:00 -08:00
|
|
|
const applied_config = self.currentOutputConfig() catch {
|
2021-10-11 03:44:46 -07:00
|
|
|
std.log.scoped(.output_manager).err("out of memory", .{});
|
2020-12-27 06:24:20 -08:00
|
|
|
return;
|
|
|
|
};
|
|
|
|
self.output_manager.setConfiguration(applied_config);
|
|
|
|
}
|
|
|
|
|
2020-12-31 06:35:35 -08:00
|
|
|
fn handleManagerTest(
|
2022-01-28 14:28:00 -08:00
|
|
|
listener: *wl.Listener(*wlr.OutputConfigurationV1),
|
2020-12-27 06:24:20 -08:00
|
|
|
config: *wlr.OutputConfigurationV1,
|
|
|
|
) void {
|
2022-01-28 14:28:00 -08:00
|
|
|
const self = @fieldParentPtr(Self, "manager_test", listener);
|
2020-12-27 06:24:20 -08:00
|
|
|
defer config.destroy();
|
|
|
|
|
2022-01-28 14:28:00 -08:00
|
|
|
self.processOutputConfig(config, .test_only);
|
2020-12-27 06:24:20 -08:00
|
|
|
}
|
|
|
|
|
2022-01-28 14:28:00 -08:00
|
|
|
fn processOutputConfig(
|
|
|
|
self: *Self,
|
|
|
|
config: *wlr.OutputConfigurationV1,
|
|
|
|
action: enum { test_only, apply },
|
|
|
|
) void {
|
|
|
|
// Ignore layout change events this function generates while applying the config
|
2020-12-27 06:24:20 -08:00
|
|
|
self.layout_change.link.remove();
|
|
|
|
defer self.output_layout.events.change.add(&self.layout_change);
|
|
|
|
|
2022-01-28 14:28:00 -08:00
|
|
|
var success = true;
|
2020-12-27 06:24:20 -08:00
|
|
|
|
|
|
|
var it = config.heads.iterator(.forward);
|
|
|
|
while (it.next()) |head| {
|
|
|
|
const wlr_output = head.state.output;
|
2022-01-28 14:28:00 -08:00
|
|
|
const output = @intToPtr(*Output, wlr_output.data);
|
2020-12-27 06:24:20 -08:00
|
|
|
|
2023-01-24 04:55:40 -08:00
|
|
|
var proposed_state = wlr.Output.State.init();
|
|
|
|
head.state.apply(&proposed_state);
|
2020-12-27 06:24:20 -08:00
|
|
|
|
2023-01-24 04:55:40 -08:00
|
|
|
switch (action) {
|
|
|
|
.test_only => {
|
|
|
|
if (!wlr_output.testState(&proposed_state)) success = false;
|
|
|
|
},
|
|
|
|
.apply => {
|
|
|
|
if (wlr_output.commitState(&proposed_state)) {
|
|
|
|
if (head.state.enabled) {
|
2022-01-28 14:28:00 -08:00
|
|
|
// Just updates the output's position if it is already in the layout
|
|
|
|
self.output_layout.add(output.wlr_output, head.state.x, head.state.y);
|
2023-01-27 13:09:35 -08:00
|
|
|
output.tree.node.setEnabled(true);
|
|
|
|
output.tree.node.setPosition(head.state.x, head.state.y);
|
2023-03-01 10:40:37 -08:00
|
|
|
// Even though we call this in the output's handler for the mode event
|
|
|
|
// it is necessary to call it here as well since changing e.g. only
|
|
|
|
// the transform will require the dimensions of the background to be
|
|
|
|
// updated but will not trigger a mode event.
|
|
|
|
output.updateBackgroundRect();
|
2023-02-11 03:23:07 -08:00
|
|
|
output.arrangeLayers();
|
2023-01-24 04:55:40 -08:00
|
|
|
} else {
|
2022-01-28 14:28:00 -08:00
|
|
|
self.removeOutput(output);
|
|
|
|
self.output_layout.remove(output.wlr_output);
|
2023-01-27 13:09:35 -08:00
|
|
|
output.tree.node.setEnabled(false);
|
2022-01-28 14:28:00 -08:00
|
|
|
}
|
2023-01-30 14:48:24 -08:00
|
|
|
} else {
|
2023-01-24 04:55:40 -08:00
|
|
|
std.log.scoped(.output_manager).err("failed to apply config to output {s}", .{
|
|
|
|
output.wlr_output.name,
|
|
|
|
});
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
},
|
2020-12-27 06:24:20 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
if (action == .apply) self.applyPending();
|
2020-12-27 06:24:20 -08:00
|
|
|
|
2022-01-28 14:28:00 -08:00
|
|
|
if (success) {
|
|
|
|
config.sendSucceeded();
|
|
|
|
} else {
|
|
|
|
config.sendFailed();
|
2020-12-27 06:24:20 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 14:28:00 -08:00
|
|
|
fn currentOutputConfig(self: *Self) !*wlr.OutputConfigurationV1 {
|
2020-12-27 06:24:20 -08:00
|
|
|
const config = try wlr.OutputConfigurationV1.create();
|
|
|
|
// this destroys all associated config heads as well
|
|
|
|
errdefer config.destroy();
|
|
|
|
|
|
|
|
var it = self.all_outputs.first;
|
2022-01-28 14:28:00 -08:00
|
|
|
while (it) |node| : (it = node.next) {
|
|
|
|
const output = node.data;
|
|
|
|
const head = try wlr.OutputConfigurationV1.Head.create(config, output.wlr_output);
|
|
|
|
|
2022-11-11 11:25:21 -08:00
|
|
|
// If the output is not part of the layout (and thus disabled)
|
|
|
|
// the box will be zeroed out.
|
|
|
|
var box: wlr.Box = undefined;
|
|
|
|
self.output_layout.getBox(output.wlr_output, &box);
|
|
|
|
head.state.x = box.x;
|
|
|
|
head.state.y = box.y;
|
2022-01-28 14:28:00 -08:00
|
|
|
}
|
2020-12-27 06:24:20 -08:00
|
|
|
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
2020-12-31 06:35:35 -08:00
|
|
|
fn handlePowerManagerSetMode(
|
2021-10-11 03:44:46 -07:00
|
|
|
_: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode),
|
2020-12-27 06:24:20 -08:00
|
|
|
event: *wlr.OutputPowerManagerV1.event.SetMode,
|
|
|
|
) void {
|
|
|
|
const enable = event.mode == .on;
|
|
|
|
|
|
|
|
const log_text = if (enable) "Enabling" else "Disabling";
|
2021-02-05 00:46:18 -08:00
|
|
|
std.log.scoped(.output_manager).debug(
|
2021-05-23 04:35:37 -07:00
|
|
|
"{s} dpms for output {s}",
|
2021-12-20 19:18:03 -08:00
|
|
|
.{ log_text, event.output.name },
|
2020-12-27 06:24:20 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
event.output.enable(enable);
|
2021-05-23 04:35:37 -07:00
|
|
|
event.output.commit() catch {
|
2021-12-20 19:18:03 -08:00
|
|
|
std.log.scoped(.server).err("output commit failed for {s}", .{event.output.name});
|
2021-05-23 04:35:37 -07:00
|
|
|
};
|
2020-12-27 06:24:20 -08:00
|
|
|
}
|