Repository: alacritty/alacritty (language: rust)

Issue:
Pass xdg-activation token through `alacritty msg create-window`
Currently, alacritty correctly activates itself on opening using the token from the compositor (`XDG_ACTIVATION_TOKEN`). However, spawning a new window through `alacritty msg create-window` doesn't thread through the token, so the new window cannot activate itself.

My use-case is a compositor bind to spawn a terminal which looks like: `alacritty msg create-window || alacritty`. This way, if an alacritty process already exists, it is re-used for (slightly) faster startup and less resource use. However, this currently doesn't work with xdg-activation because the token isn't passed through `msg create-window`.

I imagine it can become more of a problem as compositors start to adopt stricter new window autofocus policies, or it can break the various "show spinning cursor until the app opens" things.

### System

OS: Linux
Version: alacritty 0.14.0
Linux/BSD: Wayland, niri



Discussion / hints:
Honestly if it was up to me, it probably wouldn't work at all. The only reason why it does is because Winit just handles it automatically.

The idea of capturing a random string and sending it along the IPC message doesn't really excite me. Though I guess it would essentially be no effort to do so?

Generally speaking though I'd consider any compositor behaving strangely without it just bad, so I'm not particularly worried about it.
I guess the point is to avoid keyboard focus stealing like you can do with `wl-copy` where you don't have the `wlr_data_control` around, so windows don't get randomly focused to steal the keyboard input.

Generally passing token is fairly simple.
> I guess the point is to avoid keyboard focus stealing like you can do with wl-copy where you don't have the wlr_data_control around, so windows don't get randomly focused to steal the keyboard input.

Yeah but people actually **want** that though, right? Also don't we unset the activation token after startup anyway, so it's never visible to applications running inside of Alacritty?
yeah, we unset it, since it shouldn't be passed further. The by design is _not focus steal_ and i think the token is the only thing that kind of makes it work.

It does mean that if you run e.g. `wl-copy` via something that activates it'll work, but it means that _user_ wanted to run it in a first place.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f23f442fcce..882c7c1c5d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ Notable changes to the `alacritty_terminal` crate are documented in its
 
 - Config option `window.level = "AlwaysOnTop"` to force Alacritty to always be the toplevel window
 - Escape sequence to move cursor forward tabs ( CSI Ps I )
+- Pass activation token in `alacritty msg create-window` on Wayland/X11
 
 ### Changed
 
diff --git a/Cargo.lock b/Cargo.lock
index e93272f175f..b7a2d9b81e1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2526,9 +2526,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winit"
-version = "0.30.7"
+version = "0.30.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dba50bc8ef4b6f1a75c9274fb95aa9a8f63fbc66c56f391bd85cf68d51e7b1a3"
+checksum = "f5d74280aabb958072864bff6cfbcf9025cf8bfacdde5e32b5e12920ef703b0f"
 dependencies = [
  "ahash",
  "android-activity",
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml
index eff0b0896ff..6d05983a605 100644
--- a/alacritty/Cargo.toml
+++ b/alacritty/Cargo.toml
@@ -41,7 +41,7 @@ tempfile = "3.12.0"
 toml = "0.8.2"
 toml_edit = "0.22.21"
 unicode-width = "0.1"
-winit = { version = "0.30.7", default-features = false, features = ["rwh_06", "serde"] }
+winit = { version = "0.30.8", default-features = false, features = ["rwh_06", "serde"] }
 
 [build-dependencies]
 gl_generator = "0.14.0"
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs
index bb0a24f4bd7..5010ffc87c0 100644
--- a/alacritty/src/cli.rs
+++ b/alacritty/src/cli.rs
@@ -300,6 +300,11 @@ pub struct WindowOptions {
     /// The window tabbing identifier to use when building a window.
     pub window_tabbing_id: Option<String>,
 
+    #[clap(skip)]
+    #[cfg(not(any(target_os = "macos", windows)))]
+    /// `ActivationToken` that we pass to winit.
+    pub activation_token: Option<String>,
+
     /// Override configuration file options [example: 'cursor.style="Beam"'].
     #[clap(short = 'o', long, num_args = 1..)]
     option: Vec<String>,
diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs
index 1c8089bcea1..fe40fab52f6 100644
--- a/alacritty/src/display/window.rs
+++ b/alacritty/src/display/window.rs
@@ -2,6 +2,8 @@
 use winit::platform::startup_notify::{
     self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify,
 };
+#[cfg(not(any(target_os = "macos", windows)))]
+use winit::window::ActivationToken;
 
 #[cfg(all(not(feature = "x11"), not(any(target_os = "macos", windows))))]
 use winit::platform::wayland::WindowAttributesExtWayland;
@@ -38,6 +40,7 @@ use winit::window::{
 
 use alacritty_terminal::index::Point;
 
+use crate::cli::WindowOptions;
 use crate::config::window::{Decorations, Identity, WindowConfig};
 use crate::config::UiConfig;
 use crate::display::SizeInfo;
@@ -124,9 +127,7 @@ impl Window {
         event_loop: &ActiveEventLoop,
         config: &UiConfig,
         identity: &Identity,
-        #[rustfmt::skip]
-        #[cfg(target_os = "macos")]
-        tabbing_id: &Option<String>,
+        _options: &mut WindowOptions,
         #[rustfmt::skip]
         #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
         x11_visual: Option<X11VisualInfo>,
@@ -138,7 +139,7 @@ impl Window {
             #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
             x11_visual,
             #[cfg(target_os = "macos")]
-            tabbing_id,
+            &_options.window_tabbing_id.take(),
         );
 
         if let Some(position) = config.window.position {
@@ -147,7 +148,12 @@ impl Window {
         }
 
         #[cfg(not(any(target_os = "macos", windows)))]
-        if let Some(token) = event_loop.read_token_from_env() {
+        if let Some(token) = _options
+            .activation_token
+            .take()
+            .map(ActivationToken::from_raw)
+            .or_else(|| event_loop.read_token_from_env())
+        {
             log::debug!("Activating window with token: {token:?}");
             window_attributes = window_attributes.with_activation_token(token);
 
diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs
index 6bbf8dfd5d3..5382e475170 100644
--- a/alacritty/src/main.rs
+++ b/alacritty/src/main.rs
@@ -55,6 +55,8 @@ mod gl {
 
 #[cfg(unix)]
 use crate::cli::MessageOptions;
+#[cfg(not(any(target_os = "macos", windows)))]
+use crate::cli::SocketMessage;
 use crate::cli::{Options, Subcommands};
 use crate::config::monitor::ConfigMonitor;
 use crate::config::UiConfig;
@@ -89,7 +91,13 @@ fn main() -> Result<(), Box<dyn Error>> {
 
 /// `msg` subcommand entrypoint.
 #[cfg(unix)]
-fn msg(options: MessageOptions) -> Result<(), Box<dyn Error>> {
+#[allow(unused_mut)]
+fn msg(mut options: MessageOptions) -> Result<(), Box<dyn Error>> {
+    #[cfg(not(any(target_os = "macos", windows)))]
+    if let SocketMessage::CreateWindow(window_options) = &mut options.message {
+        window_options.activation_token =
+            env::var("XDG_ACTIVATION_TOKEN").or_else(|_| env::var("DESKTOP_STARTUP_ID")).ok();
+    }
     ipc::send_message(options.socket, options.message).map_err(|err| err.into())
 }
 
diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs
index cfc3cd9645d..e3c39382b89 100644
--- a/alacritty/src/window_context.rs
+++ b/alacritty/src/window_context.rs
@@ -73,7 +73,7 @@ impl WindowContext {
         event_loop: &ActiveEventLoop,
         proxy: EventLoopProxy<Event>,
         config: Rc<UiConfig>,
-        options: WindowOptions,
+        mut options: WindowOptions,
     ) -> Result<Self, Box<dyn Error>> {
         let raw_display_handle = event_loop.display_handle().unwrap().as_raw();
 
@@ -83,7 +83,7 @@ impl WindowContext {
         // Windows has different order of GL platform initialization compared to any other platform;
         // it requires the window first.
         #[cfg(windows)]
-        let window = Window::new(event_loop, &config, &identity)?;
+        let window = Window::new(event_loop, &config, &identity, &mut options)?;
         #[cfg(windows)]
         let raw_window_handle = Some(window.raw_window_handle());
 
@@ -102,10 +102,9 @@ impl WindowContext {
             event_loop,
             &config,
             &identity,
+            &mut options,
             #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
             gl_config.x11_visual(),
-            #[cfg(target_os = "macos")]
-            &options.window_tabbing_id,
         )?;
 
         // Create context.
@@ -123,7 +122,7 @@ impl WindowContext {
         event_loop: &ActiveEventLoop,
         proxy: EventLoopProxy<Event>,
         config: Rc<UiConfig>,
-        options: WindowOptions,
+        mut options: WindowOptions,
         config_overrides: ParsedOptions,
     ) -> Result<Self, Box<dyn Error>> {
         let gl_display = gl_config.display();
@@ -135,10 +134,9 @@ impl WindowContext {
             event_loop,
             &config,
             &identity,
+            &mut options,
             #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
             gl_config.x11_visual(),
-            #[cfg(target_os = "macos")]
-            &options.window_tabbing_id,
         )?;
 
         // Create context.


Test patch: