mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-04 01:20:02 +02:00
Compare commits
1 Commits
dependabot
...
niels9001/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d628c243d9 |
@@ -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" />
|
||||
|
||||
66
doc/devdocs/modules/show-desktop.md
Normal file
66
doc/devdocs/modules/show-desktop.md
Normal 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.
|
||||
@@ -18,6 +18,7 @@
|
||||
<?define RegistryPreviewProjectName="RegistryPreview"?>
|
||||
<?define PeekProjectName="Peek"?>
|
||||
<?define WorkspacesProjectName="Workspaces"?>
|
||||
<?define ShowDesktopProjectName="ShowDesktop"?>
|
||||
|
||||
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
|
||||
<?if $(var.Platform) = x64?>
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<ComponentGroupRef Id="ToolComponentGroup" />
|
||||
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
|
||||
<ComponentGroupRef Id="WorkspacesComponentGroup" />
|
||||
<ComponentGroupRef Id="ShowDesktopComponentGroup" />
|
||||
<ComponentGroupRef Id="CmdPalComponentGroup" />
|
||||
</Feature>
|
||||
|
||||
|
||||
30
installer/PowerToysSetupVNext/ShowDesktop.wxs
Normal file
30
installer/PowerToysSetupVNext/ShowDesktop.wxs
Normal 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>
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -76,5 +76,10 @@ namespace PowerToys.GPOWrapperProjection
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
|
||||
}
|
||||
|
||||
public static GpoRuleConfigured GetConfiguredShowDesktopEnabledValue()
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShowDesktopEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace ManagedCommon
|
||||
PowerOCR,
|
||||
Workspaces,
|
||||
GrabAndMove,
|
||||
ShowDesktop,
|
||||
ZoomIt,
|
||||
GeneralSettings,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
217
src/modules/ShowDesktop/ShowDesktop/DesktopDetector.cs
Normal file
217
src/modules/ShowDesktop/ShowDesktop/DesktopDetector.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
195
src/modules/ShowDesktop/ShowDesktop/DesktopPeek.cs
Normal file
195
src/modules/ShowDesktop/ShowDesktop/DesktopPeek.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/modules/ShowDesktop/ShowDesktop/FocusWatcher.cs
Normal file
71
src/modules/ShowDesktop/ShowDesktop/FocusWatcher.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/modules/ShowDesktop/ShowDesktop/MouseHook.cs
Normal file
104
src/modules/ShowDesktop/ShowDesktop/MouseHook.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/modules/ShowDesktop/ShowDesktop/MouseHookEventArgs.cs
Normal file
17
src/modules/ShowDesktop/ShowDesktop/MouseHookEventArgs.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
346
src/modules/ShowDesktop/ShowDesktop/NativeMethods.cs
Normal file
346
src/modules/ShowDesktop/ShowDesktop/NativeMethods.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
13
src/modules/ShowDesktop/ShowDesktop/PeekMode.cs
Normal file
13
src/modules/ShowDesktop/ShowDesktop/PeekMode.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
165
src/modules/ShowDesktop/ShowDesktop/Program.cs
Normal file
165
src/modules/ShowDesktop/ShowDesktop/Program.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/modules/ShowDesktop/ShowDesktop/ShowDesktop.csproj
Normal file
35
src/modules/ShowDesktop/ShowDesktop/ShowDesktop.csproj
Normal 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>
|
||||
20
src/modules/ShowDesktop/ShowDesktop/Win32MessageLoop.cs
Normal file
20
src/modules/ShowDesktop/ShowDesktop/Win32MessageLoop.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/modules/ShowDesktop/ShowDesktop/WindowInfo.cs
Normal file
21
src/modules/ShowDesktop/ShowDesktop/WindowInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
202
src/modules/ShowDesktop/ShowDesktop/WindowTracker.cs
Normal file
202
src/modules/ShowDesktop/ShowDesktop/WindowTracker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include "resource.h"
|
||||
@@ -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>
|
||||
187
src/modules/ShowDesktop/ShowDesktopModuleInterface/dllmain.cpp
Normal file
187
src/modules/ShowDesktop/ShowDesktopModuleInterface/dllmain.cpp
Normal 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();
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
9
src/modules/ShowDesktop/ShowDesktopModuleInterface/pch.h
Normal file
9
src/modules/ShowDesktop/ShowDesktopModuleInterface/pch.h
Normal 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>
|
||||
@@ -0,0 +1,2 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
@@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
#include <SDKDDKVer.h>
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
47
src/settings-ui/Settings.UI.Library/ShowDesktopProperties.cs
Normal file
47
src/settings-ui/Settings.UI.Library/ShowDesktopProperties.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/settings-ui/Settings.UI.Library/ShowDesktopSettings.cs
Normal file
38
src/settings-ui/Settings.UI.Library/ShowDesktopSettings.cs
Normal 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 |
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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=}">
|
||||
<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=}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RequireDoubleClick, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="ShowDesktop_EnableTaskbarPeek"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<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=}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableGamingDetection, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="ShowDesktop_FlyAwayAnimationDuration"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
200
src/settings-ui/Settings.UI/ViewModels/ShowDesktopViewModel.cs
Normal file
200
src/settings-ui/Settings.UI/ViewModels/ShowDesktopViewModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user