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

commitaf9440bbc40d4cc627655d411bd57a76b5b80a24
parented041e6fc1
authorLucas Galante <[email protected]>
date2026-07-17 21:30
fix: suppress activation-attention notifications during startup

Session restore respawns every previously-open window on login, and each
issues an xdg-activation request as it maps, spraying a burst of "<app>
needs attention / has requested activation" popups. Add a startup grace
window (8s, set just before spawning startup programs and restored
windows) during which handle_request_activate swallows the notification.
Outside the startup path the grace cell is never set, so activation
notifications behave normally.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

 src/server/run_server.rs |  6 ++++++
 src/server/server.rs     | 40 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/src/server/run_server.rs b/src/server/run_server.rs
index c30fc2f..d54eb15 100644
--- a/src/server/run_server.rs
+++ b/src/server/run_server.rs
@@ -188,6 +188,12 @@ pub fn run_server() {
         std::process::exit(1);
     }
 
+    // Suppress the "requested activation" notification burst that session
+    // restore is about to trigger: every respawned window issues an
+    // xdg-activation request as it maps. The grace window covers the whole
+    // startup sequence (startup programs + restored windows).
+    server::begin_startup_activation_grace();
+
     // Spawn configured startup programs
     let current_startup = server.wm.startup.clone();
     for prog in current_startup {
diff --git a/src/server/server.rs b/src/server/server.rs
index bad0aa4..7440588 100644
--- a/src/server/server.rs
+++ b/src/server/server.rs
@@ -15,6 +15,35 @@ use crate::xkb_config::XkbConfig;
 use crate::idle_inhibit_manager::IdleInhibitManager;
 use crate::lock_manager::LockManager;
 
+// Activation-attention notifications ("<app> needs attention / has requested
+// activation") are suppressed during the compositor's startup sequence: session
+// restore respawns every previously-open window, and each one issues an
+// xdg-activation request as it maps, which would otherwise spray a burst of
+// "needs attention" popups the moment you log in. `begin_startup_activation_grace`
+// is called once when startup begins; until the deadline passes, the handler
+// swallows the notification. Outside the startup path the cell is never set, so
+// activation notifications behave normally.
+static STARTUP_ACTIVATION_GRACE_UNTIL: std::sync::OnceLock<std::time::Instant> =
+    std::sync::OnceLock::new();
+
+/// How long after startup begins to keep suppressing activation notifications.
+/// Generous enough to cover restored heavyweight apps (e.g. a browser) that take
+/// several seconds to launch and request focus.
+const STARTUP_ACTIVATION_GRACE: std::time::Duration = std::time::Duration::from_secs(8);
+
+/// Begin the startup grace window during which activation-attention
+/// notifications are suppressed. Idempotent — only the first call takes effect.
+pub fn begin_startup_activation_grace() {
+    let _ = STARTUP_ACTIVATION_GRACE_UNTIL.set(std::time::Instant::now() + STARTUP_ACTIVATION_GRACE);
+}
+
+/// True while we're still inside the post-startup grace window.
+fn in_startup_activation_grace() -> bool {
+    STARTUP_ACTIVATION_GRACE_UNTIL
+        .get()
+        .is_some_and(|deadline| std::time::Instant::now() < *deadline)
+}
+
 // Helper macro equivalent to @fieldParentPtr in Zig
 #[macro_export]
 macro_rules! container_of {
@@ -361,7 +390,16 @@ unsafe extern "C" fn handle_request_activate(listener: *mut ffi::wl_listener, da
             };
             
             log::info!("xdg activation request for window '{}' ({})", title, app_id);
-            
+
+            if in_startup_activation_grace() {
+                log::info!(
+                    "activation notification suppressed during startup grace for '{}' ({})",
+                    title,
+                    app_id
+                );
+                break;
+            }
+
             let uid = unsafe { libc::getuid() };
             let bus_address = format!("unix:path=/run/user/{}/bus", uid);