// 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 . 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); }