river/river/LayoutDemand.zig
2023-10-16 16:27:03 +02:00

158 lines
5.2 KiB
Zig

// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2020 - 2021 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, version 3.
//
// 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 std = @import("std");
const assert = std.debug.assert;
const wlr = @import("wlroots");
const wayland = @import("wayland");
const wl = wayland.server.wl;
const server = &@import("main.zig").server;
const util = @import("util.zig");
const Layout = @import("Layout.zig");
const Server = @import("Server.zig");
const Output = @import("Output.zig");
const View = @import("View.zig");
const log = std.log.scoped(.layout);
const Error = error{ViewDimensionMismatch};
const timeout_ms = 100;
serial: u32,
/// Number of views for which dimensions have not been pushed.
/// This will go negative if the client pushes too many dimensions.
views: i32,
/// Proposed view dimensions
view_boxen: []wlr.Box,
timeout_timer: *wl.EventSource,
pub fn init(layout: *Layout, views: u32) !Self {
const event_loop = server.wl_server.getEventLoop();
const timeout_timer = try event_loop.addTimer(*Layout, handleTimeout, layout);
errdefer timeout_timer.remove();
try timeout_timer.timerUpdate(timeout_ms);
return Self{
.serial = server.wl_server.nextSerial(),
.views = @intCast(views),
.view_boxen = try util.gpa.alloc(wlr.Box, views),
.timeout_timer = timeout_timer,
};
}
pub fn deinit(self: *const Self) void {
self.timeout_timer.remove();
util.gpa.free(self.view_boxen);
}
/// Destroy the LayoutDemand on timeout.
/// All further responses to the event will simply be ignored.
fn handleTimeout(layout: *Layout) c_int {
log.info(
"layout demand for layout '{s}' on output '{s}' timed out",
.{ layout.namespace, layout.output.wlr_output.name },
);
layout.output.inflight.layout_demand.?.deinit();
layout.output.inflight.layout_demand = null;
server.root.notifyLayoutDemandDone();
return 0;
}
/// Push a set of proposed view dimensions and position to the list
pub fn pushViewDimensions(self: *Self, x: i32, y: i32, width: u31, height: u31) void {
// The client pushed too many dimensions
if (self.views <= 0) {
self.views -= 1;
return;
}
self.view_boxen[self.view_boxen.len - @as(usize, @intCast(self.views))] = .{
.x = x,
.y = y,
.width = width,
.height = height,
};
self.views -= 1;
}
/// Apply the proposed layout to the output
pub fn apply(self: *Self, layout: *Layout) void {
// Note: output.layout may not be equal to layout here if the layout
// namespace changes while a transactions is inflight.
const output = layout.output;
// Whether the layout demand succeeds or fails, we are done with it and
// need to clean up
defer {
output.inflight.layout_demand.?.deinit();
output.inflight.layout_demand = null;
server.root.notifyLayoutDemandDone();
}
// Check that the number of proposed dimensions is correct.
if (self.views != 0) {
log.err(
"proposed dimension count ({}) does not match view count ({}), aborting layout demand",
.{ -self.views + @as(i32, @intCast(self.view_boxen.len)), self.view_boxen.len },
);
layout.layout.postError(
.count_mismatch,
"number of proposed view dimensions must match number of views",
);
return;
}
// Apply proposed layout to the inflight state of the target views
var it = output.inflight.wm_stack.iterator(.forward);
var i: u32 = 0;
while (it.next()) |view| {
if (!view.inflight.float and !view.inflight.fullscreen and
view.inflight.tags & output.inflight.tags != 0)
{
const proposed = &self.view_boxen[i];
// Here we apply the offset to align the coords with the origin of the
// usable area and shrink the dimensions to accommodate the border size.
const border_width = if (view.inflight.ssd) server.config.border_width else 0;
view.inflight.box = .{
.x = proposed.x + output.usable_box.x + border_width,
.y = proposed.y + output.usable_box.y + border_width,
.width = proposed.width - 2 * border_width,
.height = proposed.height - 2 * border_width,
};
view.applyConstraints(&view.inflight.box);
// State flowing "backwards" like this is pretty ugly, but I don't
// see a better way to sync this up right now.
if (!view.pending.float and !view.pending.fullscreen) {
view.pending.box = view.inflight.box;
}
i += 1;
}
}
assert(i == self.view_boxen.len);
}