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. ///