mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 01:36:31 +02:00
CmdPal: Make Dock stay on top of all other windows (#46163)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This PR makes Dock stay on top of all other windows: - Marks the Dock window as topmost when there is no full-screen window. - Adds a new option that allows the user to disable this behavior. - Adds a timer that polls the system API to determine whether a full-screen window is present. ## Pictures? Pictures! <img width="560" height="283" alt="image" src="https://github.com/user-attachments/assets/55346005-2fac-4357-88bd-60c899565fac" /> https://github.com/user-attachments/assets/b81bff6d-4616-4d17-a1b0-063d254022ed <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #46161 <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
This commit is contained in:
1
.github/actions/spell-check/allow/code.txt
vendored
1
.github/actions/spell-check/allow/code.txt
vendored
@@ -339,6 +339,7 @@ SETAUTOHIDEBAR
|
||||
WINDOWPOS
|
||||
WINEVENTPROC
|
||||
WORKERW
|
||||
FULLSCREENAPP
|
||||
|
||||
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
|
||||
DDDD
|
||||
|
||||
@@ -22,6 +22,8 @@ public record DockSettings
|
||||
|
||||
public DockSize DockIconsSize { get; init; } = DockSize.Small;
|
||||
|
||||
public bool AlwaysOnTop { get; set; } = true;
|
||||
|
||||
// <Theme settings>
|
||||
public DockBackdrop Backdrop { get; init; } = DockBackdrop.Acrylic;
|
||||
|
||||
|
||||
@@ -226,6 +226,15 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
public bool Dock_AlwaysOnTop
|
||||
{
|
||||
get => _settingsService.Settings.DockSettings.AlwaysOnTop;
|
||||
set
|
||||
{
|
||||
_settingsService.UpdateSettings(s => s with { DockSettings = s.DockSettings with { AlwaysOnTop = value } });
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableDock
|
||||
{
|
||||
get => _settingsService.Settings.EnableDock;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
@@ -51,6 +52,8 @@ public sealed partial class DockWindow : WindowEx,
|
||||
private HWND _hwnd = HWND.Null;
|
||||
private APPBARDATA _appBarData;
|
||||
private uint _callbackMessageId;
|
||||
private bool _isWindowTopmost;
|
||||
private bool _isFullScreenAppOpen;
|
||||
|
||||
private DockSettings _settings;
|
||||
private DockViewModel viewModel;
|
||||
@@ -129,6 +132,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
_ = PInvoke.SetWindowLong(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, (int)style);
|
||||
|
||||
ShowDesktop.AddHook(this);
|
||||
_isFullScreenAppOpen = WindowHelper.IsWindowFullscreen();
|
||||
UpdateSettingsOnUiThread();
|
||||
}
|
||||
|
||||
@@ -147,6 +151,8 @@ public sealed partial class DockWindow : WindowEx,
|
||||
BOOL value = false;
|
||||
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &value, (uint)sizeof(BOOL));
|
||||
}
|
||||
|
||||
UpdateTopmostState();
|
||||
}
|
||||
|
||||
private HWND GetWindowHandle(Window window)
|
||||
@@ -161,6 +167,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
UpdateBackdrop();
|
||||
|
||||
_dock.UpdateSettings(_settings);
|
||||
|
||||
var side = DockSettingsToViews.GetAppBarEdge(_settings.Side);
|
||||
|
||||
if (_appBarData.hWnd != IntPtr.Zero)
|
||||
@@ -169,6 +176,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
var sameSize = _lastSize == _settings.DockSize;
|
||||
if (sameEdge && sameSize)
|
||||
{
|
||||
UpdateTopmostState();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -176,6 +184,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
}
|
||||
|
||||
CreateAppBar(_hwnd);
|
||||
UpdateTopmostState();
|
||||
}
|
||||
|
||||
private void InitializeBackdropSupport()
|
||||
@@ -333,6 +342,41 @@ public sealed partial class DockWindow : WindowEx,
|
||||
_appBarData = default;
|
||||
}
|
||||
|
||||
private void UpdateTopmostState(bool bringToFront = false)
|
||||
{
|
||||
var shouldStayOnTop = _settings.AlwaysOnTop && !_isFullScreenAppOpen;
|
||||
const SET_WINDOW_POS_FLAGS flags = SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE;
|
||||
|
||||
if (shouldStayOnTop)
|
||||
{
|
||||
if (_isWindowTopmost && !bringToFront)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PInvoke.SetWindowPos(_hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, flags);
|
||||
_isWindowTopmost = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bringToFront)
|
||||
{
|
||||
// Win32 trick: briefly set HWND_TOPMOST then immediately clear it
|
||||
// with HWND_NOTOPMOST. This brings the window to the foreground
|
||||
// without permanently pinning it as topmost.
|
||||
PInvoke.SetWindowPos(_hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, flags);
|
||||
}
|
||||
|
||||
if (!_isWindowTopmost && !bringToFront)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var zOrder = _isFullScreenAppOpen ? HWND.HWND_BOTTOM : HWND.HWND_NOTOPMOST;
|
||||
PInvoke.SetWindowPos(_hwnd, zOrder, 0, 0, 0, 0, flags);
|
||||
_isWindowTopmost = false;
|
||||
}
|
||||
|
||||
private void UpdateWindowPosition()
|
||||
{
|
||||
Logger.LogDebug("UpdateWindowPosition");
|
||||
@@ -558,6 +602,11 @@ public sealed partial class DockWindow : WindowEx,
|
||||
{
|
||||
UpdateWindowPosition();
|
||||
}
|
||||
else if (wParam.Value == PInvoke.ABN_FULLSCREENAPP)
|
||||
{
|
||||
_isFullScreenAppOpen = lParam != 0;
|
||||
UpdateTopmostState();
|
||||
}
|
||||
}
|
||||
else if (msg == WM_TASKBAR_RESTART)
|
||||
{
|
||||
@@ -576,9 +625,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
var onTop = message.OnTop ? HWND.HWND_TOPMOST : HWND.HWND_NOTOPMOST;
|
||||
PInvoke.SetWindowPos(_hwnd, onTop, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
|
||||
PInvoke.SetWindowPos(_hwnd, HWND.HWND_NOTOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
|
||||
UpdateTopmostState(message.BringToFront);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -770,18 +817,20 @@ internal static class ShowDesktop
|
||||
if (eventType == PInvoke.EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
var @class = GetWindowClass(hwnd);
|
||||
if (string.Equals(@class, WORKERW, StringComparison.Ordinal) || string.Equals(@class, PROGMAN, StringComparison.Ordinal))
|
||||
var bringToFront = string.Equals(@class, WORKERW, StringComparison.Ordinal) || string.Equals(@class, PROGMAN, StringComparison.Ordinal);
|
||||
if (bringToFront)
|
||||
{
|
||||
Logger.LogDebug("ShowDesktop invoked. Bring us back");
|
||||
WeakReferenceMessenger.Default.Send<BringToTopMessage>(new(true));
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.Send<BringToTopMessage>(new(bringToFront));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsHooked { get; private set; }
|
||||
}
|
||||
|
||||
internal sealed record BringToTopMessage(bool OnTop);
|
||||
internal sealed record BringToTopMessage(bool BringToFront);
|
||||
|
||||
internal sealed record RequestShowPaletteAtMessage(Point PosDips);
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ ABM_REMOVE
|
||||
ABM_SETAUTOHIDEBAR
|
||||
ABS_AUTOHIDE
|
||||
ABN_POSCHANGED
|
||||
ABN_FULLSCREENAPP
|
||||
APPBARDATA
|
||||
ABE_TOP
|
||||
ABE_BOTTOM
|
||||
|
||||
@@ -212,6 +212,13 @@
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- Behavior Section -->
|
||||
<TextBlock x:Uid="BehaviorSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="DockBehavior_AlwaysOnTop_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.Dock_AlwaysOnTop, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Bands Section -->
|
||||
<!-- <TextBlock x:Uid="DockBandsSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
@@ -246,4 +253,4 @@
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
</Page>
|
||||
|
||||
@@ -464,6 +464,12 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Settings_GeneralPage_EnableDock_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Enable a toolbar with quick access to commands</value>
|
||||
</data>
|
||||
<data name="DockBehavior_AlwaysOnTop_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Always stay on top</value>
|
||||
</data>
|
||||
<data name="DockBehavior_AlwaysOnTop_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Keep the dock above other windows, except while an app is fullscreen</value>
|
||||
</data>
|
||||
<data name="BackButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Back</value>
|
||||
</data>
|
||||
|
||||
Reference in New Issue
Block a user