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

commit8b292e8dd6f3682f0a9adeb7119500c3ee31771e
parentb923cc67ae
authorIsaac Freund <[email protected]>
date2026-03-29 19:32
Server: implement color management protocols

Thank you wlroots!

 build.zig        |  6 +++++-
 build.zig.zon    |  4 ++--
 river/Scene.zig  |  1 +
 river/Server.zig | 30 +++++++++++++++++++++++++++++-
 4 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/build.zig b/build.zig
index 1433d42..46b515b 100644
--- a/build.zig
+++ b/build.zig
@@ -74,8 +74,10 @@ pub fn build(b: *Build) !void {
 
     const scanner = Scanner.create(b, .{});
 
-    scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
     scanner.addSystemProtocol("stable/tablet/tablet-v2.xml");
+    scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
+    scanner.addSystemProtocol("staging/color-management/color-management-v1.xml");
+    scanner.addSystemProtocol("staging/color-representation/color-representation-v1.xml");
     scanner.addSystemProtocol("staging/cursor-shape/cursor-shape-v1.xml");
     scanner.addSystemProtocol("staging/ext-session-lock/ext-session-lock-v1.xml");
     scanner.addSystemProtocol("staging/tearing-control/tearing-control-v1.xml");
@@ -114,6 +116,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("wp_tearing_control_manager_v1", 1);
+    scanner.generate("wp_color_manager_v1", 2);
+    scanner.generate("wp_color_representation_manager_v1", 1);
 
     scanner.generate("river_window_manager_v1", 4);
     scanner.generate("river_xkb_bindings_v1", 2);
diff --git a/build.zig.zon b/build.zig.zon
index 9030dd2..d6ea98a 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -20,8 +20,8 @@
             .hash = "wayland-0.5.0-lQa1knz8AQCh08NA8BeQrwJB9U3CfqcVAdHZYGRKIGuu",
         },
         .wlroots = .{
-            .url = "git+https://codeberg.org/ifreund/zig-wlroots?ref=master#22180881fd3f67b2449a34dd7dd978c3291ccc15",
-            .hash = "wlroots-0.20.0-dev-jmOlcu8pBACQUTdOlveX6Evwnm4BeqEANGGwgxR-pSHn",
+            .url = "git+https://codeberg.org/ifreund/zig-wlroots?ref=master#eb7631742812665c10ff3a9b4b069c3d0cc2017b",
+            .hash = "wlroots-0.20.0-dev-jmOlcuxBBAC74vcynqYuP8sjbyLrH6cnNlKzhXKg8zVO",
         },
         .xkbcommon = .{
             .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.4.0.tar.gz",
diff --git a/river/Scene.zig b/river/Scene.zig
index f2359e0..5a144bf 100644
--- a/river/Scene.zig
+++ b/river/Scene.zig
@@ -54,6 +54,7 @@ pub fn init(scene: *Scene) !void {
     errdefer wlr_scene.tree.node.destroy();
 
     if (server.linux_dmabuf) |linux_dmabuf| wlr_scene.setLinuxDmabufV1(linux_dmabuf);
+    if (server.color_manager) |color_manager| wlr_scene.setColorManagerV1(color_manager);
 
     const interactive_tree = try wlr_scene.tree.createSceneTree();
     const drag_icons = try wlr_scene.tree.createSceneTree();
diff --git a/river/Server.zig b/river/Server.zig
index 1fc6a41..44c30fc 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -9,7 +9,9 @@ const assert = std.debug.assert;
 const mem = std.mem;
 const posix = std.posix;
 const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
+const wayland = @import("wayland");
+const wl = wayland.server.wl;
+const wp = wayland.server.wp;
 
 const c = @import("c.zig").c;
 const util = @import("util.zig");
@@ -57,6 +59,9 @@ linux_drm_syncobj_manager: ?*wlr.LinuxDrmSyncobjManagerV1 = null,
 single_pixel_buffer_manager: *wlr.SinglePixelBufferManagerV1,
 alpha_modifier: *wlr.AlphaModifierV1,
 
+color_manager: ?*wlr.ColorManagerV1 = null,
+color_representation_manager: *wlr.ColorRepresentationManagerV1,
+
 viewporter: *wlr.Viewporter,
 fractional_scale_manager: *wlr.FractionalScaleManagerV1,
 compositor: *wlr.Compositor,
@@ -135,6 +140,8 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
         .single_pixel_buffer_manager = try wlr.SinglePixelBufferManagerV1.create(wl_server),
         .alpha_modifier = try wlr.AlphaModifierV1.create(wl_server),
 
+        .color_representation_manager = try wlr.ColorRepresentationManagerV1.createWithRenderer(wl_server, 1, renderer),
+
         .viewporter = try wlr.Viewporter.create(wl_server),
         .fractional_scale_manager = try wlr.FractionalScaleManagerV1.create(wl_server, 1),
         .compositor = compositor,
@@ -182,6 +189,23 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
         }
     }
 
+    if (renderer.features.input_color_transform) {
+        const render_intents: []const wp.ColorManagerV1.RenderIntent = &.{.perceptual};
+        const transfer_functions = renderer.transferFunctionList();
+        defer std.c.free(transfer_functions.ptr);
+        const primaries = renderer.primariesList();
+        defer std.c.free(primaries.ptr);
+        server.color_manager = try wlr.ColorManagerV1.create(wl_server, 2, .{
+            .features = .{
+                .parametric = true,
+                .set_mastering_display_primaries = true,
+            },
+            .render_intents = render_intents,
+            .transfer_functions = transfer_functions,
+            .primaries = primaries,
+        });
+    }
+
     if (build_options.xwayland and runtime_xwayland) {
         server.xwayland = try wlr.Xwayland.create(wl_server, compositor, false);
         server.xwayland.?.events.new_surface.add(&server.new_xsurface);
@@ -296,6 +320,9 @@ fn allowlist(server: *Server, global: *const wl.Global) bool {
     if (server.linux_drm_syncobj_manager) |linux_drm_syncobj_manager| {
         if (global == linux_drm_syncobj_manager.global) return true;
     }
+    if (server.color_manager) |color_manager| {
+        if (global == color_manager.global) return true;
+    }
 
     // We must use the getInterface() approach for dynamically created globals
     // such as wl_output and wl_seat since the wl_global_create() function will
@@ -314,6 +341,7 @@ fn allowlist(server: *Server, global: *const wl.Global) bool {
         global == server.shm.global or
         global == server.single_pixel_buffer_manager.global or
         global == server.alpha_modifier.global or
+        global == server.color_representation_manager.global or
         global == server.viewporter.global or
         global == server.fractional_scale_manager.global or
         global == server.compositor.global or