@@ -39,6 +39,8 @@ public sealed partial class DockWindow : WindowEx,
3939 IRecipient < QuitMessage > ,
4040 IDisposable
4141{
42+ private static readonly TimeSpan TopmostStateRefreshInterval = TimeSpan . FromSeconds ( 1 ) ;
43+
4244#pragma warning disable SA1306 // Field names should begin with lower-case letter
4345#pragma warning disable SA1310 // Field names should not contain underscore
4446 private readonly uint WM_TASKBAR_RESTART ;
@@ -49,9 +51,13 @@ public sealed partial class DockWindow : WindowEx,
4951 private readonly DockWindowViewModel _windowViewModel ;
5052 private readonly HiddenOwnerWindowBehavior _hiddenOwnerWindowBehavior = new ( ) ;
5153
54+ // SHQueryUserNotificationState does not raise change notifications, so we poll lightly.
55+ private readonly DispatcherTimer _topmostStateTimer = new ( ) ;
56+
5257 private HWND _hwnd = HWND . Null ;
5358 private APPBARDATA _appBarData ;
5459 private uint _callbackMessageId ;
60+ private bool _isWindowTopmost ;
5561
5662 private DockSettings _settings ;
5763 private DockViewModel viewModel ;
@@ -91,6 +97,8 @@ public DockWindow()
9197 }
9298
9399 this . Activated += DockWindow_Activated ;
100+ _topmostStateTimer . Interval = TopmostStateRefreshInterval ;
101+ _topmostStateTimer . Tick += TopmostStateTimer_Tick ;
94102
95103 WeakReferenceMessenger . Default . Register < BringToTopMessage > ( this ) ;
96104 WeakReferenceMessenger . Default . Register < RequestShowPaletteAtMessage > ( this ) ;
@@ -143,6 +151,13 @@ private void DockWindow_Activated(object sender, WindowActivatedEventArgs args)
143151 BOOL value = false ;
144152 PInvoke . DwmSetWindowAttribute ( _hwnd , DWMWINDOWATTRIBUTE . DWMWA_WINDOW_CORNER_PREFERENCE , & value , ( uint ) sizeof ( BOOL ) ) ;
145153 }
154+
155+ UpdateTopmostState ( ) ;
156+ }
157+
158+ private void TopmostStateTimer_Tick ( object ? sender , object e )
159+ {
160+ UpdateTopmostState ( ) ;
146161 }
147162
148163 private HWND GetWindowHandle ( Window window )
@@ -164,6 +179,18 @@ private void UpdateSettingsOnUiThread()
164179 }
165180
166181 _dock . UpdateSettings ( _settings ) ;
182+
183+ // Only poll for fullscreen changes when AlwaysOnTop is active;
184+ // when disabled the timer is unnecessary work.
185+ if ( _settings . AlwaysOnTop )
186+ {
187+ _topmostStateTimer . Start ( ) ;
188+ }
189+ else
190+ {
191+ _topmostStateTimer . Stop ( ) ;
192+ }
193+
167194 var side = DockSettingsToViews . GetAppBarEdge ( _settings . Side ) ;
168195
169196 if ( _appBarData . hWnd != IntPtr . Zero )
@@ -172,13 +199,15 @@ private void UpdateSettingsOnUiThread()
172199 var sameSize = _lastSize == _settings . DockSize ;
173200 if ( sameEdge && sameSize )
174201 {
202+ UpdateTopmostState ( ) ;
175203 return ;
176204 }
177205
178206 DestroyAppBar ( _hwnd ) ;
179207 }
180208
181209 CreateAppBar ( _hwnd ) ;
210+ UpdateTopmostState ( ) ;
182211 }
183212
184213 // We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
@@ -278,6 +307,40 @@ private void DestroyAppBar(HWND hwnd)
278307 _appBarData = default ;
279308 }
280309
310+ private void UpdateTopmostState ( bool bringToFront = false )
311+ {
312+ var shouldStayOnTop = _settings . AlwaysOnTop && ! WindowHelper . IsWindowFullscreen ( ) ;
313+ const SET_WINDOW_POS_FLAGS flags = SET_WINDOW_POS_FLAGS . SWP_NOMOVE | SET_WINDOW_POS_FLAGS . SWP_NOSIZE | SET_WINDOW_POS_FLAGS . SWP_NOACTIVATE ;
314+
315+ if ( shouldStayOnTop )
316+ {
317+ if ( _isWindowTopmost && ! bringToFront )
318+ {
319+ return ;
320+ }
321+
322+ PInvoke . SetWindowPos ( _hwnd , HWND . HWND_TOPMOST , 0 , 0 , 0 , 0 , flags ) ;
323+ _isWindowTopmost = true ;
324+ return ;
325+ }
326+
327+ if ( bringToFront )
328+ {
329+ // Win32 trick: briefly set HWND_TOPMOST then immediately clear it
330+ // with HWND_NOTOPMOST. This brings the window to the foreground
331+ // without permanently pinning it as topmost.
332+ PInvoke . SetWindowPos ( _hwnd , HWND . HWND_TOPMOST , 0 , 0 , 0 , 0 , flags ) ;
333+ }
334+
335+ if ( ! _isWindowTopmost && ! bringToFront )
336+ {
337+ return ;
338+ }
339+
340+ PInvoke . SetWindowPos ( _hwnd , HWND . HWND_NOTOPMOST , 0 , 0 , 0 , 0 , flags ) ;
341+ _isWindowTopmost = false ;
342+ }
343+
281344 private void UpdateWindowPosition ( )
282345 {
283346 Logger . LogDebug ( "UpdateWindowPosition" ) ;
@@ -521,9 +584,7 @@ void IRecipient<BringToTopMessage>.Receive(BringToTopMessage message)
521584 {
522585 DispatcherQueue . TryEnqueue ( ( ) =>
523586 {
524- var onTop = message . OnTop ? HWND . HWND_TOPMOST : HWND . HWND_NOTOPMOST ;
525- PInvoke . SetWindowPos ( _hwnd , onTop , 0 , 0 , 0 , 0 , SET_WINDOW_POS_FLAGS . SWP_NOMOVE | SET_WINDOW_POS_FLAGS . SWP_NOSIZE ) ;
526- PInvoke . SetWindowPos ( _hwnd , HWND . HWND_NOTOPMOST , 0 , 0 , 0 , 0 , SET_WINDOW_POS_FLAGS . SWP_NOMOVE | SET_WINDOW_POS_FLAGS . SWP_NOSIZE ) ;
587+ UpdateTopmostState ( message . BringToFront ) ;
527588 } ) ;
528589 }
529590
@@ -615,6 +676,8 @@ private void RequestShowPaletteOnUiThread(Point posDips)
615676
616677 public void Dispose ( )
617678 {
679+ _topmostStateTimer . Stop ( ) ;
680+ _topmostStateTimer . Tick -= TopmostStateTimer_Tick ;
618681 DisposeAcrylic ( ) ;
619682 _windowViewModel . Dispose ( ) ;
620683 }
@@ -625,6 +688,8 @@ private void DockWindow_Closed(object sender, WindowEventArgs args)
625688 var settings = serviceProvider . GetService < SettingsModel > ( ) ;
626689 settings ? . SettingsChanged - = SettingsChangedHandler ;
627690 _themeService . ThemeChanged -= ThemeService_ThemeChanged ;
691+ _topmostStateTimer . Stop ( ) ;
692+ _topmostStateTimer . Tick -= TopmostStateTimer_Tick ;
628693 DisposeAcrylic ( ) ;
629694
630695 // Remove our app bar registration
@@ -697,18 +762,20 @@ private static void WinEventCallback(
697762 if ( eventType == PInvoke . EVENT_SYSTEM_FOREGROUND )
698763 {
699764 var @class = GetWindowClass ( hwnd ) ;
700- if ( string . Equals ( @class , WORKERW , StringComparison . Ordinal ) || string . Equals ( @class , PROGMAN , StringComparison . Ordinal ) )
765+ var bringToFront = string . Equals ( @class , WORKERW , StringComparison . Ordinal ) || string . Equals ( @class , PROGMAN , StringComparison . Ordinal ) ;
766+ if ( bringToFront )
701767 {
702768 Logger . LogDebug ( "ShowDesktop invoked. Bring us back" ) ;
703- WeakReferenceMessenger . Default . Send < BringToTopMessage > ( new ( true ) ) ;
704769 }
770+
771+ WeakReferenceMessenger . Default . Send < BringToTopMessage > ( new ( bringToFront ) ) ;
705772 }
706773 }
707774
708775 public static bool IsHooked { get ; private set ; }
709776}
710777
711- internal sealed record BringToTopMessage ( bool OnTop ) ;
778+ internal sealed record BringToTopMessage ( bool BringToFront ) ;
712779
713780internal sealed record RequestShowPaletteAtMessage ( Point PosDips ) ;
714781
0 commit comments