From 237fd0ba93cd8b2ea7019ed264b6a5543b020369 Mon Sep 17 00:00:00 2001 From: Mohammadreza Khani Date: Tue, 14 Jul 2026 22:19:17 +0330 Subject: [PATCH] logiguard fork: add Window::activate_with_token for externally-minted xdg-activation tokens Mutter honors xdg_activation_v1.activate() only when the token carries a valid grab serial on the surface being activated. activate_window() mints its own token using this client's KeyboardEnter serial, which is stale after a tray-menu click (the tray popup held focus, not the app window). Add Window::activate_with_token(&str) so callers can feed in a token minted by GTK (via g_app_launch_context_get_startup_notify_id) on the connection that actually received the user gesture. That token carries the real grab serial + focused surface, which Mutter accepts. - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token with default impl that falls back to activate(). - crates/gpui/src/window.rs: public Window::activate_with_token. - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow impl that issues xdg_activation_v1.activate(token, surface) directly. Co-Authored-By: Claude Fable 5 --- crates/gpui/src/platform.rs | 10 ++++++++++ crates/gpui/src/window.rs | 9 +++++++++ crates/gpui_linux/src/linux/wayland/window.rs | 16 ++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 00cd9d1..84aedd4 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -623,6 +623,16 @@ pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle { answers: &[PromptButton], ) -> Option>; fn activate(&self); + /// Activate with an externally-generated xdg-activation token. + /// Wayland: issues `xdg_activation_v1.activate(token, surface)` directly, + /// bypassing this client's own (possibly stale) serial. The caller is + /// responsible for obtaining a token that carries a valid grab serial — + /// e.g. from GTK's `GdkAppLaunchContext::startup_notify_id()` on the + /// connection that received the user gesture. + /// Non-Wayland backends: ignore the token and fall back to `activate()`. + fn activate_with_token(&self, _token: &str) { + self.activate(); + } fn is_active(&self) -> bool; fn is_hovered(&self) -> bool; fn background_appearance(&self) -> WindowBackgroundAppearance; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 659a34d..4b49aef 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4883,6 +4883,15 @@ impl Window { self.platform_window.activate(); } + /// Focus the current window using an externally-generated xdg-activation token. + /// On Wayland this issues `xdg_activation_v1.activate(token, surface)`, which + /// Mutter honors when the token carries a valid grab serial (e.g. minted by + /// GTK on the connection that received the user gesture). On other platforms + /// this is equivalent to [`activate_window`](Self::activate_window). + pub fn activate_with_token(&self, token: &str) { + self.platform_window.activate_with_token(token); + } + /// Minimize the current window at the platform level. pub fn minimize_window(&self) { self.platform_window.minimize(); diff --git a/crates/gpui_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index 59db9dd..5cf07a5 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -1263,6 +1263,22 @@ impl PlatformWindow for WaylandWindow { } } + fn activate_with_token(&self, token: &str) { + // Activate using a token minted externally (e.g. by GTK on the + // connection that received the tray-click gesture). We pass it straight + // to `xdg_activation_v1.activate(token, surface)` — Mutter honors it + // because the token carries a valid grab serial + the focused surface, + // unlike `activate()` above which may use a stale serial. + let state = self.borrow(); + if let Some(activation) = &state.globals.activation { + activation.activate(token.to_string(), &state.surface); + } else { + // No xdg-activation global (e.g. older compositor): best-effort fallback. + drop(state); + self.activate(); + } + } + fn is_active(&self) -> bool { self.borrow().active }