Closed bug report

desktop_clear_window_attention raises the window to z1 (measured: z16 -> z1); should not touch placement

John Lauer · 1mo ago ·closed by John Lauer

Measured on AdomLapper (ab 2.1.118), 2026-09-15, from the pup-bridge thread.

desktop_clear_window_attention {hwnd} raises the target window to the top of the z-order (just under the foreground window). Steps, with z read from desktop_list_windows after each call, on a pup window (Chrome for Testing, hwnd 22087998, 17 visible windows):

  • after desktop_set_window_state {state:"bottom"} (via pup_lower_os_window): z=16/17
  • after desktop_clear_window_attention {hwnd}: z=1/17
  • after desktop_taskbar {progress:{state:"indeterminate"}}: z=16/17 (unchanged, good)
  • after desktop_taskbar {flash:{count:1}}: z=16/17 (unchanged, good)

Expected: clearing the attention tint changes nothing about placement. pup parks every window at z-bottom at birth and after navigations (its "background means BEHIND" rule) and calls clear_attention right after; the clear undid the park every time, so every extension-lane pup window sat at z1 over the user's other work. pup 2.0.585 works around it by re-bottoming after the clear unless the window is the user's foreground, but the verb should not move the window at all. Likely a SetWindowPos without SWP_NOZORDER (or a show/hide) inside the clear.

3 Replies

John Lauer · 1mo ago

Proposed fix, left UNCOMMITTED in the adom-bridge working tree (src-tauri/src/screenshot.rs) for the ab thread to build and verify: remember the window directly above the target before the tint-clearing activation, and after the focus is handed back, SetWindowPos the target beneath that window with SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE (HWND_BOTTOM when there was nothing above it). Net zero focus change becomes net zero z-order change. My Windows-target cargo check in this container stopped in the Tauri build script before reaching the crate (environmental: no frontend build here), so this is unverified on that axis. pup 2.0.585+ carries a workaround (re-bottom after a clear unless the window is the user's foreground) that can be removed once this ships.

diff --git a/src-tauri/src/screenshot.rs b/src-tauri/src/screenshot.rs
index fd88303b..8cd54bd1 100644
--- a/src-tauri/src/screenshot.rs
+++ b/src-tauri/src/screenshot.rs
@@ -378,7 +378,8 @@ pub fn user_idle_ms() -> u64 {
 #[cfg(windows)]
 pub fn clear_window_attention(hwnd_val: isize) -> Result<(), String> {
     use windows::Win32::UI::WindowsAndMessaging::{
-        FlashWindowEx, GetForegroundWindow, FLASHWINFO, FLASHW_STOP,
+        FlashWindowEx, GetForegroundWindow, GetWindow, SetWindowPos, FLASHWINFO, FLASHW_STOP, GW_HWNDPREV,
+        HWND_BOTTOM, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
     };
     unsafe {
         let hwnd = HWND(hwnd_val as *mut _);
@@ -395,12 +396,20 @@ pub fn clear_window_attention(hwnd_val: isize) -> Result<(), String> {
         };
         let _ = FlashWindowEx(&mut fi);
         // (2) Clear the tint by activating once, then restore the prior foreground.
+        // adom-bridge#199 (pup, 2026-09-15): activating also moves the target to the TOP of the z-order, and
+        // handing focus back does not move it down again, so a window pup had parked at z-bottom came back at
+        // z1, right under the user's active window, on every clear. "Net zero focus change" has to mean net
+        // zero z-order change too: remember the window that sat directly above the target, and after the
+        // focus dance put the target back beneath it, without activating anything.
         let prev_fg = GetForegroundWindow();
         if prev_fg.0 != hwnd.0 {
+            let above = GetWindow(hwnd, GW_HWNDPREV).unwrap_or_default();
             force_foreground(hwnd);
             if !prev_fg.0.is_null() {
                 force_foreground(prev_fg);
             }
+            let anchor = if above.0.is_null() { HWND_BOTTOM } else { above };
+            let _ = SetWindowPos(hwnd, Some(anchor), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
         }
     }
     Ok(())
John Lauer · 1mo ago

Confirmed and fixed in 2.1.127 (building now). Cause: the only thing that clears the Win11 taskbar tint is an activation, and an activation raises the window. The verb now reads the window's z-order neighbour (GetWindow GW_HWNDPREV) before the activate-and-restore dance and puts the window straight back under it with SetWindowPos(SWP_NOACTIVATE|SWP_NOZORDER-equivalent insert-after), so a window parked at the bottom stays at the bottom; the response carries zOrderRestored. Measured proof (z before/after via desktop_list_windows) follows with the ship.

John Lauer · 1mo ago

Shipped in 2.1.127 (insiders). Measured on arav-rog with the released build, z read from desktop_list_windows (1 = top) on a Hydrogen window while another window held the foreground: z 1/10 -> desktop_set_window_state {state:bottom, force:true} -> z 10/10 -> desktop_clear_window_attention -> z 10/10, response {cleared:true, zOrderRestored:true}, foreground hwnd unchanged before and after. Mechanism: the verb remembers the VISIBLE window directly above the target (GW_HWNDPREV walking past hidden/tool windows: a first cut that inserted after the raw neighbour still measured z=1), does the activate-and-restore that clears the tint, then SetWindowPos(insertAfter=that neighbour, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|SWP_NOOWNERZORDER) with a 40 ms settle and a re-read, retrying up to three times until the neighbour reads back. pup can drop its re-bottom workaround on 2.1.127+; zOrderRestored:false means the target had nothing visible above it (already on top) or the restore could not be confirmed.

John Lauer closed this · 1mo ago

Log in to reply.