diff options
| -rw-r--r-- | river/ForeignToplevelHandle.zig | 117 | ||||
| -rw-r--r-- | river/Server.zig | 4 | ||||
| -rw-r--r-- | river/View.zig | 22 |
3 files changed, 139 insertions, 4 deletions
diff --git a/river/ForeignToplevelHandle.zig b/river/ForeignToplevelHandle.zig new file mode 100644 index 0000000..1690858 --- /dev/null +++ b/river/ForeignToplevelHandle.zig @@ -0,0 +1,117 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2023 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 ForeignToplevelHandle = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; + +const server = &@import("main.zig").server; + +const View = @import("View.zig"); +const Seat = @import("Seat.zig"); + +wlr_handle: ?*wlr.ForeignToplevelHandleV1 = null, + +foreign_activate: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated) = + wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated).init(handleForeignActivate), +foreign_fullscreen: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen) = + wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen).init(handleForeignFullscreen), +foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) = + wl.Listener(*wlr.ForeignToplevelHandleV1).init(handleForeignClose), + +pub fn map(handle: *ForeignToplevelHandle) void { + const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle); + + assert(handle.wlr_handle == null); + + handle.wlr_handle = wlr.ForeignToplevelHandleV1.create(server.foreign_toplevel_manager) catch { + std.log.err("out of memory", .{}); + return; + }; + + handle.wlr_handle.?.events.request_activate.add(&handle.foreign_activate); + handle.wlr_handle.?.events.request_fullscreen.add(&handle.foreign_fullscreen); + handle.wlr_handle.?.events.request_close.add(&handle.foreign_close); + + if (view.getTitle()) |title| handle.wlr_handle.?.setTitle(title); + if (view.getAppId()) |app_id| handle.wlr_handle.?.setAppId(app_id); +} + +pub fn unmap(handle: *ForeignToplevelHandle) void { + const wlr_handle = handle.wlr_handle orelse return; + + handle.foreign_activate.link.remove(); + handle.foreign_fullscreen.link.remove(); + handle.foreign_close.link.remove(); + + wlr_handle.destroy(); + + handle.wlr_handle = null; +} + +/// Must be called just before the view's inflight state is made current. +pub fn update(handle: *ForeignToplevelHandle) void { + const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle); + + const wlr_handle = handle.wlr_handle orelse return; + + if (view.inflight.output != view.current.output) { + if (view.current.output) |output| wlr_handle.outputLeave(output.wlr_output); + if (view.inflight.output) |output| wlr_handle.outputEnter(output.wlr_output); + } + + wlr_handle.setActivated(view.inflight.focus != 0); + wlr_handle.setFullscreen(view.inflight.output != null and + view.inflight.output.?.inflight.fullscreen == view); +} + +/// Only honors the request if the view is already visible on the seat's +/// currently focused output. +fn handleForeignActivate( + listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated), + event: *wlr.ForeignToplevelHandleV1.event.Activated, +) void { + const handle = @fieldParentPtr(ForeignToplevelHandle, "foreign_activate", listener); + const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle); + const seat = @intToPtr(*Seat, event.seat.data); + + seat.focus(view); + server.root.applyPending(); +} + +fn handleForeignFullscreen( + listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen), + event: *wlr.ForeignToplevelHandleV1.event.Fullscreen, +) void { + const handle = @fieldParentPtr(ForeignToplevelHandle, "foreign_fullscreen", listener); + const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle); + + view.pending.fullscreen = event.fullscreen; + server.root.applyPending(); +} + +fn handleForeignClose( + listener: *wl.Listener(*wlr.ForeignToplevelHandleV1), + _: *wlr.ForeignToplevelHandleV1, +) void { + const handle = @fieldParentPtr(ForeignToplevelHandle, "foreign_close", listener); + const view = @fieldParentPtr(View, "foreign_toplevel_handle", handle); + + view.close(); +} diff --git a/river/Server.zig b/river/Server.zig index 8cb5671..456d3a5 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -61,6 +61,8 @@ new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1), xwayland: if (build_options.xwayland) *wlr.Xwayland else void, new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void, +foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1, + xdg_activation: *wlr.XdgActivationV1, request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate), @@ -111,6 +113,8 @@ pub fn init(self: *Self) !void { self.xwayland.events.new_surface.add(&self.new_xwayland_surface); } + self.foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(self.wl_server); + self.xdg_activation = try wlr.XdgActivationV1.create(self.wl_server); self.xdg_activation.events.request_activate.add(&self.request_activate); self.request_activate.setNotify(handleRequestActivate); diff --git a/river/View.zig b/river/View.zig index 24e6652..7dba9a3 100644 --- a/river/View.zig +++ b/river/View.zig @@ -27,6 +27,7 @@ const wl = @import("wayland").server.wl; const server = &@import("main.zig").server; const util = @import("util.zig"); +const ForeignToplevelHandle = @import("ForeignToplevelHandle.zig"); const Output = @import("Output.zig"); const SceneNodeData = @import("SceneNodeData.zig"); const Seat = @import("Seat.zig"); @@ -147,6 +148,8 @@ float_box: wlr.Box = undefined, /// exiting fullscreen if there is no active layout. post_fullscreen_box: wlr.Box = undefined, +foreign_toplevel_handle: ForeignToplevelHandle = .{}, + pub fn create(impl: Impl) error{OutOfMemory}!*Self { const view = try util.gpa.create(Self); errdefer util.gpa.destroy(view); @@ -216,6 +219,8 @@ pub fn destroy(view: *Self) void { pub fn updateCurrent(view: *Self) void { const config = &server.config; + view.foreign_toplevel_handle.update(); + view.current = view.inflight; view.dropSavedSurfaceTree(); if (view.impl == .xdg_toplevel) { @@ -387,6 +392,8 @@ pub fn map(view: *Self) !void { assert(!view.mapped and !view.destroying); view.mapped = true; + view.foreign_toplevel_handle.map(); + view.pending.borders = !server.config.csdAllowed(view); if (server.input_manager.defaultSeat().focused_output) |output| { @@ -427,14 +434,19 @@ pub fn unmap(view: *Self) void { assert(view.mapped and !view.destroying); view.mapped = false; + view.foreign_toplevel_handle.unmap(); + server.root.applyPending(); } -pub fn notifyTitle(self: *const Self) void { +pub fn notifyTitle(view: *const Self) void { + if (view.foreign_toplevel_handle.wlr_handle) |wlr_handle| { + if (view.getTitle()) |title| wlr_handle.setTitle(title); + } // Send title to all status listeners attached to a seat which focuses this view var seat_it = server.input_manager.seats.first; while (seat_it) |seat_node| : (seat_it = seat_node.next) { - if (seat_node.data.focused == .view and seat_node.data.focused.view == self) { + if (seat_node.data.focused == .view and seat_node.data.focused.view == view) { var client_it = seat_node.data.status_trackers.first; while (client_it) |client_node| : (client_it = client_node.next) { client_node.data.sendFocusedView(); @@ -443,6 +455,8 @@ pub fn notifyTitle(self: *const Self) void { } } -pub fn notifyAppId(_: Self) void { - // TODO reimplement foreign-toplevel-management I guess. +pub fn notifyAppId(view: Self) void { + if (view.foreign_toplevel_handle.wlr_handle) |wlr_handle| { + if (view.getAppId()) |app_id| wlr_handle.setAppId(app_id); + } } |
