river/river/LayoutDemand.zig
Isaac Freund e80b883a47 river-layout: update to v2
This implements the changes to the river-layout protocol proposed
in the previous commit removing river-options.
2021-04-27 00:10:20 +02:00

139 lines
4.8 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, 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 std = @import("std");
const wlr = @import("wlroots");
const wayland = @import("wayland");
const wl = wayland.server.wl;
const util = @import("util.zig");
const Layout = @import("Layout.zig");
const Box = @import("Box.zig");
const Server = @import("Server.zig");
const Output = @import("Output.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const log = std.log.scoped(.layout);
const Error = error{ViewDimensionMismatch};
const timeout_ms = 1000;
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: []Box,
timeout_timer: *wl.EventSource,
pub fn init(layout: *Layout, views: u32) !Self {
const event_loop = layout.output.root.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 = layout.output.root.server.wl_server.nextSerial(),
.views = @intCast(i32, views),
.view_boxen = try util.gpa.alloc(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) callconv(.C) c_int {
log.notice(
"layout demand for layout '{}' on output '{}' timed out",
.{ layout.namespace, layout.output.wlr_output.name },
);
layout.output.layout_demand.?.deinit();
layout.output.layout_demand = null;
layout.output.root.notifyLayoutDemandDone();
return 0;
}
/// Push a set of proposed view dimensions and position to the list
pub fn pushViewDimensions(self: *Self, output: *Output, x: i32, y: i32, width: u32, height: u32) void {
// The client pushed too many dimensions
if (self.views < 0) return;
// Here we apply the offset to align the coords with the origin of the
// usable area and shrink the dimensions to accomodate the border size.
const border_width = output.root.server.config.border_width;
self.view_boxen[self.view_boxen.len - @intCast(usize, self.views)] = .{
.x = x + output.usable_box.x + @intCast(i32, border_width),
.y = y + output.usable_box.y + @intCast(i32, border_width),
.width = if (width > 2 * border_width) width - 2 * border_width else width,
.height = if (height > 2 * border_width) height - 2 * border_width else height,
};
self.views -= 1;
}
/// Apply the proposed layout to the output
pub fn apply(self: *Self, layout: *Layout) void {
const output = layout.output;
// Whether the layout demand succeeds or fails, we are done with it and
// need to clean up
defer {
output.layout_demand.?.deinit();
output.layout_demand = null;
output.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 + @intCast(i32, 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 views
var it = ViewStack(View).iter(output.views.first, .forward, output.pending.tags, Output.arrangeFilter);
var i: u32 = 0;
while (it.next()) |view| : (i += 1) {
if (view.pending.fullscreen) {
view.post_fullscreen_box = self.view_boxen[i];
} else {
view.pending.box = self.view_boxen[i];
}
view.applyConstraints();
}
std.debug.assert(i == self.view_boxen.len);
output.pending.layout = layout;
}