Compare commits

...

1 Commits

Author SHA1 Message Date
Niels Laute
d628c243d9 Add ShowDesktop module - click wallpaper to reveal desktop
Integrates the PeekDesktop utility by Scott Hanselman
(https://github.com/shanselman/PeekDesktop) as a full PowerToys module.

This brings the macOS Sonoma 'click wallpaper to show desktop' feature
to Windows with three peek modes: Native (Win+D), Minimize, and FlyAway.

New module components:
- C++ interface DLL (ShowDesktopModuleInterface) for Runner integration
- C# application with low-level mouse hook for desktop click detection
- Settings UI page with peek mode, double-click, taskbar peek, and
  gaming detection options
- Full infrastructure registration (GPO, runner, settings, installer)
- Unit tests and developer documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 18:26:13 +02:00
58 changed files with 2876 additions and 0 deletions

View File

@@ -1040,6 +1040,11 @@
<Project Path="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" Id="568c4c30-2e3c-4c2c-a691-007362073765" />
<Project Path="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" Id="2c3f7770-4e57-46b7-8dc1-7428a383d0db" />
</Folder>
<Folder Name="/modules/ShowDesktop/">
<Project Path="src/modules/ShowDesktop/ShowDesktopModuleInterface/ShowDesktopModuleInterface.vcxproj" Id="a1b2c3d4-e5f6-7890-abcd-ef1234567890" />
<Project Path="src/modules/ShowDesktop/ShowDesktop/ShowDesktop.csproj" Id="b2c3d4e5-f6a7-8901-bcde-f12345678901" />
<Project Path="src/modules/ShowDesktop/ShowDesktop.UnitTests/ShowDesktop.UnitTests.csproj" Id="c3d4e5f6-a7b8-9012-cdef-123456789012" />
</Folder>
<Folder Name="/settings-ui/">
<Project Path="src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
@@ -1106,6 +1111,7 @@
<BuildDependency Project="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" />
<BuildDependency Project="src/modules/ShowDesktop/ShowDesktopModuleInterface/ShowDesktopModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/powerrename/dll/PowerRenameExt.vcxproj" />
<BuildDependency Project="src/modules/powerrename/lib/PowerRenameLib.vcxproj" />
<BuildDependency Project="src/modules/previewpane/Common/PreviewHandlerCommon.csproj" />

View File

@@ -0,0 +1,66 @@
# ShowDesktop Module
## Overview
ShowDesktop brings macOS Sonoma's "click wallpaper to reveal desktop" feature to Windows. When enabled, clicking on empty desktop wallpaper minimizes all windows to reveal the desktop. Clicking again (or clicking any app/taskbar) restores all windows to their previous positions.
## Architecture
ShowDesktop follows the **External Application Launcher** pattern:
```
Runner
└─ ShowDesktopModuleInterface.dll (C++ interface DLL)
└─ PowerToys.ShowDesktop.exe (C# application)
```
### Components
| Component | Description |
|-----------|-------------|
| **ShowDesktopModuleInterface** | C++ DLL implementing `PowertoyModuleIface`. Handles module lifecycle (enable/disable) and spawns the C# app. |
| **PowerToys.ShowDesktop.exe** | C# application containing the core desktop-peek logic. Runs a Win32 message loop for the low-level mouse hook. |
### Core C# Classes
| Class | Purpose |
|-------|---------|
| `DesktopPeek` | Core state machine (Idle ↔ Peeking). Orchestrates click detection, window tracking, and restore. |
| `MouseHook` | Low-level mouse hook (`WH_MOUSE_LL`) for detecting clicks on desktop wallpaper. |
| `FocusWatcher` | `WinEventHook(EVENT_SYSTEM_FOREGROUND)` for monitoring foreground window changes. |
| `WindowTracker` | Enumerates, captures, minimizes, restores, and animates windows. |
| `DesktopDetector` | Identifies whether a click landed on desktop wallpaper, desktop icons, or the taskbar. |
| `VirtualDesktopService` | Virtual desktop management via undocumented COM interfaces. |
| `NativeMethods` | P/Invoke declarations for Win32 APIs. |
## Settings
Settings are stored in `%LOCALAPPDATA%\Microsoft\PowerToys\ShowDesktop\settings.json`.
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `peek-mode` | int | 0 | 0=Native Show Desktop, 1=Minimize, 2=Fly Away |
| `require-double-click` | bool | false | Require double-click on wallpaper to activate |
| `enable-taskbar-peek` | bool | false | Also trigger peek from empty taskbar space |
| `enable-gaming-detection` | bool | true | Auto-pause during fullscreen gaming |
| `fly-away-animation-duration-ms` | int | 300 | Animation duration for Fly Away mode (ms) |
## Peek Modes
- **Native Show Desktop (0)**: Uses the built-in Win+D mechanism via `SendInput`.
- **Minimize (1)**: Minimizes each window individually, preserving exact `WINDOWPLACEMENT` state for restore.
- **Fly Away (2)**: Animated offscreen movement of windows, then restore with reverse animation.
## IPC
Communication between the interface DLL and the C# app uses Windows named events:
- `SHOW_DESKTOP_TERMINATE_EVENT`: Signaled by the interface DLL to terminate the C# app on disable.
## GPO Policy
Registry value: `ConfigureEnabledUtilityShowDesktop` under `SOFTWARE\Policies\PowerToys`
## Credits
Based on [PeekDesktop](https://github.com/shanselman/PeekDesktop) by Scott Hanselman.

View File

@@ -18,6 +18,7 @@
<?define RegistryPreviewProjectName="RegistryPreview"?>
<?define PeekProjectName="Peek"?>
<?define WorkspacesProjectName="Workspaces"?>
<?define ShowDesktopProjectName="ShowDesktop"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?if $(var.Platform) = x64?>

View File

@@ -69,6 +69,7 @@
<ComponentGroupRef Id="ToolComponentGroup" />
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
<ComponentGroupRef Id="WorkspacesComponentGroup" />
<ComponentGroupRef Id="ShowDesktopComponentGroup" />
<ComponentGroupRef Id="CmdPalComponentGroup" />
</Feature>

View File

@@ -0,0 +1,30 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define ShowDesktopImagesFiles=?>
<?define ShowDesktopImagesFilesPath=$(var.BinDir)\Assets\ShowDesktop\?>
<Fragment>
<!-- ShowDesktop images -->
<DirectoryRef Id="BaseApplicationsAssetsFolder">
<Directory Id="ShowDesktopImagesFolder" Name="ShowDesktop" />
</DirectoryRef>
<DirectoryRef Id="ShowDesktopImagesFolder" FileSource="$(var.ShowDesktopImagesFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ShowDesktopImagesFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="ShowDesktopComponentGroup">
<Component Id="RemoveShowDesktopFolder" Guid="7B3A9C1E-2D4F-5A6B-8E7C-9F0D1A2B3C4E" Directory="INSTALLFOLDER">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveShowDesktopFolder" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveFolderShowDesktopImagesFolder" Directory="ShowDesktopImagesFolder" On="uninstall" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -236,6 +236,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredNewPlusEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredShowDesktopEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredShowDesktopEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredWorkspacesEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredWorkspacesEnabledValue());

View File

@@ -65,6 +65,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredShowDesktopEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();

View File

@@ -69,6 +69,7 @@ namespace PowerToys
static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredShowDesktopEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();

View File

@@ -76,5 +76,10 @@ namespace PowerToys.GPOWrapperProjection
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
}
public static GpoRuleConfigured GetConfiguredShowDesktopEnabledValue()
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShowDesktopEnabledValue();
}
}
}

View File

@@ -37,6 +37,7 @@ namespace ManagedCommon
PowerOCR,
Workspaces,
GrabAndMove,
ShowDesktop,
ZoomIt,
GeneralSettings,
}

View File

@@ -187,6 +187,9 @@ namespace CommonSharedConstants
const wchar_t MWB_TOGGLE_EASY_MOUSE_EVENT[] = L"Local\\PowerToysMWB-ToggleEasyMouseEvent-a9c8d7b6-e5f4-3c2a-1b0d-9e8f7a6b5c4d";
const wchar_t MWB_RECONNECT_EVENT[] = L"Local\\PowerToysMWB-ReconnectEvent-b8d7c6a5-f4e3-2b1c-0a9d-8e7f6a5b4c3d";
// Path to the event used by ShowDesktop
const wchar_t SHOW_DESKTOP_TERMINATE_EVENT[] = L"Local\\PowerToysShowDesktop-TerminateEvent-7b3a9c1e-2d4f-5a6b-8e7c-9f0d1a2b3c4e";
// Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100;
}

View File

@@ -71,6 +71,7 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_NEWPLUS = L"ConfigureEnabledUtilityNewPlus";
const std::wstring POLICY_CONFIGURE_ENABLED_SHOW_DESKTOP = L"ConfigureEnabledUtilityShowDesktop";
const std::wstring POLICY_CONFIGURE_ENABLED_WORKSPACES = L"ConfigureEnabledUtilityWorkspaces";
// The registry value names for PowerToys installer and update policies.
@@ -512,6 +513,11 @@ namespace powertoys_gpo
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_NEWPLUS);
}
inline gpo_rule_configured_t getConfiguredShowDesktopEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_SHOW_DESKTOP);
}
#pragma endregion UtilityEnabledStatePolicies
// Individual module setting policies

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class DesktopDetectorTests
{
[TestMethod]
public void IsDesktopWindow_ZeroHandle_ReturnsFalse()
{
bool result = DesktopDetector.IsDesktopWindow(IntPtr.Zero);
Assert.IsFalse(result);
}
[TestMethod]
public void IsDesktopWindow_InvalidHandle_ReturnsFalse()
{
// An invalid, non-zero handle should not be detected as a desktop window
// since GetClassName will return empty for invalid handles
bool result = DesktopDetector.IsDesktopWindow(new IntPtr(0x7FFFFFFF));
Assert.IsFalse(result);
}
}
}

View File

@@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class DesktopPeekTests
{
private static ShowDesktopSettings CreateSettings(
int peekMode = 0,
bool requireDoubleClick = false,
bool enableTaskbarPeek = false,
bool enableGamingDetection = true)
{
var settings = new ShowDesktopSettings();
settings.Properties.PeekMode = new IntProperty(peekMode);
settings.Properties.RequireDoubleClick = new BoolProperty(requireDoubleClick);
settings.Properties.EnableTaskbarPeek = new BoolProperty(enableTaskbarPeek);
settings.Properties.EnableGamingDetection = new BoolProperty(enableGamingDetection);
return settings;
}
[TestMethod]
public void Constructor_WithDefaultSettings_DoesNotThrow()
{
var settings = CreateSettings();
using var peek = new DesktopPeek(settings);
// No exception means construction succeeded
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithNativeMode_DoesNotThrow()
{
var settings = CreateSettings(peekMode: (int)PeekMode.Native);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithMinimizeMode_DoesNotThrow()
{
var settings = CreateSettings(peekMode: (int)PeekMode.Minimize);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithFlyAwayMode_DoesNotThrow()
{
var settings = CreateSettings(peekMode: (int)PeekMode.FlyAway);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void UpdateSettings_AppliesNewSettings()
{
var settings = CreateSettings(peekMode: (int)PeekMode.Native);
using var peek = new DesktopPeek(settings);
var newSettings = CreateSettings(
peekMode: (int)PeekMode.Minimize,
requireDoubleClick: true,
enableTaskbarPeek: true,
enableGamingDetection: false);
// Should not throw
peek.UpdateSettings(newSettings);
}
[TestMethod]
public void Dispose_CalledTwice_DoesNotThrow()
{
var settings = CreateSettings();
var peek = new DesktopPeek(settings);
peek.Dispose();
peek.Dispose(); // second dispose should be safe
}
[TestMethod]
public void Constructor_WithDoubleClickEnabled_DoesNotThrow()
{
var settings = CreateSettings(requireDoubleClick: true);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithTaskbarPeekEnabled_DoesNotThrow()
{
var settings = CreateSettings(enableTaskbarPeek: true);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_WithGamingDetectionDisabled_DoesNotThrow()
{
var settings = CreateSettings(enableGamingDetection: false);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
[TestMethod]
public void Constructor_AllSettingsCombined_DoesNotThrow()
{
var settings = CreateSettings(
peekMode: (int)PeekMode.FlyAway,
requireDoubleClick: true,
enableTaskbarPeek: true,
enableGamingDetection: false);
using var peek = new DesktopPeek(settings);
Assert.IsNotNull(peek);
}
}
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class MouseHookEventArgsTests
{
[TestMethod]
public void Properties_DefaultValues_AreCorrect()
{
var args = new MouseHookEventArgs();
Assert.AreEqual(0, args.X);
Assert.AreEqual(0, args.Y);
Assert.IsFalse(args.IsDoubleClick);
Assert.IsFalse(args.IsTaskbar);
}
[TestMethod]
public void Properties_WhenInitialized_ReturnSetValues()
{
var args = new MouseHookEventArgs
{
X = 100,
Y = 200,
IsDoubleClick = true,
IsTaskbar = false,
};
Assert.AreEqual(100, args.X);
Assert.AreEqual(200, args.Y);
Assert.IsTrue(args.IsDoubleClick);
Assert.IsFalse(args.IsTaskbar);
}
[TestMethod]
public void Properties_TaskbarClick_SetsCorrectly()
{
var args = new MouseHookEventArgs
{
X = 500,
Y = 1060,
IsDoubleClick = false,
IsTaskbar = true,
};
Assert.AreEqual(500, args.X);
Assert.AreEqual(1060, args.Y);
Assert.IsFalse(args.IsDoubleClick);
Assert.IsTrue(args.IsTaskbar);
}
[TestMethod]
public void Properties_NegativeCoordinates_AreAllowed()
{
var args = new MouseHookEventArgs
{
X = -100,
Y = -200,
};
Assert.AreEqual(-100, args.X);
Assert.AreEqual(-200, args.Y);
}
}
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class PeekModeTests
{
[TestMethod]
public void Native_HasValue_Zero()
{
Assert.AreEqual(0, (int)PeekMode.Native);
}
[TestMethod]
public void Minimize_HasValue_One()
{
Assert.AreEqual(1, (int)PeekMode.Minimize);
}
[TestMethod]
public void FlyAway_HasValue_Two()
{
Assert.AreEqual(2, (int)PeekMode.FlyAway);
}
[TestMethod]
public void PeekMode_HasExactlyThreeValues()
{
var values = System.Enum.GetValues<PeekMode>();
Assert.AreEqual(3, values.Length);
}
[TestMethod]
[DataRow(0, PeekMode.Native)]
[DataRow(1, PeekMode.Minimize)]
[DataRow(2, PeekMode.FlyAway)]
public void PeekMode_CastsFromInt_Correctly(int intValue, PeekMode expected)
{
Assert.AreEqual(expected, (PeekMode)intValue);
}
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<SelfContained>true</SelfContained>
<RuntimeIdentifier Condition="'$(Platform)' == 'x64'">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$(Platform)' == 'ARM64'">win-arm64</RuntimeIdentifier>
<OutputType>Exe</OutputType>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\ShowDesktop.UnitTests\</OutputPath>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ShowDesktop\ShowDesktop.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class ShowDesktopSettingsTests
{
[TestMethod]
public void DefaultSettings_PeekMode_IsNative()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultPeekMode, settings.Properties.PeekMode.Value);
Assert.AreEqual(0, settings.Properties.PeekMode.Value);
}
[TestMethod]
public void DefaultSettings_RequireDoubleClick_IsFalse()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultRequireDoubleClick, settings.Properties.RequireDoubleClick.Value);
Assert.IsFalse(settings.Properties.RequireDoubleClick.Value);
}
[TestMethod]
public void DefaultSettings_EnableTaskbarPeek_IsFalse()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultEnableTaskbarPeek, settings.Properties.EnableTaskbarPeek.Value);
Assert.IsFalse(settings.Properties.EnableTaskbarPeek.Value);
}
[TestMethod]
public void DefaultSettings_EnableGamingDetection_IsTrue()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultEnableGamingDetection, settings.Properties.EnableGamingDetection.Value);
Assert.IsTrue(settings.Properties.EnableGamingDetection.Value);
}
[TestMethod]
public void DefaultSettings_FlyAwayDuration_Is300()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual(ShowDesktopProperties.DefaultFlyAwayAnimationDurationMs, settings.Properties.FlyAwayAnimationDurationMs.Value);
Assert.AreEqual(300, settings.Properties.FlyAwayAnimationDurationMs.Value);
}
[TestMethod]
public void GetModuleName_ReturnsShowDesktop()
{
var settings = new ShowDesktopSettings();
Assert.AreEqual("ShowDesktop", settings.GetModuleName());
}
[TestMethod]
public void ModuleName_Constant_IsShowDesktop()
{
Assert.AreEqual("ShowDesktop", ShowDesktopSettings.ModuleName);
}
[TestMethod]
public void Properties_PeekMode_CanBeSet()
{
var settings = new ShowDesktopSettings();
settings.Properties.PeekMode = new IntProperty((int)PeekMode.Minimize);
Assert.AreEqual((int)PeekMode.Minimize, settings.Properties.PeekMode.Value);
}
[TestMethod]
public void Properties_ToJsonString_IsNotEmpty()
{
var properties = new ShowDesktopProperties();
string json = properties.ToJsonString();
Assert.IsFalse(string.IsNullOrEmpty(json));
}
[TestMethod]
public void Properties_ToJsonString_ContainsPeekMode()
{
var properties = new ShowDesktopProperties();
string json = properties.ToJsonString();
Assert.IsTrue(json.Contains("peek-mode"));
}
[TestMethod]
public void Properties_ToJsonString_ContainsAllProperties()
{
var properties = new ShowDesktopProperties();
string json = properties.ToJsonString();
Assert.IsTrue(json.Contains("peek-mode"));
Assert.IsTrue(json.Contains("require-double-click"));
Assert.IsTrue(json.Contains("enable-taskbar-peek"));
Assert.IsTrue(json.Contains("enable-gaming-detection"));
Assert.IsTrue(json.Contains("fly-away-animation-duration-ms"));
}
[TestMethod]
public void UpgradeSettingsConfiguration_ReturnsFalse()
{
var settings = new ShowDesktopSettings();
Assert.IsFalse(settings.UpgradeSettingsConfiguration());
}
}
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class WindowInfoTests
{
[TestMethod]
public void Constructor_StoresHwnd()
{
IntPtr hwnd = new IntPtr(12345);
var placement = NativeMethods.WINDOWPLACEMENT.Default;
var info = new WindowInfo(hwnd, placement);
Assert.AreEqual(hwnd, info.Hwnd);
}
[TestMethod]
public void Constructor_StoresPlacement()
{
IntPtr hwnd = new IntPtr(42);
var placement = NativeMethods.WINDOWPLACEMENT.Default;
placement.showCmd = NativeMethods.SW_SHOWMAXIMIZED;
var info = new WindowInfo(hwnd, placement);
Assert.AreEqual((uint)NativeMethods.SW_SHOWMAXIMIZED, info.Placement.showCmd);
}
[TestMethod]
public void Constructor_ZeroHwnd_IsAllowed()
{
var placement = NativeMethods.WINDOWPLACEMENT.Default;
var info = new WindowInfo(IntPtr.Zero, placement);
Assert.AreEqual(IntPtr.Zero, info.Hwnd);
}
[TestMethod]
public void Placement_PreservesNormalPosition()
{
var placement = NativeMethods.WINDOWPLACEMENT.Default;
placement.rcNormalPosition = new NativeMethods.RECT
{
Left = 10,
Top = 20,
Right = 810,
Bottom = 620,
};
var info = new WindowInfo(new IntPtr(1), placement);
Assert.AreEqual(10, info.Placement.rcNormalPosition.Left);
Assert.AreEqual(20, info.Placement.rcNormalPosition.Top);
Assert.AreEqual(810, info.Placement.rcNormalPosition.Right);
Assert.AreEqual(620, info.Placement.rcNormalPosition.Bottom);
}
}
}

View File

@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ShowDesktop.UnitTests
{
[TestClass]
public class WindowTrackerTests
{
[TestMethod]
public void SendShowDesktop_GeneratesCorrectInputStructures()
{
// Verify the INPUT array structure by recreating the same logic
var inputs = new NativeMethods.INPUT[4];
// Win key down
inputs[0].type = NativeMethods.INPUT_KEYBOARD;
inputs[0].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[0].u.ki.dwFlags = 0;
// D key down
inputs[1].type = NativeMethods.INPUT_KEYBOARD;
inputs[1].u.ki.wVk = NativeMethods.VK_D;
inputs[1].u.ki.dwFlags = 0;
// D key up
inputs[2].type = NativeMethods.INPUT_KEYBOARD;
inputs[2].u.ki.wVk = NativeMethods.VK_D;
inputs[2].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
// Win key up
inputs[3].type = NativeMethods.INPUT_KEYBOARD;
inputs[3].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[3].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
// Validate all four inputs are keyboard type
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[0].type);
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[1].type);
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[2].type);
Assert.AreEqual(NativeMethods.INPUT_KEYBOARD, inputs[3].type);
// Validate key sequence: Win↓, D↓, D↑, Win↑
Assert.AreEqual(NativeMethods.VK_LWIN, inputs[0].u.ki.wVk);
Assert.AreEqual(0u, inputs[0].u.ki.dwFlags);
Assert.AreEqual(NativeMethods.VK_D, inputs[1].u.ki.wVk);
Assert.AreEqual(0u, inputs[1].u.ki.dwFlags);
Assert.AreEqual(NativeMethods.VK_D, inputs[2].u.ki.wVk);
Assert.AreEqual(NativeMethods.KEYEVENTF_KEYUP, inputs[2].u.ki.dwFlags);
Assert.AreEqual(NativeMethods.VK_LWIN, inputs[3].u.ki.wVk);
Assert.AreEqual(NativeMethods.KEYEVENTF_KEYUP, inputs[3].u.ki.dwFlags);
}
[TestMethod]
public void HasSavedWindows_InitialState_IsFalse()
{
var tracker = new WindowTracker();
Assert.IsFalse(tracker.HasSavedWindows);
}
[TestMethod]
public void InputStructSize_IsConsistent()
{
// Verify that Marshal.SizeOf<INPUT>() returns a consistent value
int size = Marshal.SizeOf<NativeMethods.INPUT>();
Assert.IsTrue(size > 0, "INPUT struct size should be positive");
}
[TestMethod]
public void NativeConstants_HaveExpectedValues()
{
// Validate key constants used by SendShowDesktop
Assert.AreEqual(1, NativeMethods.INPUT_KEYBOARD);
Assert.AreEqual(0x0002u, NativeMethods.KEYEVENTF_KEYUP);
Assert.AreEqual((ushort)0x5B, NativeMethods.VK_LWIN);
Assert.AreEqual((ushort)0x44, NativeMethods.VK_D);
}
[TestMethod]
public void WindowPlacement_Default_HasCorrectLength()
{
var placement = NativeMethods.WINDOWPLACEMENT.Default;
Assert.AreEqual((uint)Marshal.SizeOf<NativeMethods.WINDOWPLACEMENT>(), placement.length);
}
}
}

View File

@@ -0,0 +1,217 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using ManagedCommon;
namespace ShowDesktop
{
/// <summary>
/// Determines whether a screen point corresponds to the desktop wallpaper
/// (not desktop icons, taskbar, or other windows).
/// </summary>
internal static class DesktopDetector
{
private const string ProgmanClassName = "Progman";
private const string WorkerWClassName = "WorkerW";
private const string ShellDllDefViewClassName = "SHELLDLL_DefView";
private const string SysListView32ClassName = "SysListView32";
private const string TaskbarClassName = "Shell_TrayWnd";
private const string SecondaryTaskbarClassName = "Shell_SecondaryTrayWnd";
/// <summary>
/// Returns true if the click at (x, y) lands on the desktop wallpaper surface.
/// </summary>
public static bool IsDesktopClick(int x, int y)
{
try
{
var point = new NativeMethods.POINT(x, y);
IntPtr hwnd = NativeMethods.WindowFromPoint(point);
if (hwnd == IntPtr.Zero)
{
return false;
}
return IsDesktopWindow(hwnd);
}
catch (Exception ex)
{
Logger.LogError($"Error detecting desktop click: {ex.Message}");
return false;
}
}
/// <summary>
/// Returns true if the click at (x, y) lands on empty taskbar space.
/// </summary>
public static bool IsTaskbarClick(int x, int y)
{
try
{
var point = new NativeMethods.POINT(x, y);
IntPtr hwnd = NativeMethods.WindowFromPoint(point);
if (hwnd == IntPtr.Zero)
{
return false;
}
string className = GetClassName(hwnd);
// Direct hit on the taskbar window or secondary taskbar
if (className == TaskbarClassName || className == SecondaryTaskbarClassName)
{
return true;
}
// Walk parents to check if any ancestor is the taskbar
IntPtr parent = hwnd;
while (parent != IntPtr.Zero)
{
string parentClass = GetClassName(parent);
if (parentClass == TaskbarClassName || parentClass == SecondaryTaskbarClassName)
{
return true;
}
IntPtr nextParent = NativeMethods.GetParent(parent);
if (nextParent == parent)
{
break;
}
parent = nextParent;
}
return false;
}
catch (Exception ex)
{
Logger.LogError($"Error detecting taskbar click: {ex.Message}");
return false;
}
}
/// <summary>
/// Checks if the given window handle is part of the desktop surface.
/// The desktop can be either Progman or a WorkerW that hosts SHELLDLL_DefView.
/// </summary>
public static bool IsDesktopWindow(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
{
return false;
}
string className = GetClassName(hwnd);
// Direct Progman hit (the base desktop window)
if (className == ProgmanClassName)
{
return true;
}
// WorkerW hosts the desktop when wallpaper slideshow or certain effects are active
if (className == WorkerWClassName)
{
// Check if this WorkerW contains SHELLDLL_DefView (the desktop icon container)
IntPtr shellView = NativeMethods.FindWindowEx(hwnd, IntPtr.Zero, ShellDllDefViewClassName, null);
if (shellView != IntPtr.Zero)
{
return true;
}
// Also check if it's a sibling WorkerW to the one hosting SHELLDLL_DefView
// (Windows creates multiple WorkerW windows for the desktop)
return HasDesktopSiblingWorkerW(hwnd);
}
// SHELLDLL_DefView is the icon container on the desktop
if (className == ShellDllDefViewClassName)
{
return true;
}
// SysListView32 hosts the actual desktop icons inside SHELLDLL_DefView.
// We consider this a desktop click even though it's on the icon area,
// because the user clicked the desktop surface behind the icons.
if (className == SysListView32ClassName)
{
IntPtr parent = NativeMethods.GetParent(hwnd);
if (parent != IntPtr.Zero && GetClassName(parent) == ShellDllDefViewClassName)
{
return true;
}
}
// Walk up the parent chain
IntPtr current = NativeMethods.GetParent(hwnd);
while (current != IntPtr.Zero)
{
string parentClass = GetClassName(current);
if (parentClass == ProgmanClassName || parentClass == WorkerWClassName)
{
return true;
}
IntPtr next = NativeMethods.GetParent(current);
if (next == current)
{
break;
}
current = next;
}
return false;
}
/// <summary>
/// When wallpaper slideshow is active, Windows creates the desktop using WorkerW windows.
/// One WorkerW has SHELLDLL_DefView, and a sibling WorkerW renders behind it.
/// This checks if any sibling WorkerW of <paramref name="hwnd"/> hosts the desktop.
/// </summary>
private static bool HasDesktopSiblingWorkerW(IntPtr hwnd)
{
// Find Progman first
IntPtr progman = NativeMethods.FindWindow(ProgmanClassName, null);
if (progman == IntPtr.Zero)
{
return false;
}
// Enumerate top-level WorkerW windows looking for one that has SHELLDLL_DefView
bool foundDesktopWorkerW = false;
NativeMethods.EnumWindows(
(enumHwnd, _) =>
{
if (GetClassName(enumHwnd) == WorkerWClassName)
{
IntPtr shellView = NativeMethods.FindWindowEx(enumHwnd, IntPtr.Zero, ShellDllDefViewClassName, null);
if (shellView != IntPtr.Zero)
{
// Found the WorkerW that hosts the desktop icons.
// The clicked WorkerW is a sibling used for the wallpaper surface.
foundDesktopWorkerW = true;
return false; // stop enumeration
}
}
return true;
},
IntPtr.Zero);
return foundDesktopWorkerW;
}
private static string GetClassName(IntPtr hwnd)
{
var buffer = new char[256];
int length = NativeMethods.GetClassName(hwnd, buffer, buffer.Length);
return length > 0 ? new string(buffer, 0, length) : string.Empty;
}
}
}

View File

@@ -0,0 +1,195 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace ShowDesktop
{
internal sealed class DesktopPeek : IDisposable
{
private enum State
{
Idle,
Peeking,
}
private readonly MouseHook _mouseHook;
private readonly FocusWatcher _focusWatcher;
private readonly WindowTracker _windowTracker;
private State _state = State.Idle;
private ShowDesktopSettings _settings;
private bool _disposed;
// Cached settings values
private PeekMode _peekMode;
private bool _requireDoubleClick;
private bool _enableTaskbarPeek;
private bool _enableGamingDetection;
public DesktopPeek(ShowDesktopSettings settings)
{
_settings = settings;
ApplySettings();
_mouseHook = new MouseHook();
_focusWatcher = new FocusWatcher();
_windowTracker = new WindowTracker();
}
public void Start()
{
_mouseHook.DesktopClicked += OnDesktopClicked;
_mouseHook.Install();
_focusWatcher.ForegroundWindowChanged += OnForegroundChanged;
_focusWatcher.Start();
Logger.LogInfo("DesktopPeek started.");
}
public void Stop()
{
_mouseHook.DesktopClicked -= OnDesktopClicked;
_mouseHook.Uninstall();
_focusWatcher.ForegroundWindowChanged -= OnForegroundChanged;
_focusWatcher.Stop();
// Restore windows if we're currently peeking
if (_state == State.Peeking && _peekMode != PeekMode.Native)
{
_windowTracker.Restore(_peekMode);
}
_state = State.Idle;
Logger.LogInfo("DesktopPeek stopped.");
}
public void UpdateSettings(ShowDesktopSettings settings)
{
_settings = settings;
ApplySettings();
Logger.LogInfo("DesktopPeek settings updated.");
}
private void ApplySettings()
{
_peekMode = (PeekMode)_settings.Properties.PeekMode.Value;
_requireDoubleClick = _settings.Properties.RequireDoubleClick.Value;
_enableTaskbarPeek = _settings.Properties.EnableTaskbarPeek.Value;
_enableGamingDetection = _settings.Properties.EnableGamingDetection.Value;
}
private void OnDesktopClicked(MouseHookEventArgs args)
{
try
{
// Ignore taskbar clicks unless explicitly enabled
if (args.IsTaskbar && !_enableTaskbarPeek)
{
return;
}
// Gaming / fullscreen detection
if (_enableGamingDetection && IsFullscreenAppRunning())
{
return;
}
// Double-click requirement
if (_requireDoubleClick && !args.IsDoubleClick)
{
return;
}
if (_state == State.Idle)
{
Logger.LogInfo($"Desktop clicked — peeking (mode={_peekMode}).");
if (_peekMode == PeekMode.Native)
{
WindowTracker.SendShowDesktop();
}
else
{
_windowTracker.CaptureAndMinimize(_peekMode);
}
_state = State.Peeking;
}
else
{
Logger.LogInfo("Desktop clicked — restoring.");
if (_peekMode == PeekMode.Native)
{
WindowTracker.SendShowDesktop(); // toggles back
}
else
{
_windowTracker.Restore(_peekMode);
}
_state = State.Idle;
}
}
catch (Exception ex)
{
Logger.LogError($"Error handling desktop click: {ex.Message}");
}
}
private void OnForegroundChanged()
{
try
{
if (_state != State.Peeking)
{
return;
}
// If the user activated a regular window (not the desktop), cancel peek
IntPtr fg = NativeMethods.GetForegroundWindow();
if (fg == IntPtr.Zero || DesktopDetector.IsDesktopWindow(fg))
{
return;
}
Logger.LogInfo("Foreground changed to non-desktop window — cancelling peek.");
if (_peekMode != PeekMode.Native)
{
_windowTracker.Restore(_peekMode);
}
_state = State.Idle;
}
catch (Exception ex)
{
Logger.LogError($"Error handling foreground change: {ex.Message}");
}
}
private static bool IsFullscreenAppRunning()
{
int hr = NativeMethods.SHQueryUserNotificationState(out var state);
if (hr != 0)
{
return false;
}
return state == NativeMethods.QUERY_USER_NOTIFICATION_STATE.QUNS_RUNNING_D3D_FULL_SCREEN
|| state == NativeMethods.QUERY_USER_NOTIFICATION_STATE.QUNS_BUSY;
}
public void Dispose()
{
if (!_disposed)
{
Stop();
_mouseHook.Dispose();
_focusWatcher.Dispose();
_disposed = true;
}
}
}
}

View File

@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using ManagedCommon;
namespace ShowDesktop
{
internal sealed class FocusWatcher : IDisposable
{
private IntPtr _hookId;
private NativeMethods.WinEventDelegate? _proc;
private bool _disposed;
public event Action? ForegroundWindowChanged;
public void Start()
{
_proc = WinEventCallback;
_hookId = NativeMethods.SetWinEventHook(
NativeMethods.EVENT_SYSTEM_FOREGROUND,
NativeMethods.EVENT_SYSTEM_FOREGROUND,
IntPtr.Zero,
_proc,
0,
0,
NativeMethods.WINEVENT_OUTOFCONTEXT);
if (_hookId == IntPtr.Zero)
{
Logger.LogError("Failed to install WinEvent hook for foreground tracking.");
}
else
{
Logger.LogInfo("FocusWatcher started.");
}
}
public void Stop()
{
if (_hookId != IntPtr.Zero)
{
NativeMethods.UnhookWinEvent(_hookId);
_hookId = IntPtr.Zero;
Logger.LogInfo("FocusWatcher stopped.");
}
}
private void WinEventCallback(
IntPtr hWinEventHook,
uint eventType,
IntPtr hwnd,
int idObject,
int idChild,
uint dwEventThread,
uint dwmsEventTime)
{
ForegroundWindowChanged?.Invoke();
}
public void Dispose()
{
if (!_disposed)
{
Stop();
_disposed = true;
}
}
}
}

View File

@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using ManagedCommon;
namespace ShowDesktop
{
internal sealed class MouseHook : IDisposable
{
private const int DoubleClickTimeMs = 500;
private IntPtr _hookId;
private NativeMethods.LowLevelMouseProc? _proc;
private DateTime _lastClickTime = DateTime.MinValue;
private bool _disposed;
public event Action<MouseHookEventArgs>? DesktopClicked;
public void Install()
{
_proc = HookCallback;
_hookId = NativeMethods.SetWindowsHookEx(
NativeMethods.WH_MOUSE_LL,
_proc,
IntPtr.Zero,
0);
if (_hookId == IntPtr.Zero)
{
Logger.LogError($"Failed to install mouse hook. Error: {Marshal.GetLastWin32Error()}");
}
else
{
Logger.LogInfo("Mouse hook installed successfully.");
}
}
public void Uninstall()
{
if (_hookId != IntPtr.Zero)
{
NativeMethods.UnhookWindowsHookEx(_hookId);
_hookId = IntPtr.Zero;
Logger.LogInfo("Mouse hook uninstalled.");
}
}
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
int msg = wParam.ToInt32();
if (msg == NativeMethods.WM_LBUTTONDOWN || msg == NativeMethods.WM_LBUTTONDBLCLK)
{
var hookStruct = Marshal.PtrToStructure<NativeMethods.MSLLHOOKSTRUCT>(lParam);
int x = hookStruct.pt.X;
int y = hookStruct.pt.Y;
bool isDoubleClick = msg == NativeMethods.WM_LBUTTONDBLCLK;
if (!isDoubleClick)
{
var now = DateTime.UtcNow;
if ((now - _lastClickTime).TotalMilliseconds <= DoubleClickTimeMs)
{
isDoubleClick = true;
}
_lastClickTime = now;
}
bool isDesktop = DesktopDetector.IsDesktopClick(x, y);
bool isTaskbar = !isDesktop && DesktopDetector.IsTaskbarClick(x, y);
if (isDesktop || isTaskbar)
{
var args = new MouseHookEventArgs
{
X = x,
Y = y,
IsDoubleClick = isDoubleClick,
IsTaskbar = isTaskbar,
};
DesktopClicked?.Invoke(args);
}
}
}
return NativeMethods.CallNextHookEx(_hookId, nCode, wParam, lParam);
}
public void Dispose()
{
if (!_disposed)
{
Uninstall();
_disposed = true;
}
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace ShowDesktop
{
internal sealed class MouseHookEventArgs
{
public int X { get; init; }
public int Y { get; init; }
public bool IsDoubleClick { get; init; }
public bool IsTaskbar { get; init; }
}
}

View File

@@ -0,0 +1,346 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace ShowDesktop
{
internal static partial class NativeMethods
{
// Hook constants
public const int WH_MOUSE_LL = 14;
// Mouse messages
public const int WM_LBUTTONDOWN = 0x0201;
public const int WM_LBUTTONUP = 0x0202;
public const int WM_LBUTTONDBLCLK = 0x0203;
public const int WM_QUIT = 0x0012;
// ShowWindow constants
public const int SW_HIDE = 0;
public const int SW_SHOWNORMAL = 1;
public const int SW_SHOWMINIMIZED = 2;
public const int SW_SHOWMAXIMIZED = 3;
public const int SW_MINIMIZE = 6;
public const int SW_RESTORE = 9;
// WinEvent constants
public const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
public const uint WINEVENT_OUTOFCONTEXT = 0x0000;
// GetWindow constants
public const uint GW_OWNER = 4;
// GetWindowLong constants
public const int GWL_STYLE = -16;
public const int GWL_EXSTYLE = -20;
// Window styles
public const long WS_VISIBLE = 0x10000000L;
public const long WS_EX_TOOLWINDOW = 0x00000080L;
public const long WS_EX_APPWINDOW = 0x00040000L;
public const long WS_EX_NOACTIVATE = 0x08000000L;
// DWM constants
public const int DWMWA_CLOAKED = 14;
// SendInput constants
public const int INPUT_KEYBOARD = 1;
public const uint KEYEVENTF_KEYUP = 0x0002;
public const ushort VK_LWIN = 0x5B;
public const ushort VK_D = 0x44;
// ChildWindowFromPointEx flags
public const uint CWP_SKIPINVISIBLE = 0x0001;
public const uint CWP_SKIPDISABLED = 0x0002;
public const uint CWP_SKIPTRANSPARENT = 0x0004;
public const uint CWP_ALL = CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT;
// Notification state
public enum QUERY_USER_NOTIFICATION_STATE
{
QUNS_NOT_PRESENT = 1,
QUNS_BUSY = 2,
QUNS_RUNNING_D3D_FULL_SCREEN = 3,
QUNS_PRESENTATION_MODE = 4,
QUNS_ACCEPTS_NOTIFICATIONS = 5,
QUNS_QUIET_TIME = 6,
QUNS_APP = 7,
}
// Delegates
public delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
public delegate void WinEventDelegate(
IntPtr hWinEventHook,
uint eventType,
IntPtr hwnd,
int idObject,
int idChild,
uint dwEventThread,
uint dwmsEventTime);
public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
// Structs
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public POINT pt;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
public uint length;
public uint flags;
public uint showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
public static WINDOWPLACEMENT Default
{
get
{
var wp = default(WINDOWPLACEMENT);
wp.length = (uint)Marshal.SizeOf<WINDOWPLACEMENT>();
return wp;
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public int type;
public INPUTUNION u;
}
[StructLayout(LayoutKind.Explicit)]
public struct INPUTUNION
{
[FieldOffset(0)]
public MOUSEINPUT mi;
[FieldOffset(0)]
public KEYBDINPUT ki;
[FieldOffset(0)]
public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct HARDWAREINPUT
{
public uint uMsg;
public ushort wParamL;
public ushort wParamH;
}
// Mouse hook
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[LibraryImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
// Message loop
[DllImport("user32.dll")]
public static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll")]
public static extern IntPtr DispatchMessage(ref MSG lpMsg);
[LibraryImport("user32.dll")]
public static partial void PostQuitMessage(int nExitCode);
// Window enumeration and management
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool SetForegroundWindow(IntPtr hWnd);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool IsWindowVisible(IntPtr hWnd);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool IsIconic(IntPtr hWnd);
[LibraryImport("user32.dll")]
public static partial IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[LibraryImport("user32.dll")]
public static partial IntPtr GetForegroundWindow();
[LibraryImport("user32.dll")]
public static partial long GetWindowLongA(IntPtr hWnd, int nIndex);
// WinEvent hook
[DllImport("user32.dll")]
public static extern IntPtr SetWinEventHook(
uint eventMin,
uint eventMax,
IntPtr hmodWinEventProc,
WinEventDelegate lpfnWinEventProc,
uint idProcess,
uint idThread,
uint dwFlags);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool UnhookWinEvent(IntPtr hWinEventHook);
// Window identification
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindWindow(string? lpClassName, string? lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetClassName(IntPtr hWnd, [Out] char[] lpClassName, int nMaxCount);
[LibraryImport("user32.dll")]
public static partial IntPtr GetParent(IntPtr hWnd);
[LibraryImport("user32.dll")]
public static partial IntPtr GetDesktopWindow();
[LibraryImport("user32.dll")]
public static partial IntPtr GetShellWindow();
// Hit-testing
[LibraryImport("user32.dll")]
public static partial IntPtr WindowFromPoint(POINT point);
[LibraryImport("user32.dll")]
public static partial IntPtr ChildWindowFromPointEx(IntPtr hwndParent, POINT point, uint uFlags);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetCursorPos(out POINT lpPoint);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
// Window text
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, [Out] char[] lpString, int nMaxCount);
[LibraryImport("user32.dll")]
public static partial int GetWindowTextLengthA(IntPtr hWnd);
// DWM
[LibraryImport("dwmapi.dll")]
public static partial int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out int pvAttribute, int cbAttribute);
// SendInput for Win+D
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
// Process info
[LibraryImport("user32.dll")]
public static partial uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
// Notification state for gaming detection
[LibraryImport("shell32.dll")]
public static partial int SHQueryUserNotificationState(out QUERY_USER_NOTIFICATION_STATE pquns);
// EnumChildWindows for desktop icon detection
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string? lpszClass, string? lpszWindow);
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace ShowDesktop
{
public enum PeekMode
{
Native = 0,
Minimize = 1,
FlyAway = 2,
}
}

View File

@@ -0,0 +1,165 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace ShowDesktop
{
internal sealed class Program
{
private const string TerminateEventName = "Local\\PowerToysShowDesktop-TerminateEvent-7b3a9c1e-2d4f-5a6b-8e7c-9f0d1a2b3c4e";
private const string MutexName = "PowerToys.ShowDesktop.Mutex";
private static DesktopPeek? _peek;
private static FileSystemWatcher? _settingsWatcher;
[STAThread]
private static int Main(string[] args)
{
Logger.InitializeLogger("\\ShowDesktop\\Logs");
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShowDesktopEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
Logger.LogWarning("ShowDesktop is disabled by GPO policy.");
return 0;
}
// Parse runner PID
int runnerPid = 0;
if (args.Length > 0)
{
_ = int.TryParse(args[0], out runnerPid);
}
// Single instance
using var mutex = new Mutex(true, MutexName, out bool createdNew);
if (!createdNew)
{
Logger.LogWarning("Another instance of ShowDesktop is already running.");
return 0;
}
Logger.LogInfo($"ShowDesktop starting. Runner PID={runnerPid}");
// Open the terminate event signaled by the runner when the module should exit
using var terminateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, TerminateEventName);
// Watch for runner exit
if (runnerPid > 0)
{
try
{
var runner = Process.GetProcessById(runnerPid);
runner.EnableRaisingEvents = true;
runner.Exited += (s, e) =>
{
Logger.LogInfo("Runner process exited — shutting down.");
NativeMethods.PostQuitMessage(0);
};
}
catch (Exception ex)
{
Logger.LogError($"Failed to watch runner process: {ex.Message}");
}
}
// Watch for terminate event from runner
var registeredWait = ThreadPool.RegisterWaitForSingleObject(
terminateEvent,
(state, timedOut) =>
{
Logger.LogInfo("Terminate event received — shutting down.");
NativeMethods.PostQuitMessage(0);
},
null,
-1,
true);
// Load settings
var settingsUtils = SettingsUtils.Default;
ShowDesktopSettings settings;
try
{
settings = settingsUtils.GetSettingsOrDefault<ShowDesktopSettings>(ShowDesktopSettings.ModuleName);
}
catch (Exception ex)
{
Logger.LogError($"Failed to load settings, using defaults: {ex.Message}");
settings = new ShowDesktopSettings();
}
// Create and start the core
_peek = new DesktopPeek(settings);
_peek.Start();
// Watch for settings file changes
SetupSettingsWatcher(settingsUtils);
// Run the message loop (required for the low-level mouse hook)
Win32MessageLoop.Run();
// Cleanup
Logger.LogInfo("ShowDesktop shutting down.");
_peek.Stop();
_peek.Dispose();
_settingsWatcher?.Dispose();
registeredWait.Unregister(null);
return 0;
}
private static void SetupSettingsWatcher(SettingsUtils settingsUtils)
{
try
{
string settingsDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"PowerToys",
ShowDesktopSettings.ModuleName);
if (!Directory.Exists(settingsDir))
{
Directory.CreateDirectory(settingsDir);
}
_settingsWatcher = new FileSystemWatcher(settingsDir, "settings.json")
{
NotifyFilter = NotifyFilters.LastWrite,
EnableRaisingEvents = true,
};
_settingsWatcher.Changed += (s, e) =>
{
try
{
Logger.LogInfo("Settings file changed, reloading.");
// Small delay to avoid reading a partially-written file
Thread.Sleep(200);
var newSettings = settingsUtils.GetSettingsOrDefault<ShowDesktopSettings>(ShowDesktopSettings.ModuleName);
_peek?.UpdateSettings(newSettings);
}
catch (Exception ex)
{
Logger.LogError($"Error reloading settings: {ex.Message}");
}
};
Logger.LogInfo($"Watching settings at: {settingsDir}");
}
catch (Exception ex)
{
Logger.LogError($"Failed to set up settings watcher: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)</OutputPath>
<Nullable>enable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<UseWindowsForms>False</UseWindowsForms>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
<AssemblyName>PowerToys.ShowDesktop</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>ShowDesktop.UnitTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace ShowDesktop
{
internal static class Win32MessageLoop
{
public static void Run()
{
while (NativeMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0) > 0)
{
NativeMethods.TranslateMessage(ref msg);
NativeMethods.DispatchMessage(ref msg);
}
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace ShowDesktop
{
internal sealed class WindowInfo
{
public IntPtr Hwnd { get; }
public NativeMethods.WINDOWPLACEMENT Placement { get; }
public WindowInfo(IntPtr hwnd, NativeMethods.WINDOWPLACEMENT placement)
{
Hwnd = hwnd;
Placement = placement;
}
}
}

View File

@@ -0,0 +1,202 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ManagedCommon;
namespace ShowDesktop
{
internal sealed class WindowTracker
{
private readonly List<WindowInfo> _savedWindows = new();
public bool HasSavedWindows => _savedWindows.Count > 0;
/// <summary>
/// Captures all visible application windows and either minimizes or moves them off-screen.
/// </summary>
public void CaptureAndMinimize(PeekMode mode)
{
_savedWindows.Clear();
NativeMethods.EnumWindows(
(hwnd, _) =>
{
if (!ShouldTrack(hwnd))
{
return true;
}
var placement = NativeMethods.WINDOWPLACEMENT.Default;
NativeMethods.GetWindowPlacement(hwnd, ref placement);
_savedWindows.Add(new WindowInfo(hwnd, placement));
switch (mode)
{
case PeekMode.Minimize:
NativeMethods.ShowWindow(hwnd, NativeMethods.SW_MINIMIZE);
break;
case PeekMode.FlyAway:
NativeMethods.ShowWindow(hwnd, NativeMethods.SW_MINIMIZE);
break;
}
return true;
},
IntPtr.Zero);
Logger.LogInfo($"Captured {_savedWindows.Count} windows (mode={mode}).");
}
/// <summary>
/// Restores all previously saved windows to their original positions.
/// </summary>
public void Restore(PeekMode mode)
{
for (int i = _savedWindows.Count - 1; i >= 0; i--)
{
var info = _savedWindows[i];
var placement = info.Placement;
NativeMethods.SetWindowPlacement(info.Hwnd, ref placement);
}
Logger.LogInfo($"Restored {_savedWindows.Count} windows.");
_savedWindows.Clear();
}
/// <summary>
/// Uses Win+D key combination to toggle show desktop (native Windows behavior).
/// </summary>
public static void SendShowDesktop()
{
var inputs = new NativeMethods.INPUT[4];
// Win key down
inputs[0].type = NativeMethods.INPUT_KEYBOARD;
inputs[0].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[0].u.ki.dwFlags = 0;
// D key down
inputs[1].type = NativeMethods.INPUT_KEYBOARD;
inputs[1].u.ki.wVk = NativeMethods.VK_D;
inputs[1].u.ki.dwFlags = 0;
// D key up
inputs[2].type = NativeMethods.INPUT_KEYBOARD;
inputs[2].u.ki.wVk = NativeMethods.VK_D;
inputs[2].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
// Win key up
inputs[3].type = NativeMethods.INPUT_KEYBOARD;
inputs[3].u.ki.wVk = NativeMethods.VK_LWIN;
inputs[3].u.ki.dwFlags = NativeMethods.KEYEVENTF_KEYUP;
uint sent = NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<NativeMethods.INPUT>());
if (sent != inputs.Length)
{
Logger.LogError($"SendInput sent only {sent}/{inputs.Length} inputs. Error: {Marshal.GetLastWin32Error()}");
}
else
{
Logger.LogInfo("Sent Win+D (native show desktop).");
}
}
/// <summary>
/// Determines whether a window should be tracked for minimize/restore.
/// </summary>
private static bool ShouldTrack(IntPtr hwnd)
{
// Must be visible
if (!NativeMethods.IsWindowVisible(hwnd))
{
return false;
}
// Skip already-minimized windows
if (NativeMethods.IsIconic(hwnd))
{
return false;
}
// Skip cloaked windows (UWP suspended, virtual desktop, etc.)
if (IsCloaked(hwnd))
{
return false;
}
// Must be a top-level "app" window: has no owner and is either WS_EX_APPWINDOW
// or not a tool window.
long exStyle = NativeMethods.GetWindowLongA(hwnd, NativeMethods.GWL_EXSTYLE);
IntPtr owner = NativeMethods.GetWindow(hwnd, NativeMethods.GW_OWNER);
bool isToolWindow = (exStyle & NativeMethods.WS_EX_TOOLWINDOW) != 0;
bool isAppWindow = (exStyle & NativeMethods.WS_EX_APPWINDOW) != 0;
bool isNoActivate = (exStyle & NativeMethods.WS_EX_NOACTIVATE) != 0;
if (isNoActivate && !isAppWindow)
{
return false;
}
if (owner != IntPtr.Zero && !isAppWindow)
{
return false;
}
if (isToolWindow && !isAppWindow)
{
return false;
}
// Skip shell windows (desktop, taskbar)
if (IsShellWindow(hwnd))
{
return false;
}
// Must have a non-empty title
int titleLength = NativeMethods.GetWindowTextLengthA(hwnd);
if (titleLength == 0)
{
return false;
}
return true;
}
private static bool IsCloaked(IntPtr hwnd)
{
int hr = NativeMethods.DwmGetWindowAttribute(
hwnd,
NativeMethods.DWMWA_CLOAKED,
out int cloaked,
sizeof(int));
return hr == 0 && cloaked != 0;
}
private static bool IsShellWindow(IntPtr hwnd)
{
IntPtr shellWindow = NativeMethods.GetShellWindow();
if (hwnd == shellWindow)
{
return true;
}
string className = GetClassName(hwnd);
return className is "Progman" or "WorkerW" or "Shell_TrayWnd" or "Shell_SecondaryTrayWnd";
}
private static string GetClassName(IntPtr hwnd)
{
var buffer = new char[256];
int length = NativeMethods.GetClassName(hwnd, buffer, buffer.Length);
return length > 0 ? new string(buffer, 0, length) : string.Empty;
}
}
}

View File

@@ -0,0 +1 @@
#include "resource.h"

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>showdesktop</RootNamespace>
<ProjectName>ShowDesktopModuleInterface</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup>
<TargetName>PowerToys.ShowDesktopModuleInterface</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\;$(RepoRoot)src\common\inc;$(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ShowDesktopModuleInterface.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,187 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/logger/logger.h>
#include <common/utils/resources.h>
#include <common/utils/winapi_error.h>
#include <shellapi.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/interop/shared_constants.h>
namespace NonLocalizable
{
const wchar_t ModulePath[] = L"PowerToys.ShowDesktop.exe";
const wchar_t ModuleKey[] = L"ShowDesktop";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
class ShowDesktopModuleInterface : public PowertoyModuleIface
{
public:
// Return the localized display name of the powertoy
virtual PCWSTR get_name() override
{
return app_name.c_str();
}
// Return the non localized key of the powertoy, this will be cached by the runner
virtual const wchar_t* get_key() override
{
return app_key.c_str();
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredShowDesktopEnabledValue();
}
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
PowerToysSettings::Settings settings(hinstance, get_name());
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Passes JSON with the configuration settings for the powertoy.
virtual void set_config(const wchar_t* config) override
{
try
{
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
values.save_to_settings_file();
}
catch (std::exception&)
{
// Improper JSON.
}
}
virtual bool on_hotkey(size_t /*hotkeyId*/) override
{
return false;
}
virtual size_t get_hotkeys(Hotkey* /*hotkeys*/, size_t /*buffer_size*/) override
{
return 0;
}
// Enable the powertoy
virtual void enable()
{
Logger::info("ShowDesktop enabling");
Enable();
}
// Disable the powertoy
virtual void disable()
{
Logger::info("ShowDesktop disabling");
Disable();
}
// Returns if the powertoy is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
Disable();
delete this;
}
ShowDesktopModuleInterface()
{
app_name = L"ShowDesktop";
app_key = NonLocalizable::ModuleKey;
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_DESKTOP_TERMINATE_EVENT);
}
private:
void Enable()
{
m_enabled = true;
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = NonLocalizable::ModulePath;
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei) == false)
{
Logger::error(L"Failed to start ShowDesktop");
auto message = get_last_error_message(GetLastError());
if (message.has_value())
{
Logger::error(message.value());
}
}
else
{
m_hProcess = sei.hProcess;
}
}
void Disable()
{
m_enabled = false;
SetEvent(m_hTerminateEvent);
// Wait for 1.5 seconds for the process to end correctly
WaitForSingleObject(m_hProcess, 1500);
// If process is still running, terminate it
if (m_hProcess)
{
TerminateProcess(m_hProcess, 0);
m_hProcess = nullptr;
}
}
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
std::wstring app_name;
std::wstring app_key;
bool m_enabled = false;
HANDLE m_hProcess = nullptr;
HANDLE m_hTerminateEvent;
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new ShowDesktopModuleInterface();
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
</packages>

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,9 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Unknwn.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <wil\common.h>
#include <wil\result.h>

View File

@@ -0,0 +1,2 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.

View File

@@ -0,0 +1,2 @@
#pragma once
#include <SDKDDKVer.h>

View File

@@ -288,6 +288,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"PowerToys.LightSwitchModuleInterface.dll",
L"PowerToys.PowerDisplayModuleInterface.dll",
L"PowerToys.GrabAndMoveModuleInterface.dll",
L"PowerToys.ShowDesktopModuleInterface.dll",
};
for (auto moduleSubdir : knownModules)

View File

@@ -45,6 +45,7 @@ internal static class ModuleGpoHelper
ModuleType.PowerOCR => GPOWrapper.GetConfiguredTextExtractorEnabledValue(),
ModuleType.ZoomIt => GPOWrapper.GetConfiguredZoomItEnabledValue(),
ModuleType.GrabAndMove => GPOWrapper.GetConfiguredGrabAndMoveEnabledValue(),
ModuleType.ShowDesktop => GPOWrapper.GetConfiguredShowDesktopEnabledValue(),
_ => GpoRuleConfigured.Unavailable,
};
}

View File

@@ -580,6 +580,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool showDesktop; // defaulting to off
[JsonPropertyName("ShowDesktop")]
public bool ShowDesktop
{
get => showDesktop;
set
{
if (showDesktop != value)
{
LogTelemetryEvent(value);
showDesktop = value;
NotifyChange();
}
}
}
private void NotifyChange()
{
notifyEnabledChangedAction?.Invoke();

View File

@@ -76,6 +76,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
ModuleType.PowerDisplay => generalSettingsConfig.Enabled.PowerDisplay,
ModuleType.Workspaces => generalSettingsConfig.Enabled.Workspaces,
ModuleType.GrabAndMove => generalSettingsConfig.Enabled.GrabAndMove,
ModuleType.ShowDesktop => generalSettingsConfig.Enabled.ShowDesktop,
ModuleType.ZoomIt => generalSettingsConfig.Enabled.ZoomIt,
ModuleType.GeneralSettings => generalSettingsConfig.EnableQuickAccess,
_ => false,
@@ -117,6 +118,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
case ModuleType.PowerDisplay: generalSettingsConfig.Enabled.PowerDisplay = isEnabled; break;
case ModuleType.Workspaces: generalSettingsConfig.Enabled.Workspaces = isEnabled; break;
case ModuleType.GrabAndMove: generalSettingsConfig.Enabled.GrabAndMove = isEnabled; break;
case ModuleType.ShowDesktop: generalSettingsConfig.Enabled.ShowDesktop = isEnabled; break;
case ModuleType.ZoomIt: generalSettingsConfig.Enabled.ZoomIt = isEnabled; break;
case ModuleType.GeneralSettings: generalSettingsConfig.EnableQuickAccess = isEnabled; break;
}
@@ -161,6 +163,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
ModuleType.PowerOCR => PowerOcrSettings.ModuleName,
ModuleType.Workspaces => WorkspacesSettings.ModuleName,
ModuleType.GrabAndMove => GrabAndMoveSettings.ModuleName,
ModuleType.ShowDesktop => ShowDesktopSettings.ModuleName,
ModuleType.ZoomIt => ZoomItSettings.ModuleName,
_ => moduleType.ToString(),
};

View File

@@ -76,6 +76,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonSerializable(typeof(ShortcutGuideSettings))]
[JsonSerializable(typeof(WorkspacesSettings))]
[JsonSerializable(typeof(GrabAndMoveSettings))]
[JsonSerializable(typeof(ShowDesktopSettings))]
[JsonSerializable(typeof(ZoomItSettings))]
// Properties Classes
@@ -117,6 +118,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonSerializable(typeof(ShortcutGuideProperties))]
[JsonSerializable(typeof(WorkspacesProperties))]
[JsonSerializable(typeof(GrabAndMoveProperties))]
[JsonSerializable(typeof(ShowDesktopProperties))]
[JsonSerializable(typeof(ZoomItProperties))]
// Base Property Types (used throughout settings)

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class ShowDesktopProperties
{
public const int DefaultPeekMode = 0;
public const bool DefaultRequireDoubleClick = false;
public const bool DefaultEnableTaskbarPeek = false;
public const bool DefaultEnableGamingDetection = true;
public const int DefaultFlyAwayAnimationDurationMs = 300;
public ShowDesktopProperties()
{
PeekMode = new IntProperty(DefaultPeekMode);
RequireDoubleClick = new BoolProperty(DefaultRequireDoubleClick);
EnableTaskbarPeek = new BoolProperty(DefaultEnableTaskbarPeek);
EnableGamingDetection = new BoolProperty(DefaultEnableGamingDetection);
FlyAwayAnimationDurationMs = new IntProperty(DefaultFlyAwayAnimationDurationMs);
}
[JsonPropertyName("peek-mode")]
public IntProperty PeekMode { get; set; }
[JsonPropertyName("require-double-click")]
public BoolProperty RequireDoubleClick { get; set; }
[JsonPropertyName("enable-taskbar-peek")]
public BoolProperty EnableTaskbarPeek { get; set; }
[JsonPropertyName("enable-gaming-detection")]
public BoolProperty EnableGamingDetection { get; set; }
[JsonPropertyName("fly-away-animation-duration-ms")]
public IntProperty FlyAwayAnimationDurationMs { get; set; }
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class ShowDesktopSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "ShowDesktop";
public const string ModuleVersion = "0.0.1";
public ShowDesktopSettings()
{
Name = ModuleName;
Version = ModuleVersion;
Properties = new ShowDesktopProperties();
}
[JsonPropertyName("properties")]
public ShowDesktopProperties Properties { get; set; }
public string GetModuleName()
{
return Name;
}
public ModuleType GetModuleType() => ModuleType.ShowDesktop;
public bool UpgradeSettingsConfiguration()
{
return false;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

View File

@@ -48,6 +48,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
case ModuleType.PowerDisplay: return GPOWrapper.GetConfiguredPowerDisplayEnabledValue();
case ModuleType.ZoomIt: return GPOWrapper.GetConfiguredZoomItEnabledValue();
case ModuleType.GrabAndMove: return GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
case ModuleType.ShowDesktop: return GPOWrapper.GetConfiguredShowDesktopEnabledValue();
default: return GpoRuleConfigured.Unavailable;
}
}
@@ -89,6 +90,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
ModuleType.PowerDisplay => typeof(PowerDisplayPage),
ModuleType.ZoomIt => typeof(ZoomItPage),
ModuleType.GrabAndMove => typeof(GrabAndMovePage),
ModuleType.ShowDesktop => typeof(ShowDesktopPage),
_ => typeof(DashboardPage), // never called, all values listed above
};
}

View File

@@ -38,6 +38,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
[JsonSerializable(typeof(PowerDisplaySettings))]
[JsonSerializable(typeof(RegistryPreviewSettings))]
[JsonSerializable(typeof(ShortcutConflictProperties))]
[JsonSerializable(typeof(ShowDesktopSettings))]
[JsonSerializable(typeof(ShortcutGuideSettings))]
[JsonSerializable(typeof(WINDOWPLACEMENT))]
[JsonSerializable(typeof(WorkspacesSettings))]

View File

@@ -448,6 +448,7 @@ namespace Microsoft.PowerToys.Settings.UI
case "CmdPal": return typeof(CmdPalPage);
case "ZoomIt": return typeof(ZoomItPage);
case "GrabAndMove": return typeof(GrabAndMovePage);
case "ShowDesktop": return typeof(ShowDesktopPage);
default:
// Fallback to Dashboard
Debug.Assert(false, "Unexpected SettingsWindow argument value");

View File

@@ -280,6 +280,12 @@
helpers:NavHelper.NavigateTo="views:GrabAndMovePage"
AutomationProperties.AutomationId="GrabAndMoveNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/GrabAndMove.png}" />
<NavigationViewItem
x:Name="ShowDesktopNavigationItem"
x:Uid="Shell_ShowDesktop"
helpers:NavHelper.NavigateTo="views:ShowDesktopPage"
AutomationProperties.AutomationId="ShowDesktopNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShowDesktop.png}" />
</NavigationViewItem.MenuItems>
</NavigationViewItem>

View File

@@ -0,0 +1,80 @@
<local:NavigablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.ShowDesktopPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<local:NavigablePage.Resources />
<controls:SettingsPageControl
x:Uid="ShowDesktop"
IsTabStop="False"
ModuleImageSource="ms-appx:///Assets/Settings/Modules/ShowDesktop.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
x:Uid="ShowDesktop_EnableToggleControl_HeaderText"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShowDesktop.png}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:GPOInfoControl>
<controls:SettingsGroup x:Uid="ShowDesktop_Behavior_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
x:Uid="ShowDesktop_PeekMode"
HeaderIcon="{ui:FontIcon Glyph=&#xE7B3;}">
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.PeekMode, Mode=TwoWay}">
<ComboBoxItem x:Uid="ShowDesktop_PeekMode_Native" />
<ComboBoxItem x:Uid="ShowDesktop_PeekMode_Minimize" />
<ComboBoxItem x:Uid="ShowDesktop_PeekMode_FlyAway" />
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
x:Uid="ShowDesktop_RequireDoubleClick"
HeaderIcon="{ui:FontIcon Glyph=&#xE8B0;}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RequireDoubleClick, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
x:Uid="ShowDesktop_EnableTaskbarPeek"
HeaderIcon="{ui:FontIcon Glyph=&#xE7C4;}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableTaskbarPeek, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="ShowDesktop_Advanced_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
x:Uid="ShowDesktop_EnableGamingDetection"
HeaderIcon="{ui:FontIcon Glyph=&#xE7FC;}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableGamingDetection, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
x:Uid="ShowDesktop_FlyAwayAnimationDuration"
HeaderIcon="{ui:FontIcon Glyph=&#xE916;}"
Visibility="{x:Bind ViewModel.IsFlyAwayMode, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<Slider
MinWidth="{StaticResource SettingActionControlMinWidth}"
LargeChange="100"
Maximum="1000"
Minimum="100"
SmallChange="50"
StepFrequency="50"
Value="{x:Bind ViewModel.FlyAwayAnimationDurationMs, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>
<controls:SettingsPageControl.PrimaryLinks>
<controls:PageLink x:Uid="LearnMore_ShowDesktop" Link="https://aka.ms/PowerToysOverview_ShowDesktop" />
</controls:SettingsPageControl.PrimaryLinks>
</controls:SettingsPageControl>
</local:NavigablePage>

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class ShowDesktopPage : NavigablePage, IRefreshablePage
{
private ShowDesktopViewModel ViewModel { get; set; }
public ShowDesktopPage()
{
var settingsUtils = SettingsUtils.Default;
ViewModel = new ShowDesktopViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<ShowDesktopSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
Loaded += (s, e) => ViewModel.OnPageLoaded();
}
public void RefreshEnabledState()
{
ViewModel.RefreshEnabledState();
}
}
}

View File

@@ -5979,4 +5979,75 @@ Text uses the current drawing color.</value>
<value>Example: outlook.exe</value>
<comment>{Locked="outlook.exe"}</comment>
</data>
<data name="ShowDesktop.ModuleDescription" xml:space="preserve">
<value>Click on empty desktop wallpaper to minimize all windows. Click again to restore them.</value>
<comment>"Show Desktop" is the name of the utility</comment>
</data>
<data name="ShowDesktop.ModuleTitle" xml:space="preserve">
<value>Show Desktop</value>
<comment>"Show Desktop" is the name of the utility</comment>
</data>
<data name="Shell_ShowDesktop.Content" xml:space="preserve">
<value>Show Desktop</value>
<comment>"Show Desktop" is the name of the utility</comment>
</data>
<data name="ShowDesktop_EnableToggleControl_HeaderText.Header" xml:space="preserve">
<value>Show Desktop</value>
<comment>"Show Desktop" is the name of the utility</comment>
</data>
<data name="LearnMore_ShowDesktop.Text" xml:space="preserve">
<value>Learn more about Show Desktop</value>
<comment>"Show Desktop" is the name of the utility</comment>
</data>
<data name="ShowDesktop_PeekMode.Header" xml:space="preserve">
<value>Peek mode</value>
</data>
<data name="ShowDesktop_PeekMode.Description" xml:space="preserve">
<value>Choose how windows are hidden when revealing the desktop</value>
</data>
<data name="ShowDesktop_PeekMode_Native.Content" xml:space="preserve">
<value>Native Show Desktop</value>
<comment>Uses the built-in Windows Show Desktop mechanism</comment>
</data>
<data name="ShowDesktop_PeekMode_Minimize.Content" xml:space="preserve">
<value>Minimize</value>
<comment>Minimizes each window individually</comment>
</data>
<data name="ShowDesktop_PeekMode_FlyAway.Content" xml:space="preserve">
<value>Fly Away</value>
<comment>Animates windows flying off-screen</comment>
</data>
<data name="ShowDesktop_RequireDoubleClick.Header" xml:space="preserve">
<value>Require double-click</value>
</data>
<data name="ShowDesktop_RequireDoubleClick.Description" xml:space="preserve">
<value>When enabled, you must double-click the wallpaper to reveal the desktop</value>
</data>
<data name="ShowDesktop_EnableTaskbarPeek.Header" xml:space="preserve">
<value>Enable taskbar peek</value>
</data>
<data name="ShowDesktop_EnableTaskbarPeek.Description" xml:space="preserve">
<value>Also trigger peek when clicking on empty taskbar space</value>
</data>
<data name="ShowDesktop_EnableGamingDetection.Header" xml:space="preserve">
<value>Pause during fullscreen gaming</value>
</data>
<data name="ShowDesktop_EnableGamingDetection.Description" xml:space="preserve">
<value>Automatically disable Show Desktop when a fullscreen game is detected</value>
</data>
<data name="ShowDesktop_FlyAwayAnimationDuration.Header" xml:space="preserve">
<value>Fly Away animation duration (ms)</value>
</data>
<data name="ShowDesktop_FlyAwayAnimationDuration.Description" xml:space="preserve">
<value>Duration of the animation when using Fly Away mode</value>
</data>
<data name="ShowDesktop_BehaviorSettingsGroup.Header" xml:space="preserve">
<value>Behavior</value>
</data>
<data name="ShowDesktop_Behavior_GroupSettings.Header" xml:space="preserve">
<value>Behavior</value>
</data>
<data name="ShowDesktop_Advanced_GroupSettings.Header" xml:space="preserve">
<value>Advanced</value>
</data>
</root>

View File

@@ -503,6 +503,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
ModuleType.PowerAccent => GetModuleItemsPowerAccent(),
ModuleType.Workspaces => GetModuleItemsWorkspaces(),
ModuleType.GrabAndMove => new ObservableCollection<DashboardModuleItem>(),
ModuleType.ShowDesktop => new ObservableCollection<DashboardModuleItem>(),
ModuleType.RegistryPreview => GetModuleItemsRegistryPreview(),
ModuleType.MeasureTool => GetModuleItemsMeasureTool(),
ModuleType.ShortcutGuide => GetModuleItemsShortcutGuide(),

View File

@@ -0,0 +1,200 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class ShowDesktopViewModel : PageViewModelBase
{
protected override string ModuleName => ShowDesktopSettings.ModuleName;
private SettingsUtils SettingsUtils { get; set; }
private GeneralSettings GeneralSettingsConfig { get; set; }
private ShowDesktopSettings Settings { get; set; }
private Func<string, int> SendConfigMSG { get; }
public ShowDesktopViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<ShowDesktopSettings> moduleSettingsRepository, Func<string, int> ipcMSGCallBackFunc)
{
ArgumentNullException.ThrowIfNull(settingsUtils);
SettingsUtils = settingsUtils;
// To obtain the general settings configurations of PowerToys Settings.
ArgumentNullException.ThrowIfNull(settingsRepository);
GeneralSettingsConfig = settingsRepository.SettingsConfig;
InitializeEnabledValue();
// To obtain the settings configurations of ShowDesktop.
ArgumentNullException.ThrowIfNull(moduleSettingsRepository);
Settings = moduleSettingsRepository.SettingsConfig;
_peekMode = Settings.Properties.PeekMode.Value;
_requireDoubleClick = Settings.Properties.RequireDoubleClick.Value;
_enableTaskbarPeek = Settings.Properties.EnableTaskbarPeek.Value;
_enableGamingDetection = Settings.Properties.EnableGamingDetection.Value;
_flyAwayAnimationDurationMs = Settings.Properties.FlyAwayAnimationDurationMs.Value;
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
private void InitializeEnabledValue()
{
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredShowDesktopEnabledValue();
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
{
// Get the enabled state from GPO.
_enabledStateIsGPOConfigured = true;
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
}
else
{
_isEnabled = GeneralSettingsConfig.Enabled.ShowDesktop;
}
}
public bool IsEnabled
{
get => _isEnabled;
set
{
if (_enabledStateIsGPOConfigured)
{
// If it's GPO configured, shouldn't be able to change this state.
return;
}
if (value != _isEnabled)
{
_isEnabled = value;
// Set the status in the general settings configuration
GeneralSettingsConfig.Enabled.ShowDesktop = value;
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(snd.ToString());
OnPropertyChanged(nameof(IsEnabled));
}
}
}
public bool IsEnabledGpoConfigured
{
get => _enabledStateIsGPOConfigured;
}
public int PeekMode
{
get => _peekMode;
set
{
if (value != _peekMode)
{
_peekMode = value;
Settings.Properties.PeekMode.Value = value;
NotifyPropertyChanged();
OnPropertyChanged(nameof(IsFlyAwayMode));
}
}
}
public bool RequireDoubleClick
{
get => _requireDoubleClick;
set
{
if (value != _requireDoubleClick)
{
_requireDoubleClick = value;
Settings.Properties.RequireDoubleClick.Value = value;
NotifyPropertyChanged();
}
}
}
public bool EnableTaskbarPeek
{
get => _enableTaskbarPeek;
set
{
if (value != _enableTaskbarPeek)
{
_enableTaskbarPeek = value;
Settings.Properties.EnableTaskbarPeek.Value = value;
NotifyPropertyChanged();
}
}
}
public bool EnableGamingDetection
{
get => _enableGamingDetection;
set
{
if (value != _enableGamingDetection)
{
_enableGamingDetection = value;
Settings.Properties.EnableGamingDetection.Value = value;
NotifyPropertyChanged();
}
}
}
public int FlyAwayAnimationDurationMs
{
get => _flyAwayAnimationDurationMs;
set
{
if (value != _flyAwayAnimationDurationMs)
{
_flyAwayAnimationDurationMs = value;
Settings.Properties.FlyAwayAnimationDurationMs.Value = value;
NotifyPropertyChanged();
}
}
}
public bool IsFlyAwayMode => PeekMode == 2;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
SettingsUtils.SaveSettings(Settings.ToJsonString(), ShowDesktopSettings.ModuleName);
}
public void RefreshEnabledState()
{
InitializeEnabledValue();
OnPropertyChanged(nameof(IsEnabled));
}
private GpoRuleConfigured _enabledGpoRuleConfiguration;
private bool _enabledStateIsGPOConfigured;
private bool _isEnabled;
private int _peekMode;
private bool _requireDoubleClick;
private bool _enableTaskbarPeek;
private bool _enableGamingDetection;
private int _flyAwayAnimationDurationMs;
}
}