mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +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
|
WINDOWPOS
|
||||||
WINEVENTPROC
|
WINEVENTPROC
|
||||||
WORKERW
|
WORKERW
|
||||||
|
FULLSCREENAPP
|
||||||
|
|
||||||
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
|
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
|
||||||
DDDD
|
DDDD
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public record DockSettings
|
|||||||
|
|
||||||
public DockSize DockIconsSize { get; init; } = DockSize.Small;
|
public DockSize DockIconsSize { get; init; } = DockSize.Small;
|
||||||
|
|
||||||
|
public bool AlwaysOnTop { get; set; } = true;
|
||||||
|
|
||||||
// <Theme settings>
|
// <Theme settings>
|
||||||
public DockBackdrop Backdrop { get; init; } = DockBackdrop.Acrylic;
|
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
|
public bool EnableDock
|
||||||
{
|
{
|
||||||
get => _settingsService.Settings.EnableDock;
|
get => _settingsService.Settings.EnableDock;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
|
using Microsoft.CmdPal.UI.Helpers;
|
||||||
using Microsoft.CmdPal.UI.ViewModels;
|
using Microsoft.CmdPal.UI.ViewModels;
|
||||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||||
@@ -51,6 +52,8 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
private HWND _hwnd = HWND.Null;
|
private HWND _hwnd = HWND.Null;
|
||||||
private APPBARDATA _appBarData;
|
private APPBARDATA _appBarData;
|
||||||
private uint _callbackMessageId;
|
private uint _callbackMessageId;
|
||||||
|
private bool _isWindowTopmost;
|
||||||
|
private bool _isFullScreenAppOpen;
|
||||||
|
|
||||||
private DockSettings _settings;
|
private DockSettings _settings;
|
||||||
private DockViewModel viewModel;
|
private DockViewModel viewModel;
|
||||||
@@ -129,6 +132,7 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
_ = PInvoke.SetWindowLong(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, (int)style);
|
_ = PInvoke.SetWindowLong(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, (int)style);
|
||||||
|
|
||||||
ShowDesktop.AddHook(this);
|
ShowDesktop.AddHook(this);
|
||||||
|
_isFullScreenAppOpen = WindowHelper.IsWindowFullscreen();
|
||||||
UpdateSettingsOnUiThread();
|
UpdateSettingsOnUiThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +151,8 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
BOOL value = false;
|
BOOL value = false;
|
||||||
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &value, (uint)sizeof(BOOL));
|
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &value, (uint)sizeof(BOOL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateTopmostState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private HWND GetWindowHandle(Window window)
|
private HWND GetWindowHandle(Window window)
|
||||||
@@ -161,6 +167,7 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
UpdateBackdrop();
|
UpdateBackdrop();
|
||||||
|
|
||||||
_dock.UpdateSettings(_settings);
|
_dock.UpdateSettings(_settings);
|
||||||
|
|
||||||
var side = DockSettingsToViews.GetAppBarEdge(_settings.Side);
|
var side = DockSettingsToViews.GetAppBarEdge(_settings.Side);
|
||||||
|
|
||||||
if (_appBarData.hWnd != IntPtr.Zero)
|
if (_appBarData.hWnd != IntPtr.Zero)
|
||||||
@@ -169,6 +176,7 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
var sameSize = _lastSize == _settings.DockSize;
|
var sameSize = _lastSize == _settings.DockSize;
|
||||||
if (sameEdge && sameSize)
|
if (sameEdge && sameSize)
|
||||||
{
|
{
|
||||||
|
UpdateTopmostState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +184,7 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
CreateAppBar(_hwnd);
|
CreateAppBar(_hwnd);
|
||||||
|
UpdateTopmostState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeBackdropSupport()
|
private void InitializeBackdropSupport()
|
||||||
@@ -333,6 +342,41 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
_appBarData = default;
|
_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()
|
private void UpdateWindowPosition()
|
||||||
{
|
{
|
||||||
Logger.LogDebug("UpdateWindowPosition");
|
Logger.LogDebug("UpdateWindowPosition");
|
||||||
@@ -558,6 +602,11 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
{
|
{
|
||||||
UpdateWindowPosition();
|
UpdateWindowPosition();
|
||||||
}
|
}
|
||||||
|
else if (wParam.Value == PInvoke.ABN_FULLSCREENAPP)
|
||||||
|
{
|
||||||
|
_isFullScreenAppOpen = lParam != 0;
|
||||||
|
UpdateTopmostState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (msg == WM_TASKBAR_RESTART)
|
else if (msg == WM_TASKBAR_RESTART)
|
||||||
{
|
{
|
||||||
@@ -576,9 +625,7 @@ public sealed partial class DockWindow : WindowEx,
|
|||||||
{
|
{
|
||||||
DispatcherQueue.TryEnqueue(() =>
|
DispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
var onTop = message.OnTop ? HWND.HWND_TOPMOST : HWND.HWND_NOTOPMOST;
|
UpdateTopmostState(message.BringToFront);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,18 +817,20 @@ internal static class ShowDesktop
|
|||||||
if (eventType == PInvoke.EVENT_SYSTEM_FOREGROUND)
|
if (eventType == PInvoke.EVENT_SYSTEM_FOREGROUND)
|
||||||
{
|
{
|
||||||
var @class = GetWindowClass(hwnd);
|
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");
|
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; }
|
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);
|
internal sealed record RequestShowPaletteAtMessage(Point PosDips);
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ ABM_REMOVE
|
|||||||
ABM_SETAUTOHIDEBAR
|
ABM_SETAUTOHIDEBAR
|
||||||
ABS_AUTOHIDE
|
ABS_AUTOHIDE
|
||||||
ABN_POSCHANGED
|
ABN_POSCHANGED
|
||||||
|
ABN_FULLSCREENAPP
|
||||||
APPBARDATA
|
APPBARDATA
|
||||||
ABE_TOP
|
ABE_TOP
|
||||||
ABE_BOTTOM
|
ABE_BOTTOM
|
||||||
|
|||||||
@@ -212,6 +212,13 @@
|
|||||||
</controls:SettingsExpander.Items>
|
</controls:SettingsExpander.Items>
|
||||||
</controls:SettingsExpander>
|
</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 -->
|
<!-- Bands Section -->
|
||||||
<!-- <TextBlock x:Uid="DockBandsSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
<!-- <TextBlock x:Uid="DockBandsSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
<data name="Settings_GeneralPage_EnableDock_SettingsCard.Description" xml:space="preserve">
|
||||||
<value>Enable a toolbar with quick access to commands</value>
|
<value>Enable a toolbar with quick access to commands</value>
|
||||||
</data>
|
</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">
|
<data name="BackButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||||
<value>Back</value>
|
<value>Back</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
Reference in New Issue
Block a user