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

213 lines
7.7 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 math = std.math;
const mem = std.mem;
const wlr = @import("wlroots");
const wayland = @import("wayland");
const wl = wayland.server.wl;
const river = wayland.server.river;
const server = &@import("main.zig").server;
const util = @import("util.zig");
const Output = @import("Output.zig");
const View = @import("View.zig");
const LayoutDemand = @import("LayoutDemand.zig");
const log = std.log.scoped(.layout);
layout: *river.LayoutV3,
namespace: []const u8,
output: *Output,
pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namespace: []const u8) !void {
const layout = try river.LayoutV3.create(client, version, id);
if (namespaceInUse(namespace, output, client)) {
layout.sendNamespaceInUse();
layout.setHandler(?*anyopaque, handleRequestInert, null, null);
return;
}
const node = try util.gpa.create(std.TailQueue(Self).Node);
errdefer util.gpa.destroy(node);
node.data = .{
.layout = layout,
.namespace = try util.gpa.dupe(u8, namespace),
.output = output,
};
output.layouts.append(node);
layout.setHandler(*Self, handleRequest, handleDestroy, &node.data);
// If the namespace matches that of the output, set the layout as
// the active one of the output and arrange it.
if (mem.eql(u8, namespace, output.layoutNamespace())) {
output.layout = &node.data;
server.root.applyPending();
}
}
/// Returns true if the given namespace is already in use on the given output
/// or on another output by a different client.
fn namespaceInUse(namespace: []const u8, output: *Output, client: *wl.Client) bool {
var output_it = server.root.active_outputs.iterator(.forward);
while (output_it.next()) |o| {
var layout_it = output.layouts.first;
if (o == output) {
// On this output, no other layout can have our namespace.
while (layout_it) |layout_node| : (layout_it = layout_node.next) {
if (mem.eql(u8, namespace, layout_node.data.namespace)) return true;
}
} else {
// Layouts on other outputs may share the namespace, if they come from the same client.
while (layout_it) |layout_node| : (layout_it = layout_node.next) {
if (mem.eql(u8, namespace, layout_node.data.namespace) and
client != layout_node.data.layout.getClient()) return true;
}
}
}
return false;
}
/// This exists to handle layouts that have been rendered inert (due to the
/// namespace already being in use) until the client destroys them.
fn handleRequestInert(layout: *river.LayoutV3, request: river.LayoutV3.Request, _: ?*anyopaque) void {
if (request == .destroy) layout.destroy();
}
/// Send a layout demand to the client
pub fn startLayoutDemand(self: *Self, views: u32) void {
log.debug(
"starting layout demand '{s}' on output '{s}'",
.{ self.namespace, self.output.wlr_output.name },
);
assert(self.output.inflight.layout_demand == null);
self.output.inflight.layout_demand = LayoutDemand.init(self, views) catch {
log.err("failed starting layout demand", .{});
return;
};
self.layout.sendLayoutDemand(
views,
@intCast(self.output.usable_box.width),
@intCast(self.output.usable_box.height),
self.output.pending.tags,
self.output.inflight.layout_demand.?.serial,
);
server.root.inflight_layout_demands += 1;
}
fn handleRequest(layout: *river.LayoutV3, request: river.LayoutV3.Request, self: *Self) void {
switch (request) {
.destroy => layout.destroy(),
// We receive this event when the client wants to push a view dimension proposal
// to the layout demand matching the serial.
.push_view_dimensions => |req| {
log.debug(
"layout '{s}' on output '{s}' pushed view dimensions: {} {} {} {}",
.{ self.namespace, self.output.wlr_output.name, req.x, req.y, req.width, req.height },
);
if (self.output.inflight.layout_demand) |*layout_demand| {
// We can't raise a protocol error when the serial is old/wrong
// because we do not keep track of old serials server-side.
// Therefore, simply ignore requests with old/wrong serials.
if (layout_demand.serial != req.serial) return;
layout_demand.pushViewDimensions(
req.x,
req.y,
@min(math.maxInt(u31), req.width),
@min(math.maxInt(u31), req.height),
);
}
},
// We receive this event when the client wants to mark the proposed layout
// of the layout demand matching the serial as done.
.commit => |req| {
log.debug(
"layout '{s}' on output '{s}' commited",
.{ self.namespace, self.output.wlr_output.name },
);
if (self.output.inflight.layout_demand) |*layout_demand| {
// We can't raise a protocol error when the serial is old/wrong
// because we do not keep track of old serials server-side.
// Therefore, simply ignore requests with old/wrong serials.
if (layout_demand.serial == req.serial) layout_demand.apply(self);
}
const new_name = mem.sliceTo(req.layout_name, 0);
if (self.output.layout_name == null or
!mem.eql(u8, self.output.layout_name.?, new_name))
{
const owned = util.gpa.dupeZ(u8, new_name) catch {
log.err("out of memory", .{});
return;
};
if (self.output.layout_name) |name| util.gpa.free(name);
self.output.layout_name = owned;
self.output.status.sendLayoutName(self.output);
}
},
}
}
fn handleDestroy(_: *river.LayoutV3, self: *Self) void {
self.destroy();
}
pub fn destroy(self: *Self) void {
log.debug(
"destroying layout '{s}' on output '{s}'",
.{ self.namespace, self.output.wlr_output.name },
);
// Remove layout from the list
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
self.output.layouts.remove(node);
// If we are the currently active layout of an output, clean up.
if (self.output.layout == self) {
self.output.layout = null;
if (self.output.inflight.layout_demand) |*layout_demand| {
layout_demand.deinit();
self.output.inflight.layout_demand = null;
server.root.notifyLayoutDemandDone();
}
if (self.output.layout_name) |name| {
util.gpa.free(name);
self.output.layout_name = null;
self.output.status.sendLayoutNameClear(self.output);
}
}
self.layout.setHandler(?*anyopaque, handleRequestInert, null, null);
util.gpa.free(self.namespace);
util.gpa.destroy(node);
}