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

commitbc187cb552dfbf7e20e5476f10d38f269a9b47dd
parentab2ebd1a22
authorAnatolii Smolianinov <[email protected]>
date2026-03-23 12:29
wlr-foreign-toplevel-management: implement subset

Implement a small subset of the protocol which is used by fcitx5 to
provide critical input method functionality, namely window-specific
input method state.

It would be nice if the input method protocol itself provided this
information in the future...

 river/Server.zig |  3 +++
 river/Window.zig | 26 ++++++++++++++++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/river/Server.zig b/river/Server.zig
index 89ee50f..f990fb0 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -84,6 +84,7 @@ screencopy_manager: *wlr.ScreencopyManagerV1,
 image_copy_capture_manager: *wlr.ExtImageCopyCaptureManagerV1,
 output_image_capture_source_manager: *wlr.ExtOutputImageCaptureSourceManagerV1,
 
+wlr_foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
 foreign_toplevel_list: *wlr.ExtForeignToplevelListV1,
 toplevel_capture_source_manager: *wlr.ExtForeignToplevelImageCaptureSourceManagerV1,
 
@@ -166,6 +167,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
         .image_copy_capture_manager = try wlr.ExtImageCopyCaptureManagerV1.create(wl_server, 1),
         .output_image_capture_source_manager = try wlr.ExtOutputImageCaptureSourceManagerV1.create(wl_server, 1),
 
+        .wlr_foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(wl_server),
         .foreign_toplevel_list = try wlr.ExtForeignToplevelListV1.create(wl_server, 1),
         .toplevel_capture_source_manager = try wlr.ExtForeignToplevelImageCaptureSourceManagerV1.create(wl_server, 1),
 
@@ -379,6 +381,7 @@ fn blocklist(server: *Server, global: *const wl.Global) bool {
         global == server.screencopy_manager.global or
         global == server.image_copy_capture_manager.global or
         global == server.output_image_capture_source_manager.global or
+        global == server.wlr_foreign_toplevel_manager.global or
         global == server.foreign_toplevel_list.global or
         global == server.toplevel_capture_source_manager.global or
         global == server.export_dmabuf_manager.global or
diff --git a/river/Window.zig b/river/Window.zig
index 9b05f45..5963a59 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -266,6 +266,7 @@ rendering_requested: RenderingRequested = .init,
 box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
 
 foreign_toplevel_handle: ?*wlr.ExtForeignToplevelHandleV1 = null,
+wlr_toplevel_handle: ?*wlr.ForeignToplevelHandleV1 = null,
 
 pub fn create(impl: Impl) error{OutOfMemory}!*Window {
     assert(impl != .destroying);
@@ -430,6 +431,16 @@ pub fn manageStart(window: *Window) void {
                     }
                 }
 
+                if (window.wlr_toplevel_handle == null) {
+                    if (wlr.ForeignToplevelHandleV1.create(server.wlr_foreign_toplevel_manager)) |handle| {
+                        window.wlr_toplevel_handle = handle;
+                        if (window.getTitle()) |title| handle.setTitle(title);
+                        if (window.getAppId()) |app_id| handle.setAppId(app_id);
+                    } else |_| {
+                        log.err("failed to create wlr foreign toplevel handle", .{});
+                    }
+                }
+
                 break :blk window_v1;
             };
 
@@ -772,6 +783,10 @@ pub fn manageFinish(window: *Window) bool {
         break :blk false;
     };
 
+    if (window.wlr_toplevel_handle) |handle| {
+        handle.setActivated(activated);
+    }
+
     const width, const height = blk: {
         if (wm_requested.fullscreen) |output| {
             const width, const height = output.sent.dimensions();
@@ -1150,6 +1165,11 @@ pub fn unmap(window: *Window) void {
         window.foreign_toplevel_handle = null;
     }
 
+    if (window.wlr_toplevel_handle) |handle| {
+        handle.destroy();
+        window.wlr_toplevel_handle = null;
+    }
+
     {
         var it = server.input_manager.seats.iterator(.forward);
         while (it.next()) |seat| {
@@ -1170,6 +1190,9 @@ pub fn notifyTitle(window: *Window) void {
             .app_id = window.getAppId(),
         });
     }
+    if (window.wlr_toplevel_handle) |handle| {
+        if (window.getTitle()) |title| handle.setTitle(title);
+    }
 }
 
 pub fn notifyAppId(window: *Window) void {
@@ -1182,4 +1205,7 @@ pub fn notifyAppId(window: *Window) void {
             .app_id = window.getAppId(),
         });
     }
+    if (window.wlr_toplevel_handle) |handle| {
+        if (window.getAppId()) |app_id| handle.setAppId(app_id);
+    }
 }