diff --git a/river/Server.zig b/river/Server.zig index 0a7bd94..e730b5e 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -193,7 +193,7 @@ fn handleNewXdgSurface(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wl xdg_surface.resource.postNoMemory(); return; }; - node.view.init(output, getNewViewTags(output), xdg_surface); + node.view.init(output, xdg_surface); } /// This event is raised when the layer_shell recieves a new surface from a client. @@ -259,10 +259,5 @@ fn handleNewXwaylandSurface(listener: *wl.Listener(*wlr.XwaylandSurface), wlr_xw // The View will add itself to the output's view stack on map const output = self.input_manager.defaultSeat().focused_output; const node = util.gpa.create(ViewStack(View).Node) catch return; - node.view.init(output, getNewViewTags(output), wlr_xwayland_surface); -} - -fn getNewViewTags(output: *Output) u32 { - const tags = output.current.tags & output.spawn_tagmask; - return if (tags != 0) tags else output.current.tags; + node.view.init(output, wlr_xwayland_surface); } diff --git a/river/View.zig b/river/View.zig index 1a6c0ae..0435e62 100644 --- a/river/View.zig +++ b/river/View.zig @@ -126,11 +126,16 @@ foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) = request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) = wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate), -pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void { +pub fn init(self: *Self, output: *Output, surface: anytype) void { + const initial_tags = blk: { + const tags = output.current.tags & output.spawn_tagmask; + break :blk if (tags != 0) tags else output.current.tags; + }; + self.* = .{ .output = output, - .current = .{ .tags = tags }, - .pending = .{ .tags = tags }, + .current = .{ .tags = initial_tags }, + .pending = .{ .tags = initial_tags }, .saved_buffers = std.ArrayList(SavedBuffer).init(util.gpa), }; diff --git a/river/XwaylandUnmanaged.zig b/river/XwaylandUnmanaged.zig index 96f6f3f..4eb6a88 100644 --- a/river/XwaylandUnmanaged.zig +++ b/river/XwaylandUnmanaged.zig @@ -17,6 +17,8 @@ const Self = @This(); const std = @import("std"); +const assert = std.debug.assert; + const wlr = @import("wlroots"); const wl = @import("wayland").server.wl; @@ -24,6 +26,11 @@ const server = &@import("main.zig").server; const util = @import("util.zig"); const Box = @import("Box.zig"); +const View = @import("View.zig"); +const XwaylandView = @import("XwaylandView.zig"); +const ViewStack = @import("view_stack.zig").ViewStack; + +const log = std.log.scoped(.xwayland); /// The corresponding wlroots object xwayland_surface: *wlr.XwaylandSurface, @@ -34,6 +41,10 @@ request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) = destroy: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleDestroy), map: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleMap), unmap: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleUnmap), +set_override_redirect: wl.Listener(*wlr.XwaylandSurface) = + wl.Listener(*wlr.XwaylandSurface).init(handleSetOverrideRedirect), + +// Listeners that are only active while mapped commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), pub fn init(self: *Self, xwayland_surface: *wlr.XwaylandSurface) void { @@ -69,7 +80,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS } /// Called when the xwayland surface is mapped, or ready to display on-screen. -fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { +pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "map", listener); // Add self to the list of unmanaged views in the root @@ -110,3 +121,29 @@ fn handleCommit(_: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { var it = server.root.outputs.first; while (it) |node| : (it = node.next) node.data.damage.addWhole(); } + +fn handleSetOverrideRedirect( + listener: *wl.Listener(*wlr.XwaylandSurface), + xwayland_surface: *wlr.XwaylandSurface, +) void { + const self = @fieldParentPtr(Self, "set_override_redirect", listener); + + log.debug("xwayland surface unset override redirect, switching to managed", .{}); + + assert(!xwayland_surface.override_redirect); + + if (xwayland_surface.mapped) handleUnmap(&self.unmap, xwayland_surface); + handleDestroy(&self.destroy, xwayland_surface); + + // The View will add itself to the output's view stack on map + const output = server.input_manager.defaultSeat().focused_output; + const node = util.gpa.create(ViewStack(View).Node) catch { + log.err("out of memory", .{}); + return; + }; + node.view.init(output, xwayland_surface); + + if (xwayland_surface.mapped) { + XwaylandView.handleMap(&node.view.impl.xwayland_view.map, xwayland_surface); + } +} diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 0fbc25a..153ccee 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -17,16 +17,22 @@ const Self = @This(); const std = @import("std"); +const assert = std.debug.assert; const math = std.math; + const wlr = @import("wlroots"); const wl = @import("wayland").server.wl; const server = &@import("main.zig").server; +const util = @import("util.zig"); const Box = @import("Box.zig"); const View = @import("View.zig"); const ViewStack = @import("view_stack.zig").ViewStack; const XdgPopup = @import("XdgPopup.zig"); +const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); + +const log = std.log.scoped(.xwayland); /// The view this xwayland view implements view: *View, @@ -45,6 +51,8 @@ map: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init( unmap: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleUnmap), request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) = wl.Listener(*wlr.XwaylandSurface.event.Configure).init(handleRequestConfigure), +set_override_redirect: wl.Listener(*wlr.XwaylandSurface) = + wl.Listener(*wlr.XwaylandSurface).init(handleSetOverrideRedirect), // Listeners that are only active while the view is mapped commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), @@ -176,7 +184,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS } /// Called when the xwayland surface is mapped, or ready to display on-screen. -fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { +pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { const self = @fieldParentPtr(Self, "map", listener); const view = self.view; @@ -223,7 +231,7 @@ fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wl } view.map() catch { - std.log.err("out of memory", .{}); + log.err("out of memory", .{}); surface.resource.getClient().postNoMemory(); }; } @@ -262,6 +270,32 @@ fn handleRequestConfigure( self.configure(); } +fn handleSetOverrideRedirect( + listener: *wl.Listener(*wlr.XwaylandSurface), + xwayland_surface: *wlr.XwaylandSurface, +) void { + const self = @fieldParentPtr(Self, "set_override_redirect", listener); + + log.debug("xwayland surface set override redirect, switching to unmanaged", .{}); + + assert(xwayland_surface.override_redirect); + + if (xwayland_surface.mapped) handleUnmap(&self.unmap, xwayland_surface); + handleDestroy(&self.destroy, xwayland_surface); + + // The unmanged surface will add itself to the list of unmanaged views + // in Root when it is mapped. + const node = util.gpa.create(std.TailQueue(XwaylandUnmanaged).Node) catch { + log.err("out of memory", .{}); + return; + }; + node.data.init(xwayland_surface); + + if (xwayland_surface.mapped) { + XwaylandUnmanaged.handleMap(&node.data.map, xwayland_surface); + } +} + fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { const self = @fieldParentPtr(Self, "commit", listener);