git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitcfcf2cbd8a292b5304cd47f57c7a6c7c00b0f58e
parent7df3c2608b
authorIsaac Freund <[email protected]>
date2024-07-19 13:21
WindowManager: create global

 build.zig               |  3 ++
 river/Server.zig        |  4 +++
 river/WindowManager.zig | 93 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 100 insertions(+)

diff --git a/build.zig b/build.zig
index 8cb11f8..3766eb4 100644
--- a/build.zig
+++ b/build.zig
@@ -99,6 +99,7 @@ pub fn build(b: *Build) !void {
     scanner.addSystemProtocol("unstable/tablet/tablet-unstable-v2.xml");
     scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
 
+    scanner.addCustomProtocol("protocol/river-window-management-v1.xml");
     scanner.addCustomProtocol("protocol/river-control-unstable-v1.xml");
     scanner.addCustomProtocol("protocol/river-layout-v3.xml");
     scanner.addCustomProtocol("protocol/wlr-layer-shell-unstable-v1.xml");
@@ -124,6 +125,8 @@ pub fn build(b: *Build) !void {
     scanner.generate("ext_session_lock_manager_v1", 1);
     scanner.generate("wp_cursor_shape_manager_v1", 1);
 
+    scanner.generate("river_window_manager_v1", 1);
+
     scanner.generate("zriver_control_v1", 1);
     scanner.generate("river_layout_manager_v3", 2);
 
diff --git a/river/Server.zig b/river/Server.zig
index 459beae..8ec9201 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -36,6 +36,7 @@ const Root = @import("Root.zig");
 const Seat = @import("Seat.zig");
 const SceneNodeData = @import("SceneNodeData.zig");
 const TabletTool = @import("TabletTool.zig");
+const WindowManager = @import("WindowManager.zig");
 const XdgDecoration = @import("XdgDecoration.zig");
 const XdgToplevel = @import("XdgToplevel.zig");
 const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
@@ -86,6 +87,7 @@ root: Root,
 config: Config,
 idle_inhibit_manager: IdleInhibitManager,
 lock_manager: LockManager,
+wm: WindowManager,
 
 xwayland: if (build_options.xwayland) ?*wlr.Xwayland else void = if (build_options.xwayland) null,
 new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void =
@@ -157,6 +159,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
         .input_manager = undefined,
         .idle_inhibit_manager = undefined,
         .lock_manager = undefined,
+        .wm = undefined,
     };
 
     if (renderer.getTextureFormats(@intFromEnum(wlr.BufferCap.dmabuf)) != null) {
@@ -178,6 +181,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
     try server.input_manager.init();
     try server.idle_inhibit_manager.init();
     try server.lock_manager.init();
+    try server.wm.init();
 
     server.xdg_shell.events.new_toplevel.add(&server.new_xdg_toplevel);
     server.xdg_decoration_manager.events.new_toplevel_decoration.add(&server.new_toplevel_decoration);
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
new file mode 100644
index 0000000..38edd54
--- /dev/null
+++ b/river/WindowManager.zig
@@ -0,0 +1,93 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2024 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 WindowManager = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const wl = @import("wayland").server.wl;
+const river = @import("wayland").server.river;
+
+const server = &@import("main.zig").server;
+const util = @import("util.zig");
+
+const log = std.log.scoped(.wm);
+
+global: *wl.Global,
+server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleServerDestroy),
+
+/// The active window manager, if any.
+wm_v1: ?*river.WindowManagerV1 = null,
+
+pub fn init(wm: *WindowManager) !void {
+    wm.* = .{
+        .global = try wl.Global.create(server.wl_server, river.WindowManagerV1, 1, *WindowManager, wm, bind),
+    };
+
+    server.wl_server.addDestroyListener(&wm.server_destroy);
+}
+
+fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
+    const wm: *WindowManager = @fieldParentPtr("server_destroy", listener);
+    wm.global.destroy();
+}
+
+fn bind(client: *wl.Client, wm: *WindowManager, version: u32, id: u32) void {
+    const wm_v1 = river.WindowManagerV1.create(client, version, id) catch {
+        client.postNoMemory();
+        log.err("out of memory", .{});
+        return;
+    };
+
+    if (wm.wm_v1 != null) {
+        wm_v1.sendUnavailable();
+        wm_v1.setHandler(?*anyopaque, handleRequestInert, null, null);
+        return;
+    }
+
+    wm.wm_v1 = wm_v1;
+    wm_v1.setHandler(*WindowManager, handleRequest, null, wm);
+}
+
+fn handleRequestInert(
+    wm_v1: *river.WindowManagerV1,
+    request: river.WindowManagerV1.Request,
+    _: ?*anyopaque,
+) void {
+    if (request == .destroy) wm_v1.destroy();
+}
+
+fn handleRequest(
+    wm_v1: *river.WindowManagerV1,
+    request: river.WindowManagerV1.Request,
+    wm: *WindowManager,
+) void {
+    assert(wm.wm_v1 == wm_v1);
+    switch (request) {
+        .stop => {
+            wm.wm_v1 = null;
+            wm_v1.sendFinished();
+            wm_v1.setHandler(?*anyopaque, handleRequestInert, null, null);
+        },
+        .destroy => {
+            // XXX send protocol error
+        },
+        .ack_update => |_| {},
+        .commit => {},
+        .get_seat => |_| {},
+        .get_shell_surface => |_| {},
+    }
+}