From 656689ba7872c571c1c73db33dcfc06a97740c2b Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Thu, 30 Jul 2026 15:20:48 -0500 Subject: [PATCH] Keep the clock above the taskbar after the shell raises itself Topmost windows share a single z-order band, so clicking the taskbar or opening Start moves those shell windows above the clock and Windows never puts it back. Reassert the clock's place in the band on the existing clock tick, which also covers the raises that change no foreground window, like an auto-hide taskbar sliding up. Closes #154 --- DesktopClock/MainWindow.xaml.cs | 17 +++++++++++++++++ DesktopClock/Utilities/WindowUtil.cs | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/DesktopClock/MainWindow.xaml.cs b/DesktopClock/MainWindow.xaml.cs index 4bbdd93..5f1a91c 100644 --- a/DesktopClock/MainWindow.xaml.cs +++ b/DesktopClock/MainWindow.xaml.cs @@ -224,6 +224,8 @@ private void SystemClockTimer_SecondChanged(object sender, EventArgs e) { UpdateTimeString(); + TryReassertTopmost(); + TryShiftPixels(); TryPlaySound(); @@ -271,6 +273,21 @@ private void TryPlaySound() } } + /// + /// Puts the clock back above the taskbar, which buries it whenever the shell raises itself. + /// + /// + /// Checking every second rather than reacting to a foreground event is deliberate: the shell also raises itself without changing the foreground, as an auto-hide taskbar does, and reasserting mid-switch gets undone anyway. + /// + private void TryReassertTopmost() + { + Dispatcher.Invoke(() => + { + if (Settings.Default.Topmost && IsVisible && WindowState != WindowState.Minimized) + this.ReassertTopmost(); + }); + } + private void TryShiftPixels() { if (!Settings.Default.BurnInMitigation || DateTimeOffset.Now.Second != 0) diff --git a/DesktopClock/Utilities/WindowUtil.cs b/DesktopClock/Utilities/WindowUtil.cs index 76cc5e0..6a059e1 100644 --- a/DesktopClock/Utilities/WindowUtil.cs +++ b/DesktopClock/Utilities/WindowUtil.cs @@ -14,8 +14,11 @@ public static class WindowUtil private const uint SWP_NOMOVE = 0x0002; private const uint SWP_NOSIZE = 0x0001; private const uint SWP_NOZORDER = 0x0004; + private const uint SWP_NOACTIVATE = 0x0010; private const uint SWP_FRAMECHANGED = 0x0020; + private static readonly IntPtr HWND_TOPMOST = new(-1); + [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hwnd, int index); @@ -83,6 +86,23 @@ public static void ApplyWindowVisibility(this Window window, bool showInTaskbar, SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } + /// + /// Moves the window back to the top of the topmost band so it covers the taskbar again. + /// + /// + /// Topmost windows all share one band, so the shell raising itself leaves the clock buried inside it. + /// Assigning again wouldn't help because WPF only reapplies the z-order when the value changes. + /// + public static void ReassertTopmost(this Window window) + { + var hwnd = new WindowInteropHelper(window).Handle; + if (hwnd == IntPtr.Zero) + return; + + // SWP_NOZORDER is deliberately left out because moving within the band is the whole point, and SWP_NOACTIVATE keeps focus wherever the user left it. + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + } + /// /// Returns the extended style that matches the requested taskbar and Alt+Tab behavior. ///