mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-14 18:57:55 +01:00
[PT Run] VirtualDesktopHelper & WindowWalker improvements (#16325)
* Import vdh from poc * last changes * push changes * small change * add error handling to vdh * last changes * make spellchecker happy * last changes * last changes * spell check * fix settings defaults * Improve WindowWalkerSettings class * add comment * New settings and improvements * new features * subtitle and tool tip * spell fixes * small fixes * fixes * Explorer info * spell fixes * fixes and CloseWindow feature * last changes * first part of implementing KillProcess * killProcess Part 2 & Fixes * text fix and installer * update access modifiers * some fixes * update dev docs * fix dev docs * dev doc change * dev docs: add missed infos * dev docs: add link * Update src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowWalkerSettings.cs * fix build * resolve feedback * fix settings * add tests
This commit is contained in:
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@@ -395,6 +395,7 @@ cursorpos
|
||||
customaction
|
||||
CUSTOMACTIONTEST
|
||||
cvd
|
||||
CVirtual
|
||||
cwchar
|
||||
cwd
|
||||
cxfksword
|
||||
@@ -1829,6 +1830,7 @@ RTLREADING
|
||||
ruleset
|
||||
RUNACTIVEXCTLS
|
||||
runas
|
||||
rundll
|
||||
rungameid
|
||||
RUNLEVEL
|
||||
runsettings
|
||||
@@ -2285,6 +2287,7 @@ vcredist
|
||||
VCRT
|
||||
vcruntime
|
||||
vcvars
|
||||
VDesktop
|
||||
vdi
|
||||
VDId
|
||||
vec
|
||||
|
||||
@@ -132,6 +132,7 @@ steps:
|
||||
**\Wox.Test.dll
|
||||
**\Microsoft.PowerToys.Run.Plugin.System.UnitTests.dll
|
||||
**\Microsoft.Plugin.WindowsTerminal.UnitTests.dll
|
||||
**\Microsoft.Plugin.WindowWalker.UnitTests.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
|
||||
|
||||
@@ -399,6 +399,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonacoPreviewHandler", "src
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.TimeZone", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.TimeZone\Microsoft.PowerToys.Run.Plugin.TimeZone.csproj", "{F44934A8-36F3-49B0-9465-3831BE041CDE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.WindowWalker.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.WindowWalker.UnitTests\Microsoft.Plugin.WindowWalker.UnitTests.csproj", "{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -1080,9 +1082,8 @@ Global
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE}.Debug|x86.Build.0 = Debug|x64
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x64.ActiveCfg = Release|x64
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x64.Build.0 = Release|x64
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.ActiveCfg = Release|x64
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.Build.0 = Release|x64
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x64.Build.0 = Debug|x64
|
||||
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x86.ActiveCfg = Debug|x64
|
||||
@@ -1091,6 +1092,14 @@ Global
|
||||
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Release|x64.Build.0 = Release|x64
|
||||
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Release|x86.ActiveCfg = Release|x64
|
||||
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Release|x86.Build.0 = Release|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x64.Build.0 = Debug|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x86.Build.0 = Debug|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|x64.ActiveCfg = Release|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|x64.Build.0 = Release|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|x86.ActiveCfg = Release|x64
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -1223,6 +1232,7 @@ Global
|
||||
{F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC} = {2F305555-C296-497E-AC20-5FA1B237996A}
|
||||
{04B193D7-3E21-46B8-A958-89B63A8A69DE} = {2F305555-C296-497E-AC20-5FA1B237996A}
|
||||
{F44934A8-36F3-49B0-9465-3831BE041CDE} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
|
||||
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
# Window Walker plugin
|
||||
The window walker plugin matches the user entered query with the open windows on the system.
|
||||
The user can switch to the found windows, close them or kill their process.
|
||||
|
||||

|
||||
|
||||
|
||||
## Remarks
|
||||
|
||||
### UWP Apps
|
||||
- The process of an UWP app can't be detected correctly for windows that are minimized while searching. At this time they are assigned to the generic process `ApplicationFrameHost.exe`. If the user searches for such an window while it is not minimized, then the process gets assigned correctly/updated.
|
||||
|
||||
### Killing processes
|
||||
- Killing the Explorer process is only allowed, if each folder window is running in its own process. (See section `File Explorer setting` below.)
|
||||
- You can only kill elevated processes, if you have admin permissions (UAC).
|
||||
- If you kill the process of an UWP app window, you kill all instances of the app. All windows are assigned to the same process.
|
||||
- Windows of UWP apps don't know their process, until they are searched in non-minimized state.
|
||||
|
||||
### File Explorer setting
|
||||
- To kill the Process of an Explorer window, each window has to run in a separate process. Otherwise the process is the same one as the shell process and killing the shell process will crash the shell (Windows ui).
|
||||
- To enable this behavior the setting `Launch folder windows in a separate process` under `Folder Options > View` has to be enabled.
|
||||
- From PowerToys Run you can open the `Folder options` dialog by clicking the information message in the search results. The information message is only shown when searching with action keyword for explorer windows and can be hidden in the plugin settings.
|
||||
- Note: The folder option/process is evaluated in real time. After changing the setting it is enough to search again for the windows.
|
||||
|
||||

|
||||
|
||||
|
||||
## Optional plugin settings
|
||||
- The optional plugin settings are implemented via the [`ISettingProvider`](/src/modules/launcher/Wox.Plugin/ISettingProvider.cs) interface from `Wox.Plugin` project.
|
||||
- All available settings for the plugin are defined in the [`WindowWalkerSettings`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowWalkerSettings.cs) class of the plugin. The settings can be accessed everywhere in the plugin code via the static class instance `WindowWalkerSettings.Instance`.
|
||||
- We have the following settings that the user can configure to change the behavior of the plugin:
|
||||
|
||||
| Key | Default value | Name/Description |
|
||||
|--------------|-----------|------------|
|
||||
| `ResultsFromVisibleDesktopOnly` | `false` | Show only results from visible desktop |
|
||||
| `SubtitleShowPid` | `false` | Show process id in subtitle |
|
||||
| `SubtitleShowDesktopName` | `true` | Show desktop name in subtitle (If two or more desktops exist) |
|
||||
| `ConfirmKillProcess` | `true` | Request confirmation when killing a process |
|
||||
| `KillProcessTree` | `false` | Kill process and it's child processes |
|
||||
| `OpenAfterKillAndClose` | `false` | Stay open after closing windows and killing processes (Not working with kill process confirmation) |
|
||||
| `HideKillProcessOnElevatedProcesses` | `false` | Hide "kill process" button if additional permissions required |
|
||||
| `HideExplorerSettingInfo` | `false` | Hide Explorer process information |
|
||||
|
||||
|
||||
## Technical details
|
||||
|
||||
### [`OpenWindows.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/OpenWindows.cs)
|
||||
- The window walker plugin uses the `EnumWindows` function to enumerate all the open windows in the [`OpenWindows.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/OpenWindows.cs) class.
|
||||
|
||||
@@ -11,11 +52,33 @@ The window walker plugin matches the user entered query with the open windows on
|
||||
- It is responsible for updating the search text and performing a fuzzy search on all the open windows.
|
||||
|
||||
### [`Window.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs)
|
||||
- The [`Window`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs) class represents a specific window and has functions to get the name of the window, the state of the window (whether it is visible or not), and the `SwitchTowindow` function which switches the desktop focus to the selected window. This action is performed when the user clicks on a window walker plugin result.
|
||||
- The [`Window`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs) class represents a specific window and has functions to get the name of the window, the state of the window (whether it is visible or not), the `SwitchTowindow` function which switches the desktop focus to the selected window and the `CloseThisWindow` function which closes the window. The `SwitchTowindow` action is performed when the user clicks on a window walker plugin result.
|
||||
- The `Window` class holds a static cache with the process information of all windows we know so far and each window instance has a property which holds its process information (name, file, ...). The process data in the cache and the window property are of the type `WindowProcess`.
|
||||
- To get the desktop information for a window, we use the common [`VirtualDesktopHelper`](/src/modules/launcher/Wox.Plugin/Common/VirtualDesktop/VirtualDesktopHelper.cs) in `Wox.Plugin` project. The instance of `VirtualDesktopHelper` is cached in the [`Main`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Main.cs) class of the plugin at runtime. The desktop information is stored in a property of the type [`VDesktop`](/src/modules/launcher/Wox.Plugin/Common/VirtualDesktop/VDesktop.cs).
|
||||
|
||||
### [`WindowProcess.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs)
|
||||
- The [`WindowProcess`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs) class represents a specific process for a window. It contains static methods to query process information from the system. And it contains instance methods and properties to hold/retrieve the process information we want to know about a window's process.
|
||||
- The [`WindowProcess`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs) class represents a specific process for a window.
|
||||
- It contains static methods to query process information from the system and instance methods/properties to hold/retrieve the process information we want to know about a window's process.
|
||||
- Additionally, it contains the method `KillThisProcess` to kill the process. (If the user has not enough permissions to kill a process they are requested via UAC.)
|
||||
|
||||
### [`ResultHelper.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ResultHelper.cs)
|
||||
- The [`ResultHelper`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ResultHelper.cs) class contains the code to create the list with all results for PT Run based on the data returned from `SearchController` class.
|
||||
- There is a special result that is added if the folder windows doesn't run in separate processes and the user searches for Explorer windows using the action keyword.
|
||||
- This result informs the user that there is a setting that must be enabled to be able to kill Explorer processes.
|
||||
- The result can be disabled in plugin options. When it is clicked it opens the folder options.
|
||||
|
||||
### [`ContextMenuHelper.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ContextMenuHelper.cs)
|
||||
- The [`ContextMenuHelper`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ContextMenuHelper.cs) class provides the code for the context menu items.
|
||||
|
||||
### [`WindowWalkerSettings.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowWalkerSettings.cs)
|
||||
- The [`WindowWalkerSettings`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowWalkerSettings.cs) class provides access to all optional plugin settings.
|
||||
- The class has a static property called `Instance` that holds an instance of the class itself. This allows us to access the settings from everywhere in the plugin code without having additional parameters in our methods.
|
||||
|
||||
### Score
|
||||
The window walker plugin uses [`FuzzyMatching`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/FuzzyMatching.cs) to get the matching indices and calculates the score by creating a 2 dimensional array of the window and the query text.
|
||||
|
||||
## [Unit Tests](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker.UnitTests)
|
||||
We have a [Unit Test project](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker.UnitTests) that executes various test to ensure that the plugin works as expected.
|
||||
|
||||
### [`PluginSettingsTests.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker.UnitTests/PluginSettingsTests.cs)
|
||||
- The [`PluginSettingsTests.cs`](/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker.UnitTests/PluginSettingsTests.cs) class contains tests to validate that all settings exist and that they have the correct default values.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 153 KiB |
BIN
doc/images/launcher/plugins/windowwalker_folder_options.png
Normal file
BIN
doc/images/launcher/plugins/windowwalker_folder_options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -79,6 +79,8 @@
|
||||
<?define VSCWrkCompFiles=plugin.json;Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll?>
|
||||
|
||||
<?define WindowWlkrCompFiles=plugin.json;Microsoft.Plugin.WindowWalker.deps.json;Microsoft.Plugin.WindowWalker.dll;PowerToys.ManagedTelemetry.dll?>
|
||||
|
||||
<?define WindowWlkrImagesCompFiles=windowwalker.dark.png;windowwalker.light.png;info.dark.png;info.light.png?>
|
||||
|
||||
<?define RegistryComponentFiles=plugin.json;Microsoft.PowerToys.Run.Plugin.Registry.deps.json;Microsoft.PowerToys.Run.Plugin.Registry.dll;PowerToys.ManagedTelemetry.dll?>
|
||||
|
||||
@@ -86,10 +88,10 @@
|
||||
|
||||
<?define SystemComponentFiles=plugin.json;Microsoft.PowerToys.Run.Plugin.System.deps.json;Microsoft.PowerToys.Run.Plugin.System.dll?>
|
||||
|
||||
<?define TimeZoneComponentFiles=plugin.json;Microsoft.PowerToys.Run.Plugin.TimeZone.deps.json;Microsoft.PowerToys.Run.Plugin.TimeZone.dll;PowerToys.ManagedTelemetry.dll?>
|
||||
|
||||
<?define SystemImagesComponentFiles=lock.dark.png;lock.light.png;logoff.dark.png;logoff.light.png;recyclebin.dark.png;recyclebin.light.png;restart.dark.png;restart.light.png;shutdown.dark.png;shutdown.light.png;sleep.dark.png;sleep.light.png;firmwareSettings.dark.png;firmwareSettings.light.png?>
|
||||
|
||||
<?define TimeZoneComponentFiles=plugin.json;Microsoft.PowerToys.Run.Plugin.TimeZone.deps.json;Microsoft.PowerToys.Run.Plugin.TimeZone.dll;PowerToys.ManagedTelemetry.dll?>
|
||||
|
||||
<?define WinSetCmpFiles=plugin.json;Microsoft.PowerToys.Run.Plugin.WindowsSettings.deps.json;Microsoft.PowerToys.Run.Plugin.WindowsSettings.dll;PowerToys.ManagedTelemetry.dll?>
|
||||
|
||||
<?define WinTermCmpFiles=plugin.json;Microsoft.PowerToys.Run.Plugin.WindowsTerminal.deps.json;Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll;PowerToys.ManagedTelemetry.dll?>
|
||||
@@ -1516,12 +1518,11 @@
|
||||
<File Id="WindowWlkrCompFile_$(var.File)" Source="$(var.BinX64Dir)modules\launcher\Plugins\WindowWalker\$(var.File)" />
|
||||
</Component>
|
||||
<?endforeach?>
|
||||
<Component Id="WindowWalkerImagesComponentLight" Directory="WindowWalkerImagesFolder" >
|
||||
<File Id="WindowWalkerLightIcon" Source="$(var.BinX64Dir)modules\launcher\Plugins\WindowWalker\Images\windowwalker.light.png" />
|
||||
</Component>
|
||||
<Component Id="WindowWalkerImagesComponentDark" Directory="WindowWalkerImagesFolder" >
|
||||
<File Id="WindowWalkerDarkIcon" Source="$(var.BinX64Dir)modules\launcher\Plugins\WindowWalker\Images\windowwalker.dark.png" />
|
||||
</Component>
|
||||
<?foreach File in $(var.WindowWlkrImagesCompFiles)?>
|
||||
<Component Id="WindowWalkerImagesComp_$(var.File)" Win64="yes" Directory="WindowWalkerImagesFolder">
|
||||
<File Id="WindowWalkerImagesComp_$(var.File)" Source="$(var.BinX64Dir)modules\launcher\Plugins\WindowWalker\Images\$(var.File)" />
|
||||
</Component>
|
||||
<?endforeach?>
|
||||
|
||||
<!-- Registry Plugin -->
|
||||
<?foreach File in $(var.RegistryComponentFiles)?>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Platforms>x64</Platforms>
|
||||
<RootNamespace>Microsoft.Plugin.WindowWalker.UnitTests</RootNamespace>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisMode>Recommended</AnalysisMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Plugin.WindowWalker\Microsoft.Plugin.WindowWalker.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\..\..\codeAnalysis\GlobalSuppressions.cs">
|
||||
<Link>GlobalSuppressions.cs</Link>
|
||||
</Compile>
|
||||
<AdditionalFiles Include="..\..\..\..\codeAnalysis\StyleCop.json">
|
||||
<Link>StyleCop.json</Link>
|
||||
</AdditionalFiles>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<Version>1.1.118</Version>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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 System.Reflection;
|
||||
using Microsoft.Plugin.WindowWalker.Components;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class PluginSettingsTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void SettingsCount()
|
||||
{
|
||||
// Setup
|
||||
PropertyInfo[] settings = WindowWalkerSettings.Instance?.GetType()?.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
// Act
|
||||
var result = settings?.Length;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("ResultsFromVisibleDesktopOnly")]
|
||||
[DataRow("SubtitleShowPid")]
|
||||
[DataRow("SubtitleShowDesktopName")]
|
||||
[DataRow("ConfirmKillProcess")]
|
||||
[DataRow("KillProcessTree")]
|
||||
[DataRow("OpenAfterKillAndClose")]
|
||||
[DataRow("HideKillProcessOnElevatedProcesses")]
|
||||
[DataRow("HideExplorerSettingInfo")]
|
||||
public void DoesSettingExist(string name)
|
||||
{
|
||||
// Setup
|
||||
Type settings = WindowWalkerSettings.Instance?.GetType();
|
||||
|
||||
// Act
|
||||
var result = settings?.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("ResultsFromVisibleDesktopOnly", false)]
|
||||
[DataRow("SubtitleShowPid", false)]
|
||||
[DataRow("SubtitleShowDesktopName", true)]
|
||||
[DataRow("ConfirmKillProcess", true)]
|
||||
[DataRow("KillProcessTree", false)]
|
||||
[DataRow("OpenAfterKillAndClose", false)]
|
||||
[DataRow("HideKillProcessOnElevatedProcesses", false)]
|
||||
[DataRow("HideExplorerSettingInfo", false)]
|
||||
public void DefaultValues(string name, bool valueExpected)
|
||||
{
|
||||
// Setup
|
||||
WindowWalkerSettings setting = WindowWalkerSettings.Instance;
|
||||
|
||||
// Act
|
||||
PropertyInfo propertyInfo = setting?.GetType()?.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var result = propertyInfo?.GetValue(setting);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(valueExpected, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Plugin.WindowWalker.Properties;
|
||||
using Wox.Plugin;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
internal class ContextMenuHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a list of all <see cref="ContextMenuResult"/>s for the selected <see cref="Result"/>.
|
||||
/// </summary>
|
||||
/// <param name="result">Selected result</param>
|
||||
/// <returns>List of context menu results</returns>
|
||||
internal static List<ContextMenuResult> GetContextMenuResults(in Result result)
|
||||
{
|
||||
if (!(result?.ContextData is Window windowData))
|
||||
{
|
||||
return new List<ContextMenuResult>(0);
|
||||
}
|
||||
|
||||
var contextMenu = new List<ContextMenuResult>()
|
||||
{
|
||||
new ContextMenuResult
|
||||
{
|
||||
AcceleratorKey = Key.F4,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
Glyph = "\xE8BB", // E8B8 => Symbol: ChromeClose
|
||||
Title = $"{Resources.wox_plugin_windowwalker_Close} (Ctrl+F4)",
|
||||
Action = _ =>
|
||||
{
|
||||
if (!windowData.IsWindow)
|
||||
{
|
||||
Log.Debug($"Can not close the window '{windowData.Title}' ({windowData.Hwnd}), because it doesn't exist.", typeof(ContextMenuHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
// As a workaround to close PT Run after executing the context menu command, we switch to the window before closing it (Issue #16601).
|
||||
// We use the setting OpenAfterKillAndClose to detect if we have to switch.
|
||||
windowData.CloseThisWindow(!WindowWalkerSettings.Instance.OpenAfterKillAndClose);
|
||||
|
||||
return !WindowWalkerSettings.Instance.OpenAfterKillAndClose;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Hide menu if Explorer.exe is the shell process or the process name is ApplicationFrameHost.exe
|
||||
// In the first case we would crash the windows ui and in the second case we would kill the generic process for uwp apps.
|
||||
if (!windowData.Process.IsShellProcess && !(windowData.Process.IsUwpApp & windowData.Process.Name.ToLower() == "applicationframehost.exe")
|
||||
&& !(windowData.Process.IsFullAccessDenied & WindowWalkerSettings.Instance.HideKillProcessOnElevatedProcesses))
|
||||
{
|
||||
contextMenu.Add(new ContextMenuResult
|
||||
{
|
||||
AcceleratorKey = Key.Delete,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
Glyph = "\xE74D", // E74D => Symbol: Delete
|
||||
Title = $"{Resources.wox_plugin_windowwalker_Kill} (Ctrl+Delete)",
|
||||
Action = _ => KillProcessCommand(windowData),
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to initiate killing the process of a window
|
||||
/// </summary>
|
||||
/// <param name="window">Window data</param>
|
||||
/// <returns>True if the PT Run window should close, otherwise false.</returns>
|
||||
private static bool KillProcessCommand(Window window)
|
||||
{
|
||||
// Validate process
|
||||
if (!window.IsWindow || !window.Process.DoesExist || !window.Process.Name.Equals(WindowProcess.GetProcessNameFromProcessID(window.Process.ProcessID)) )
|
||||
{
|
||||
Log.Debug($"Can not kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}), because it doesn't exist.", typeof(ContextMenuHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request user confirmation
|
||||
if (WindowWalkerSettings.Instance.ConfirmKillProcess)
|
||||
{
|
||||
string messageBody = $"{Resources.wox_plugin_windowwalker_KillMessage}\n"
|
||||
+ $"{window.Process.Name} ({window.Process.ProcessID})\n\n"
|
||||
+ $"{(window.Process.IsUwpApp ? Resources.wox_plugin_windowwalker_KillMessageUwp : Resources.wox_plugin_windowwalker_KillMessageQuestion)}";
|
||||
MessageBoxResult messageBoxResult = MessageBox.Show(
|
||||
messageBody,
|
||||
Resources.wox_plugin_windowwalker_plugin_name + " - " + Resources.wox_plugin_windowwalker_KillMessageTitle,
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning);
|
||||
|
||||
if (messageBoxResult == MessageBoxResult.No)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// As a workaround to close PT Run before executing the command, we switch to the window before killing it's process
|
||||
if (!WindowWalkerSettings.Instance.OpenAfterKillAndClose)
|
||||
{
|
||||
window.SwitchToWindow();
|
||||
}
|
||||
|
||||
// Kill process
|
||||
window.Process.KillThisProcess(WindowWalkerSettings.Instance.KillProcessTree);
|
||||
return !WindowWalkerSettings.Instance.OpenAfterKillAndClose;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Class housing fuzzy matching methods
|
||||
/// </summary>
|
||||
public static class FuzzyMatching
|
||||
internal static class FuzzyMatching
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the best match (the one with the most
|
||||
@@ -25,7 +25,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <param name="searchText">the text to search for</param>
|
||||
/// <returns>returns the index location of each of the letters of the matches</returns>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1814:Prefer jagged arrays over multidimensional", Justification = "matches does not waste space with the current implementation, however this could probably be optimized to store the indices of matches instead of boolean values. Currently there are no unit tests for this, but we could refactor if memory/perf becomes an issue. ")]
|
||||
public static List<int> FindBestFuzzyMatch(string text, string searchText)
|
||||
internal static List<int> FindBestFuzzyMatch(string text, string searchText)
|
||||
{
|
||||
if (searchText == null)
|
||||
{
|
||||
@@ -86,7 +86,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// one the search string and each cell marked as an intersection between the two</param>
|
||||
/// <returns>a list of the possible combinations that match the search text</returns>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1814:Prefer jagged arrays over multidimensional", Justification = "matches does not waste space with the current implementation, however this could probably be optimized to store the indices of matches instead of boolean values. Currently there are no unit tests for this, but we could refactor if memory/perf becomes an issue. ")]
|
||||
public static List<List<int>> GetAllMatchIndexes(bool[,] matches)
|
||||
internal static List<List<int>> GetAllMatchIndexes(bool[,] matches)
|
||||
{
|
||||
if (matches == null)
|
||||
{
|
||||
@@ -127,7 +127,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// </summary>
|
||||
/// <param name="matches">the index of the matches</param>
|
||||
/// <returns>an integer representing the score</returns>
|
||||
public static int CalculateScoreForMatches(List<int> matches)
|
||||
internal static int CalculateScoreForMatches(List<int> matches)
|
||||
{
|
||||
if (matches == null)
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the list of all open windows
|
||||
/// </summary>
|
||||
public List<Window> Windows
|
||||
internal List<Window> Windows
|
||||
{
|
||||
get { return new List<Window>(windows); }
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// the first instance gets created and that all the requests
|
||||
/// end up at that one instance
|
||||
/// </summary>
|
||||
public static OpenWindows Instance
|
||||
internal static OpenWindows Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -69,7 +69,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Updates the list of open windows
|
||||
/// </summary>
|
||||
public void UpdateOpenWindowsList()
|
||||
internal void UpdateOpenWindowsList()
|
||||
{
|
||||
windows.Clear();
|
||||
EnumWindowsProc callbackptr = new EnumWindowsProc(WindowEnumerationCallBack);
|
||||
@@ -83,22 +83,21 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <param name="lParam">Value being passed from the caller (we don't use this but might come in handy
|
||||
/// in the future</param>
|
||||
/// <returns>true to make sure to continue enumeration</returns>
|
||||
public bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
|
||||
internal bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
|
||||
{
|
||||
Window newWindow = new Window(hwnd);
|
||||
|
||||
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
|
||||
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
|
||||
newWindow.ClassName != "Windows.UI.Core.CoreWindow" && newWindow.ProcessInfo.Name != _powerLauncherExe)
|
||||
(newWindow.Desktop.IsVisible || !WindowWalkerSettings.Instance.ResultsFromVisibleDesktopOnly) &&
|
||||
newWindow.ClassName != "Windows.UI.Core.CoreWindow" && newWindow.Process.Name != _powerLauncherExe)
|
||||
{
|
||||
// To hide (not add) preloaded uwp app windows that are invisible to the user we check the cloak state in DWM to be "none". (Issue #13637.)
|
||||
// (If user asking to see these windows again we can add an optional plugin setting in the future.)
|
||||
// [@htcfreek, 2022-02-01: Removed the IsCloaked check to list windows from virtual desktops other than the current one again (#15887). In a second PR I will fix it re-implement it with improved code again.]
|
||||
// if (!newWindow.IsCloaked)
|
||||
// {
|
||||
windows.Add(newWindow);
|
||||
|
||||
// }
|
||||
// To hide (not add) preloaded uwp app windows that are invisible to the user and other cloaked windows, we check the cloak state. (Issue #13637.)
|
||||
// (If user asking to see cloaked uwp app windows again we can add an optional plugin setting in the future.)
|
||||
if (!newWindow.IsCloaked || newWindow.GetWindowCloakState() == Window.WindowCloakState.OtherDesktop)
|
||||
{
|
||||
windows.Add(newWindow);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
// 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.Collections.Generic;
|
||||
using Microsoft.Plugin.WindowWalker.Properties;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to work with results
|
||||
/// </summary>
|
||||
internal static class ResultHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a list of all results for the query.
|
||||
/// </summary>
|
||||
/// <param name="searchControllerResults">List with all search controller matches</param>
|
||||
/// <param name="icon">The path to the result icon</param>
|
||||
/// <returns>List of results</returns>
|
||||
internal static List<Result> GetResultList(List<SearchResult> searchControllerResults, bool isKeywordSearch, string icon, string infoIcon)
|
||||
{
|
||||
bool addExplorerInfo = false;
|
||||
List<Result> resultsList = new List<Result>();
|
||||
|
||||
foreach (SearchResult x in searchControllerResults)
|
||||
{
|
||||
if (x.Result.Process.Name.ToLower() == "explorer.exe" && x.Result.Process.IsShellProcess)
|
||||
{
|
||||
addExplorerInfo = true;
|
||||
}
|
||||
|
||||
resultsList.Add(new Result()
|
||||
{
|
||||
Title = x.Result.Title,
|
||||
IcoPath = icon,
|
||||
SubTitle = GetSubtitle(x.Result),
|
||||
ContextData = x.Result,
|
||||
Action = c =>
|
||||
{
|
||||
x.Result.SwitchToWindow();
|
||||
return true;
|
||||
},
|
||||
|
||||
// For debugging you can set the second parameter to true to see more informations.
|
||||
ToolTipData = GetToolTip(x.Result, false),
|
||||
});
|
||||
}
|
||||
|
||||
if (addExplorerInfo && isKeywordSearch && !WindowWalkerSettings.Instance.HideExplorerSettingInfo)
|
||||
{
|
||||
resultsList.Add(GetExplorerInfoResult(infoIcon));
|
||||
}
|
||||
|
||||
return resultsList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the subtitle for a result
|
||||
/// </summary>
|
||||
/// <param name="window">The window properties of the result</param>
|
||||
/// <returns>String with the subtitle</returns>
|
||||
private static string GetSubtitle(Window window)
|
||||
{
|
||||
if (window == null || !(window is Window))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string subtitleText = Resources.wox_plugin_windowwalker_Running + ": " + window.Process.Name;
|
||||
|
||||
if (WindowWalkerSettings.Instance.SubtitleShowPid)
|
||||
{
|
||||
subtitleText += $" ({window.Process.ProcessID})";
|
||||
}
|
||||
|
||||
if (WindowWalkerSettings.Instance.SubtitleShowDesktopName && Main.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
|
||||
{
|
||||
subtitleText += $" - {Resources.wox_plugin_windowwalker_Desktop}: {window.Desktop.Name}";
|
||||
}
|
||||
|
||||
return subtitleText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tool tip for a result
|
||||
/// </summary>
|
||||
/// <param name="window">The window properties of the result</param>
|
||||
/// <param name="debugToolTip">Value indicating if a detailed debug tooltip should be returned</param>
|
||||
/// <returns>Tooltip for the result or null of failure</returns>
|
||||
private static ToolTipData GetToolTip(Window window, bool debugToolTip)
|
||||
{
|
||||
if (window == null || !(window is Window))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!debugToolTip)
|
||||
{
|
||||
string text = $"{Resources.wox_plugin_windowwalker_Process}: {window.Process.Name}";
|
||||
text += $"\n{Resources.wox_plugin_windowwalker_ProcessId}: {window.Process.ProcessID}";
|
||||
|
||||
if (Main.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
|
||||
{
|
||||
text += $"\n{Resources.wox_plugin_windowwalker_Desktop}: {window.Desktop.Name}";
|
||||
|
||||
if (!window.Desktop.IsAllDesktopsView)
|
||||
{
|
||||
text += $" ({Resources.wox_plugin_windowwalker_Number} {window.Desktop.Number})";
|
||||
}
|
||||
}
|
||||
|
||||
return new ToolTipData(window.Title, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
string text = $"hWnd: {window.Hwnd}\n" +
|
||||
$"Window class: {window.ClassName}\n" +
|
||||
$"Process ID: {window.Process.ProcessID}\n" +
|
||||
$"Thread ID: {window.Process.ThreadID}\n" +
|
||||
$"Process: {window.Process.Name}\n" +
|
||||
$"Process exists: {window.Process.DoesExist}\n" +
|
||||
$"Is full access denied: {window.Process.IsFullAccessDenied}\n" +
|
||||
$"Is uwp app: {window.Process.IsUwpApp}\n" +
|
||||
$"Is ShellProcess: {window.Process.IsShellProcess}\n" +
|
||||
$"Is window cloaked: {window.IsCloaked}\n" +
|
||||
$"Window cloak state: {window.GetWindowCloakState()}\n" +
|
||||
$"Desktop name: {window.Desktop.Name}" +
|
||||
$"Desktop number: {window.Desktop.Number}\n" +
|
||||
$"Desktop is visible: {window.Desktop.IsVisible}\n" +
|
||||
$"Desktop position: {window.Desktop.Position}\n" +
|
||||
$"Is AllDesktops view: {window.Desktop.IsAllDesktopsView}";
|
||||
|
||||
return new ToolTipData(window.Title, text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an information result about the explorer setting
|
||||
/// </summary>
|
||||
/// <param name="iIcon">The path to the info icon.</param>
|
||||
/// <returns>An object of the type <see cref="Result"/> with the information.</returns>
|
||||
private static Result GetExplorerInfoResult(string iIcon)
|
||||
{
|
||||
return new Result()
|
||||
{
|
||||
Title = Resources.wox_plugin_windowwalker_ExplorerInfoTitle,
|
||||
IcoPath = iIcon,
|
||||
SubTitle = Resources.wox_plugin_windowwalker_ExplorerInfoSubTitle,
|
||||
Action = c =>
|
||||
{
|
||||
Helper.OpenInShell("rundll32.exe", "shell32.dll,Options_RunDLL 7"); // "shell32.dll,Options_RunDLL 7" opens the view tab in folder options of explorer.
|
||||
return true;
|
||||
},
|
||||
Score = 100_000,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets or sets the current search text
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
internal string SearchText
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -51,7 +51,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the open window search results
|
||||
/// </summary>
|
||||
public List<SearchResult> SearchMatches
|
||||
internal List<SearchResult> SearchMatches
|
||||
{
|
||||
get { return new List<SearchResult>(searchMatches).OrderByDescending(x => x.Score).ToList(); }
|
||||
}
|
||||
@@ -59,7 +59,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets singleton Pattern
|
||||
/// </summary>
|
||||
public static SearchController Instance
|
||||
internal static SearchController Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -84,7 +84,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Event handler for when the search text has been updated
|
||||
/// </summary>
|
||||
public void UpdateSearchText(string searchText)
|
||||
internal void UpdateSearchText(string searchText)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SyncOpenWindowsWithModel();
|
||||
@@ -93,7 +93,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Syncs the open windows with the OpenWindows Model
|
||||
/// </summary>
|
||||
public void SyncOpenWindowsWithModel()
|
||||
internal void SyncOpenWindowsWithModel()
|
||||
{
|
||||
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
foreach (var window in openWindows)
|
||||
{
|
||||
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchString.SearchText);
|
||||
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.ProcessInfo.Name, searchString.SearchText);
|
||||
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.Process.Name, searchString.SearchText);
|
||||
|
||||
if ((titleMatch.Count != 0 || processMatch.Count != 0) &&
|
||||
window.Title.Length != 0)
|
||||
@@ -145,7 +145,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Event args for a window list update event
|
||||
/// </summary>
|
||||
public class SearchResultUpdateEventArgs : EventArgs
|
||||
internal class SearchResultUpdateEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Contains search result windows with each window including the reason why the result was included
|
||||
/// </summary>
|
||||
public class SearchResult
|
||||
internal class SearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actual window reference for the search result
|
||||
/// </summary>
|
||||
public Window Result
|
||||
internal Window Result
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -24,7 +24,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the title window
|
||||
/// </summary>
|
||||
public List<int> SearchMatchesInTitle
|
||||
internal List<int> SearchMatchesInTitle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -34,7 +34,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// Gets the list of indexes of the matching characters for the search in the
|
||||
/// name of the process
|
||||
/// </summary>
|
||||
public List<int> SearchMatchesInProcessName
|
||||
internal List<int> SearchMatchesInProcessName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -43,7 +43,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the type of match (shortcut, fuzzy or nothing)
|
||||
/// </summary>
|
||||
public SearchType SearchResultMatchType
|
||||
internal SearchType SearchResultMatchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -52,7 +52,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a score indicating how well this matches what we are looking for
|
||||
/// </summary>
|
||||
public int Score
|
||||
internal int Score
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -61,7 +61,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the source of where the best score was found
|
||||
/// </summary>
|
||||
public TextType BestScoreSource
|
||||
internal TextType BestScoreSource
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -71,7 +71,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
|
||||
internal SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = matchesInTitle;
|
||||
@@ -104,7 +104,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// The type of text that a string represents
|
||||
/// </summary>
|
||||
public enum TextType
|
||||
internal enum TextType
|
||||
{
|
||||
ProcessName,
|
||||
WindowTitle,
|
||||
@@ -113,7 +113,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// The type of search
|
||||
/// </summary>
|
||||
public enum SearchType
|
||||
internal enum SearchType
|
||||
{
|
||||
/// <summary>
|
||||
/// the search string is empty, which means all open windows are
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// Gets where is the search string coming from (is it a shortcut
|
||||
/// or direct string, etc...)
|
||||
/// </summary>
|
||||
public SearchResult.SearchType SearchType
|
||||
internal SearchResult.SearchType SearchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -25,7 +25,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the actual text we are searching for
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
internal string SearchText
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -37,7 +37,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// </summary>
|
||||
/// <param name="searchText">text from search</param>
|
||||
/// <param name="searchType">type of search</param>
|
||||
public SearchString(string searchText, SearchResult.SearchType searchType)
|
||||
internal SearchString(string searchText, SearchResult.SearchType searchType)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SearchType = searchType;
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wox.Plugin.Common.VirtualDesktop.Helper;
|
||||
using Wox.Plugin.Common.Win32;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
@@ -17,7 +18,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Represents a specific open window
|
||||
/// </summary>
|
||||
public class Window
|
||||
internal class Window
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle to the window
|
||||
@@ -35,10 +36,15 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// </summary>
|
||||
private readonly WindowProcess processInfo;
|
||||
|
||||
/// <summary>
|
||||
/// An instance of <see cref="VDesktop"/> that contains the desktop information for the window
|
||||
/// </summary>
|
||||
private readonly VDesktop desktopInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the window (the string displayed at the top of the window)
|
||||
/// </summary>
|
||||
public string Title
|
||||
internal string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -64,7 +70,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the handle to the window
|
||||
/// </summary>
|
||||
public IntPtr Hwnd
|
||||
internal IntPtr Hwnd
|
||||
{
|
||||
get { return hwnd; }
|
||||
}
|
||||
@@ -72,15 +78,23 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the object of with the process information of the window
|
||||
/// </summary>
|
||||
public WindowProcess ProcessInfo
|
||||
internal WindowProcess Process
|
||||
{
|
||||
get { return processInfo; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of with the desktop information of the window
|
||||
/// </summary>
|
||||
internal VDesktop Desktop
|
||||
{
|
||||
get { return desktopInfo; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the class for the window represented
|
||||
/// </summary>
|
||||
public string ClassName
|
||||
internal string ClassName
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -91,7 +105,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is visible (might return false if it is a hidden IE tab)
|
||||
/// </summary>
|
||||
public bool Visible
|
||||
internal bool Visible
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -103,7 +117,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// Gets a value indicating whether the window is cloaked (true) or not (false).
|
||||
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
|
||||
/// </summary>
|
||||
public bool IsCloaked
|
||||
internal bool IsCloaked
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -114,7 +128,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified window handle identifies an existing window.
|
||||
/// </summary>
|
||||
public bool IsWindow
|
||||
internal bool IsWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -125,7 +139,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is a toolwindow
|
||||
/// </summary>
|
||||
public bool IsToolWindow
|
||||
internal bool IsToolWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -138,7 +152,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is an appwindow
|
||||
/// </summary>
|
||||
public bool IsAppWindow
|
||||
internal bool IsAppWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -151,7 +165,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window has ITaskList_Deleted property
|
||||
/// </summary>
|
||||
public bool TaskListDeleted
|
||||
internal bool TaskListDeleted
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -162,7 +176,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified windows is the owner (i.e. doesn't have an owner)
|
||||
/// </summary>
|
||||
public bool IsOwner
|
||||
internal bool IsOwner
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -173,7 +187,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is minimized
|
||||
/// </summary>
|
||||
public bool Minimized
|
||||
internal bool Minimized
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -186,17 +200,18 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// Initializes a new Window representation
|
||||
/// </summary>
|
||||
/// <param name="hwnd">the handle to the window we are representing</param>
|
||||
public Window(IntPtr hwnd)
|
||||
internal Window(IntPtr hwnd)
|
||||
{
|
||||
// TODO: Add verification as to whether the window handle is valid
|
||||
this.hwnd = hwnd;
|
||||
processInfo = CreateWindowProcessInstance(hwnd);
|
||||
desktopInfo = Main.VirtualDesktopHelperInstance.GetWindowDesktop(hwnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches desktop focus to the window
|
||||
/// </summary>
|
||||
public void SwitchToWindow()
|
||||
internal void SwitchToWindow()
|
||||
{
|
||||
// The following block is necessary because
|
||||
// 1) There is a weird flashing behavior when trying
|
||||
@@ -219,6 +234,19 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
NativeMethods.FlashWindow(Hwnd, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the window
|
||||
/// </summary>
|
||||
internal void CloseThisWindow(bool switchBeforeClose)
|
||||
{
|
||||
if (switchBeforeClose)
|
||||
{
|
||||
SwitchToWindow();
|
||||
}
|
||||
|
||||
_ = NativeMethods.SendMessage(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_CLOSE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the window name to string along with the process name
|
||||
/// </summary>
|
||||
@@ -233,7 +261,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// Returns what the window size is
|
||||
/// </summary>
|
||||
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
|
||||
public WindowSizeState GetWindowSizeState()
|
||||
internal WindowSizeState GetWindowSizeState()
|
||||
{
|
||||
NativeMethods.GetWindowPlacement(Hwnd, out WINDOWPLACEMENT placement);
|
||||
|
||||
@@ -255,7 +283,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Enum to simplify the state of the window
|
||||
/// </summary>
|
||||
public enum WindowSizeState
|
||||
internal enum WindowSizeState
|
||||
{
|
||||
Normal,
|
||||
Minimized,
|
||||
@@ -268,7 +296,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
|
||||
/// </summary>
|
||||
/// <returns>The state (none, app, ...) of the window</returns>
|
||||
public WindowCloakState GetWindowCloakState()
|
||||
internal WindowCloakState GetWindowCloakState()
|
||||
{
|
||||
_ = NativeMethods.DwmGetWindowAttribute(Hwnd, (int)DwmWindowAttributes.Cloaked, out int isCloakedState, sizeof(uint));
|
||||
|
||||
@@ -279,7 +307,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
case (int)DwmWindowCloakStates.CloakedApp:
|
||||
return WindowCloakState.App;
|
||||
case (int)DwmWindowCloakStates.CloakedShell:
|
||||
return WindowCloakState.Shell;
|
||||
return Main.VirtualDesktopHelperInstance.IsWindowCloakedByVirtualDesktopManager(hwnd) ? WindowCloakState.OtherDesktop : WindowCloakState.Shell;
|
||||
case (int)DwmWindowCloakStates.CloakedInherited:
|
||||
return WindowCloakState.Inherited;
|
||||
default:
|
||||
@@ -290,12 +318,13 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Enum to simplify the cloak state of the window
|
||||
/// </summary>
|
||||
public enum WindowCloakState
|
||||
internal enum WindowCloakState
|
||||
{
|
||||
None,
|
||||
App,
|
||||
Shell,
|
||||
Inherited,
|
||||
OtherDesktop,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Plugin.Common.Win32;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
@@ -12,7 +13,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Represents the process data of an open window. This class is used in the process cache and for the process object of the open window
|
||||
/// </summary>
|
||||
public class WindowProcess
|
||||
internal class WindowProcess
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum size of a file name
|
||||
@@ -27,7 +28,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the id of the process
|
||||
/// </summary>
|
||||
public uint ProcessID
|
||||
internal uint ProcessID
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
@@ -35,7 +36,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the id of the thread
|
||||
/// </summary>
|
||||
public uint ThreadID
|
||||
internal uint ThreadID
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
@@ -43,7 +44,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets the name of the process
|
||||
/// </summary>
|
||||
public string Name
|
||||
internal string Name
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
@@ -51,7 +52,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window belongs to an 'Universal Windows Platform (UWP)' process
|
||||
/// </summary>
|
||||
public bool IsUwpApp
|
||||
internal bool IsUwpApp
|
||||
{
|
||||
get { return _isUwpApp; }
|
||||
}
|
||||
@@ -60,7 +61,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// Gets a value indicating whether this is the shell process or not
|
||||
/// The shell process (like explorer.exe) hosts parts of the user interface (like taskbar, start menu, ...)
|
||||
/// </summary>
|
||||
public bool IsShellProcess
|
||||
internal bool IsShellProcess
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -72,7 +73,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the process exists on the machine
|
||||
/// </summary>
|
||||
public bool DoesExist
|
||||
internal bool DoesExist
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -98,7 +99,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether full access to the process is denied or not
|
||||
/// </summary>
|
||||
public bool IsFullAccessDenied
|
||||
internal bool IsFullAccessDenied
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
@@ -109,7 +110,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <param name="pid">New process id.</param>
|
||||
/// <param name="tid">New thread id.</param>
|
||||
/// <param name="name">New process name.</param>
|
||||
public WindowProcess(uint pid, uint tid, string name)
|
||||
internal WindowProcess(uint pid, uint tid, string name)
|
||||
{
|
||||
UpdateProcessInfo(pid, tid, name);
|
||||
_isUwpApp = Name.ToUpperInvariant().Equals("APPLICATIONFRAMEHOST.EXE", StringComparison.Ordinal);
|
||||
@@ -121,7 +122,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <param name="pid">New process id.</param>
|
||||
/// <param name="tid">New thread id.</param>
|
||||
/// <param name="name">New process name.</param>
|
||||
public void UpdateProcessInfo(uint pid, uint tid, string name)
|
||||
internal void UpdateProcessInfo(uint pid, uint tid, string name)
|
||||
{
|
||||
// TODO: Add verification as to wether the process id and thread id is valid
|
||||
ProcessID = pid;
|
||||
@@ -137,7 +138,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The process ID</returns>
|
||||
public static uint GetProcessIDFromWindowHandle(IntPtr hwnd)
|
||||
internal static uint GetProcessIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
_ = NativeMethods.GetWindowThreadProcessId(hwnd, out uint processId);
|
||||
return processId;
|
||||
@@ -148,7 +149,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The thread ID</returns>
|
||||
public static uint GetThreadIDFromWindowHandle(IntPtr hwnd)
|
||||
internal static uint GetThreadIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
uint threadId = NativeMethods.GetWindowThreadProcessId(hwnd, out _);
|
||||
return threadId;
|
||||
@@ -159,7 +160,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// </summary>
|
||||
/// <param name="pid">The id of the process/param>
|
||||
/// <returns>A string representing the process name or an empty string if the function fails</returns>
|
||||
public static string GetProcessNameFromProcessID(uint pid)
|
||||
internal static string GetProcessNameFromProcessID(uint pid)
|
||||
{
|
||||
IntPtr processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryLimitedInformation, true, (int)pid);
|
||||
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
|
||||
@@ -176,6 +177,23 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the process by it's id. If permissions are required, they will be requested.
|
||||
/// </summary>
|
||||
/// <param name="killProcessTree">Kill process and sub processes.</param>
|
||||
internal void KillThisProcess(bool killProcessTree)
|
||||
{
|
||||
if (IsFullAccessDenied)
|
||||
{
|
||||
string killTree = killProcessTree ? " /t" : string.Empty;
|
||||
Helper.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.GetProcessById((int)ProcessID).Kill(killProcessTree);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Plugin.WindowWalker.Properties;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Plugin.WindowWalker.UnitTests")]
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional settings for the WindowWalker plugin.
|
||||
/// </summary>
|
||||
/// <remarks>Some code parts reused from TimeZone plugin.</remarks>
|
||||
internal sealed class WindowWalkerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Are the class properties initialized with default values
|
||||
/// </summary>
|
||||
private readonly bool _initialized;
|
||||
|
||||
/// <summary>
|
||||
/// An instance of the class <see cref="WindowWalkerSettings"></see>
|
||||
/// </summary>
|
||||
private static WindowWalkerSettings instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether we only search for windows on the currently visible desktop or on all desktops.
|
||||
/// </summary>
|
||||
internal bool ResultsFromVisibleDesktopOnly { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the process id is shown in the subtitle.
|
||||
/// </summary>
|
||||
internal bool SubtitleShowPid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the desktop name is shown in the subtitle.
|
||||
/// We don't show the desktop name if there is only one desktop.
|
||||
/// </summary>
|
||||
internal bool SubtitleShowDesktopName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether we request a confirmation when the user kills a process.
|
||||
/// </summary>
|
||||
internal bool ConfirmKillProcess { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to kill the entire process tree or the selected process only.
|
||||
/// </summary>
|
||||
internal bool KillProcessTree { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether PowerToys run should stay open after executing killing process and closing window.
|
||||
/// </summary>
|
||||
internal bool OpenAfterKillAndClose { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the "kill process" command is hidden on processes that require additional permissions (UAC).
|
||||
/// </summary>
|
||||
internal bool HideKillProcessOnElevatedProcesses { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether we show the explorer settings info or not.
|
||||
/// </summary>
|
||||
internal bool HideExplorerSettingInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowWalkerSettings"/> class.
|
||||
/// Private constructor to make sure there is never more than one instance of this class
|
||||
/// </summary>
|
||||
private WindowWalkerSettings()
|
||||
{
|
||||
// Init class properties with default values
|
||||
UpdateSettings(null);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance property of this class that makes sure that the first instance gets created
|
||||
/// and that all the requests end up at that one instance.
|
||||
/// The benefit of this is that we don't need additional variables/parameters
|
||||
/// to communicate the settings between plugin's classes/methods.
|
||||
/// We can simply access this one instance, whenever we need the actual settings.
|
||||
/// </summary>
|
||||
internal static WindowWalkerSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new WindowWalkerSettings();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list with all additional plugin options.
|
||||
/// </summary>
|
||||
/// <returns>A list with all additional plugin options.</returns>
|
||||
internal static List<PluginAdditionalOption> GetAdditionalOptions()
|
||||
{
|
||||
var optionList = new List<PluginAdditionalOption>
|
||||
{
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
Key = nameof(ResultsFromVisibleDesktopOnly),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingResultsVisibleDesktop,
|
||||
Value = false,
|
||||
},
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
Key = nameof(SubtitleShowPid),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingSubtitlePid,
|
||||
Value = false,
|
||||
},
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
// ToDo: When description property is implemented (#15853), move the note in brackets to description and update to: "The information is only shown, if more than one desktop exists."
|
||||
Key = nameof(SubtitleShowDesktopName),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingSubtitleDesktopName,
|
||||
Value = true,
|
||||
},
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
Key = nameof(ConfirmKillProcess),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingConfirmKillProcess,
|
||||
Value = true,
|
||||
},
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
// ToDo: When description property is implemented (#15853), add description: "Be careful when activating this."
|
||||
Key = nameof(KillProcessTree),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingKillProcessTree,
|
||||
Value = false,
|
||||
},
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
// ToDo: When description property is implemented (#15853), move the note in brackets to description.
|
||||
Key = nameof(OpenAfterKillAndClose),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingOpenAfterKillAndClose,
|
||||
Value = false,
|
||||
},
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
Key = nameof(HideKillProcessOnElevatedProcesses),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingHideKillProcess,
|
||||
Value = false,
|
||||
},
|
||||
new PluginAdditionalOption
|
||||
{
|
||||
// ToDo: When description property is implemented (#15853), add description: "Message is only shown when searching with direct activation command."
|
||||
Key = nameof(HideExplorerSettingInfo),
|
||||
DisplayLabel = Resources.wox_plugin_windowwalker_SettingExplorerSettingInfo,
|
||||
Value = false,
|
||||
},
|
||||
};
|
||||
|
||||
return optionList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update this settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings for all power launcher plugins.</param>
|
||||
internal void UpdateSettings(PowerLauncherPluginSettings settings)
|
||||
{
|
||||
if ((settings is null || settings.AdditionalOptions is null) & _initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ResultsFromVisibleDesktopOnly = GetSettingOrDefault(settings, nameof(ResultsFromVisibleDesktopOnly));
|
||||
SubtitleShowPid = GetSettingOrDefault(settings, nameof(SubtitleShowPid));
|
||||
SubtitleShowDesktopName = GetSettingOrDefault(settings, nameof(SubtitleShowDesktopName));
|
||||
ConfirmKillProcess = GetSettingOrDefault(settings, nameof(ConfirmKillProcess));
|
||||
KillProcessTree = GetSettingOrDefault(settings, nameof(KillProcessTree));
|
||||
OpenAfterKillAndClose = GetSettingOrDefault(settings, nameof(OpenAfterKillAndClose));
|
||||
HideKillProcessOnElevatedProcesses = GetSettingOrDefault(settings, nameof(HideKillProcessOnElevatedProcesses));
|
||||
HideExplorerSettingInfo = GetSettingOrDefault(settings, nameof(HideExplorerSettingInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return one <see cref="bool"/> setting of the given settings list with the given name.
|
||||
/// </summary>
|
||||
/// <param name="settings">The object that contain all settings.</param>
|
||||
/// <param name="name">The name of the setting.</param>
|
||||
/// <returns>A settings value.</returns>
|
||||
private static bool GetSettingOrDefault(PowerLauncherPluginSettings settings, string name)
|
||||
{
|
||||
var option = settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == name);
|
||||
|
||||
// If a setting isn't available, we use the value defined in the method GetAdditionalOptions() as fallback.
|
||||
// We can use First() instead of FirstOrDefault() because the values must exist. Otherwise, we made a mistake when defining the settings.
|
||||
return option?.Value ?? GetAdditionalOptions().First(x => x.Key == name).Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
@@ -4,23 +4,32 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Controls;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Plugin.WindowWalker.Components;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Wox.Plugin;
|
||||
using Wox.Plugin.Common.VirtualDesktop.Helper;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker
|
||||
{
|
||||
public class Main : IPlugin, IPluginI18n
|
||||
public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu
|
||||
{
|
||||
private string IconPath { get; set; }
|
||||
|
||||
private string InfoIconPath { get; set; }
|
||||
|
||||
private PluginInitContext Context { get; set; }
|
||||
|
||||
public string Name => Properties.Resources.wox_plugin_windowwalker_plugin_name;
|
||||
|
||||
private readonly string _assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
|
||||
public string Description => Properties.Resources.wox_plugin_windowwalker_plugin_description;
|
||||
|
||||
internal static readonly VirtualDesktopHelper VirtualDesktopHelperInstance = new VirtualDesktopHelper();
|
||||
|
||||
static Main()
|
||||
{
|
||||
OpenWindows.Instance.UpdateOpenWindowsList();
|
||||
@@ -33,24 +42,17 @@ namespace Microsoft.Plugin.WindowWalker
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
VirtualDesktopHelperInstance.UpdateDesktopList();
|
||||
OpenWindows.Instance.UpdateOpenWindowsList();
|
||||
SearchController.Instance.UpdateSearchText(query.Search);
|
||||
List<SearchResult> searchControllerResults = SearchController.Instance.SearchMatches;
|
||||
|
||||
return searchControllerResults.Select(x => new Result()
|
||||
{
|
||||
Title = x.Result.Title,
|
||||
IcoPath = IconPath,
|
||||
SubTitle = Properties.Resources.wox_plugin_windowwalker_running + ": " + x.Result.ProcessInfo.Name,
|
||||
Action = c =>
|
||||
{
|
||||
x.Result.SwitchToWindow();
|
||||
return true;
|
||||
},
|
||||
return ResultHelper.GetResultList(searchControllerResults, !string.IsNullOrEmpty(query.ActionKeyword), IconPath, InfoIconPath);
|
||||
}
|
||||
|
||||
// For debugging you can remove the comment sign in the next line.
|
||||
// ToolTipData = new ToolTipData(x.Result.Title, $"hWnd: {x.Result.Hwnd}\nWindow class: {x.Result.ClassName}\nProcess ID: {x.Result.ProcessInfo.ProcessID}\nThread ID: {x.Result.ProcessInfo.ThreadID}\nProcess: {x.Result.ProcessInfo.Name}\nProcess exists: {x.Result.ProcessInfo.DoesExist}\nIs full access denied: {x.Result.ProcessInfo.IsFullAccessDenied}\nIs uwp app: {x.Result.ProcessInfo.IsUwpApp}\nIs ShellProcess: {x.Result.ProcessInfo.IsShellProcess}\nIs window cloaked: {x.Result.IsCloaked}\nWindow cloak state: {x.Result.GetWindowCloakState()}"),
|
||||
}).ToList();
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
return ContextMenuHelper.GetContextMenuResults(selectedResult);
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
@@ -60,16 +62,23 @@ namespace Microsoft.Plugin.WindowWalker
|
||||
UpdateIconPath(Context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
public IEnumerable<PluginAdditionalOption> AdditionalOptions
|
||||
{
|
||||
get { return WindowWalkerSettings.GetAdditionalOptions(); }
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
IconPath = "Images/windowwalker.light.png";
|
||||
InfoIconPath = "Images/info.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
IconPath = "Images/windowwalker.dark.png";
|
||||
InfoIconPath = "Images/info.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,5 +96,15 @@ namespace Microsoft.Plugin.WindowWalker
|
||||
{
|
||||
return Properties.Resources.wox_plugin_windowwalker_plugin_description;
|
||||
}
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherPluginSettings settings)
|
||||
{
|
||||
WindowWalkerSettings.Instance.UpdateSettings(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,12 @@
|
||||
<None Update="Images\windowwalker.light.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\info.dark.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\info.light.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -19,7 +19,7 @@ namespace Microsoft.Plugin.WindowWalker.Properties {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
@@ -60,6 +60,96 @@ namespace Microsoft.Plugin.WindowWalker.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Close window.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_Close {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_Close", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Desktop.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_Desktop {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_Desktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Folder windows doesn't run in separate processes. (Klick to open Explorer properties.).
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_ExplorerInfoSubTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_ExplorerInfoSubTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Info: Killing the Explorer process isn't possible..
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_ExplorerInfoTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_ExplorerInfoTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_Kill {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_Kill", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your are going to kill the following process:.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_KillMessage {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_KillMessage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Continue?.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_KillMessageQuestion {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_KillMessageQuestion", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process confirmation.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_KillMessageTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_KillMessageTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Because this is an app process, all instances of the app will be killed. Continue?.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_KillMessageUwp {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_KillMessageUwp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No..
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_Number {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_Number", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Switches between open windows..
|
||||
/// </summary>
|
||||
@@ -78,12 +168,102 @@ namespace Microsoft.Plugin.WindowWalker.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Process name.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_Process {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_Process", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Process id.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_ProcessId {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_ProcessId", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Running.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_running {
|
||||
public static string wox_plugin_windowwalker_Running {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_running", resourceCulture);
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_Running", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Request confirmation when killing a process.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingConfirmKillProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingConfirmKillProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide Explorer process information.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingExplorerSettingInfo {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingExplorerSettingInfo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide "kill process" button if additional permissions required.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingHideKillProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingHideKillProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process and its child processes.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingKillProcessTree {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingKillProcessTree", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Stay open after closing windows and killing processes (Not working with kill process confirmation).
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingOpenAfterKillAndClose {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingOpenAfterKillAndClose", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show only results from visible desktop.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingResultsVisibleDesktop {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingResultsVisibleDesktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show desktop name in subtitle (If two or more desktops exist).
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingSubtitleDesktopName {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingSubtitleDesktopName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show process id in subtitle.
|
||||
/// </summary>
|
||||
public static string wox_plugin_windowwalker_SettingSubtitlePid {
|
||||
get {
|
||||
return ResourceManager.GetString("wox_plugin_windowwalker_SettingSubtitlePid", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,71 @@
|
||||
<data name="wox_plugin_windowwalker_plugin_description" xml:space="preserve">
|
||||
<value>Switches between open windows.</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_running" xml:space="preserve">
|
||||
<data name="wox_plugin_windowwalker_Running" xml:space="preserve">
|
||||
<value>Running</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingConfirmKillProcess" xml:space="preserve">
|
||||
<value>Request confirmation when killing a process</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingExplorerSettingInfo" xml:space="preserve">
|
||||
<value>Hide Explorer process information</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingHideKillProcess" xml:space="preserve">
|
||||
<value>Hide "kill process" button if additional permissions required</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingResultsVisibleDesktop" xml:space="preserve">
|
||||
<value>Show only results from visible desktop</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingSubtitleDesktopName" xml:space="preserve">
|
||||
<value>Show desktop name in subtitle (If two or more desktops exist)</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingSubtitlePid" xml:space="preserve">
|
||||
<value>Show process id in subtitle</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingOpenAfterKillAndClose" xml:space="preserve">
|
||||
<value>Stay open after closing windows and killing processes (Not working with kill process confirmation)</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_Desktop" xml:space="preserve">
|
||||
<value>Desktop</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_Number" xml:space="preserve">
|
||||
<value>No.</value>
|
||||
<comment>Short version of "Number"</comment>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_Process" xml:space="preserve">
|
||||
<value>Process name</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_ProcessId" xml:space="preserve">
|
||||
<value>Process id</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_ExplorerInfoSubTitle" xml:space="preserve">
|
||||
<value>Folder windows doesn't run in separate processes. (Klick to open Explorer properties.)</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_ExplorerInfoTitle" xml:space="preserve">
|
||||
<value>Info: Killing the Explorer process isn't possible.</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_Close" xml:space="preserve">
|
||||
<value>Close window</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_Kill" xml:space="preserve">
|
||||
<value>Kill process</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_KillMessage" xml:space="preserve">
|
||||
<value>Your are going to kill the following process:</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_KillMessageQuestion" xml:space="preserve">
|
||||
<value>Continue?</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_KillMessageTitle" xml:space="preserve">
|
||||
<value>Kill process confirmation</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_KillMessageUwp" xml:space="preserve">
|
||||
<value>Because this is an app process, all instances of the app will be killed. Continue?</value>
|
||||
</data>
|
||||
<data name="wox_plugin_windowwalker_SettingKillProcessTree" xml:space="preserve">
|
||||
<value>Kill process and its child processes</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -126,13 +126,14 @@ namespace Wox.Infrastructure
|
||||
return Process.Start(processStartInfo);
|
||||
}
|
||||
|
||||
public static bool OpenInShell(string path, string arguments = null, string workingDir = null, bool runAsAdmin = false)
|
||||
public static bool OpenInShell(string path, string arguments = null, string workingDir = null, bool runAsAdmin = false, bool runWithHiddenWindow = false)
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo.FileName = path;
|
||||
process.StartInfo.WorkingDirectory = string.IsNullOrWhiteSpace(workingDir) ? string.Empty : workingDir;
|
||||
process.StartInfo.Arguments = string.IsNullOrWhiteSpace(arguments) ? string.Empty : arguments;
|
||||
process.StartInfo.WindowStyle = runWithHiddenWindow ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal;
|
||||
|
||||
if (runAsAdmin)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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;
|
||||
|
||||
namespace Wox.Plugin.Common.VirtualDesktop.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Virtual Desktop Manager class
|
||||
/// Code used from <see href="https://docs.microsoft.com/en-us/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10"./>
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
|
||||
internal class CVirtualDesktopManager
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Wox.Plugin.Common.VirtualDesktop.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for accessing Virtual Desktop Manager.
|
||||
/// Code used from <see href="https://docs.microsoft.com/en-us/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10"./>
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
internal interface IVirtualDesktopManager
|
||||
{
|
||||
[PreserveSig]
|
||||
int IsWindowOnCurrentVirtualDesktop([In] IntPtr hTopLevelWindow, [Out] out int onCurrentDesktop);
|
||||
|
||||
[PreserveSig]
|
||||
int GetWindowDesktopId([In] IntPtr hTopLevelWindow, [Out] out Guid desktop);
|
||||
|
||||
[PreserveSig]
|
||||
int MoveWindowToDesktop([In] IntPtr hTopLevelWindow, [MarshalAs(UnmanagedType.LPStruct)][In] Guid desktop);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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 Wox.Plugin.Common.VirtualDesktop.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that represents a Virtual Desktop
|
||||
/// </summary>
|
||||
/// <remarks>This class is named VDesktop to make clear it isn't an instance of the original Desktop class from Virtual Desktop Manager.
|
||||
/// We can't use the original one, because therefore we must access private com interfaces. We aren't allowed to do this, because this is an official Microsoft project.</remarks>
|
||||
public class VDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the guid of the desktop
|
||||
/// </summary>
|
||||
public Guid Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the desktop
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number (position) of the desktop
|
||||
/// </summary>
|
||||
public int Number
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the desktop is currently visible to the user
|
||||
/// </summary>
|
||||
public bool IsVisible
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the desktop guid belongs to the generic "AllDesktops" view.
|
||||
/// This view hold all windows that are pinned to all desktops.
|
||||
/// </summary>
|
||||
public bool IsAllDesktopsView
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the position of a desktop in the list of all desktops
|
||||
/// </summary>
|
||||
public VirtualDesktopPosition Position
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty instance of <see cref="VDesktop"/>
|
||||
/// </summary>
|
||||
public static VDesktop Empty
|
||||
{
|
||||
get
|
||||
{
|
||||
return new VDesktop()
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Name = string.Empty,
|
||||
Number = 0,
|
||||
IsVisible = false,
|
||||
IsAllDesktopsView = false,
|
||||
Position = VirtualDesktopPosition.NotApplicable,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
using Wox.Plugin.Common.VirtualDesktop.Interop;
|
||||
using Wox.Plugin.Common.Win32;
|
||||
using Wox.Plugin.Logger;
|
||||
using Wox.Plugin.Properties;
|
||||
|
||||
namespace Wox.Plugin.Common.VirtualDesktop.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to work with Virtual Desktops.
|
||||
/// This helper uses only public available and documented COM-Interfaces or informations from registry.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this helper you have to create an instance of it and access the method via the helper instance.
|
||||
/// We are only allowed to use public documented com interfaces.
|
||||
/// </remarks>
|
||||
/// <SeeAlso href="https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ivirtualdesktopmanager">Documentation of IVirtualDesktopManager interface</SeeAlso>
|
||||
/// <SeeAlso href="https://docs.microsoft.com/en-us/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10">CSharp example code for IVirtualDesktopManager</SeeAlso>
|
||||
public class VirtualDesktopHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Instance of "Virtual Desktop Manager"
|
||||
/// </summary>
|
||||
private readonly IVirtualDesktopManager _virtualDesktopManager;
|
||||
|
||||
/// <summary>
|
||||
/// Internal settings to enable automatic update of desktop list.
|
||||
/// This will be off by default to avoid to many registry queries.
|
||||
/// </summary>
|
||||
private readonly bool _desktopListAutoUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// List of all available Virtual Desktop in their real order
|
||||
/// The order and list in the registry is always up to date
|
||||
/// </summary>
|
||||
private List<Guid> availableDesktops = new List<Guid>();
|
||||
|
||||
/// <summary>
|
||||
/// Id of the current visible Desktop.
|
||||
/// </summary>
|
||||
private Guid currentDesktop;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VirtualDesktopHelper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="desktopListUpdate">Setting to configure if the list of available desktops should update automatically or only when calling <see cref="UpdateDesktopList"/>. Per default this is set to manual update (false) to have less registry queries.</param>
|
||||
public VirtualDesktopHelper(bool desktopListUpdate = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
_virtualDesktopManager = (IVirtualDesktopManager)new CVirtualDesktopManager();
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
Log.Exception("Failed to create an instance of COM interface <IVirtualDesktopManager>.", ex, typeof(VirtualDesktopHelper));
|
||||
return;
|
||||
}
|
||||
|
||||
_desktopListAutoUpdate = desktopListUpdate;
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Virtual Desktop Manager is initialized successfully
|
||||
/// </summary>
|
||||
public bool VirtualDesktopManagerInitialized
|
||||
{
|
||||
get { return _virtualDesktopManager != null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to update the list of Virtual Desktops from Registry
|
||||
/// The data in the registry are always up to date
|
||||
/// </summary>
|
||||
public void UpdateDesktopList()
|
||||
{
|
||||
// List of all desktops
|
||||
RegistryKey allDeskSubKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops", false);
|
||||
byte[] allDeskValue = (byte[])allDeskSubKey.GetValue("VirtualDesktopIDs");
|
||||
availableDesktops.Clear();
|
||||
|
||||
if (allDeskValue != null)
|
||||
{
|
||||
// Each guid has a length of 16 elements
|
||||
int numberOfDesktops = allDeskValue.Length / 16;
|
||||
|
||||
for (int i = 0; i < numberOfDesktops; i++)
|
||||
{
|
||||
byte[] guidArray = new byte[16];
|
||||
Array.ConstrainedCopy(allDeskValue, i * 16, guidArray, 0, 16);
|
||||
availableDesktops.Add(new Guid(guidArray));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Failed to read the list of existing desktops form registry.", typeof(VirtualDesktopHelper));
|
||||
}
|
||||
|
||||
// Guid for current desktop
|
||||
int userSessionId = Process.GetCurrentProcess().SessionId;
|
||||
RegistryKey currentDeskSubKey = Registry.CurrentUser.OpenSubKey($"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SessionInfo\\{userSessionId}\\VirtualDesktops", false);
|
||||
var currentDeskValue = currentDeskSubKey.GetValue("CurrentVirtualDesktop");
|
||||
if (currentDeskValue != null)
|
||||
{
|
||||
currentDesktop = new Guid((byte[])currentDeskValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The registry value is missing when the user hasn't switched the desktop at least one time before reading the registry. In this case we can set it to desktop one.
|
||||
Log.Debug("Failed to read the id for the current desktop form registry.", typeof(VirtualDesktopHelper));
|
||||
currentDesktop = availableDesktops[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ordered list with the ids of all existing desktops. The list is ordered in the same way as the existing desktops.
|
||||
/// </summary>
|
||||
/// <returns>List of desktop ids or an empty list on failure.</returns>
|
||||
public List<Guid> GetDesktopIdList()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return availableDesktops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ordered list with of all existing desktops and their properties. The list is ordered in the same way as the existing desktops.
|
||||
/// </summary>
|
||||
/// <returns>List of desktops or an empty list on failure.</returns>
|
||||
public List<VDesktop> GetDesktopList()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
List<VDesktop> list = new List<VDesktop>();
|
||||
foreach (Guid d in availableDesktops)
|
||||
{
|
||||
list.Add(CreateVDesktopInstance(d));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the count of existing desktops
|
||||
/// </summary>
|
||||
/// <returns>Number of existing desktops or zero on failure.</returns>
|
||||
public int GetDesktopCount()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return availableDesktops.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the id of the desktop that is currently visible to the user.
|
||||
/// </summary>
|
||||
/// <returns>Guid of the current desktop or an empty guid on failure.</returns>
|
||||
public Guid GetCurrentDesktopId()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return currentDesktop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of <see cref="VDesktop"/> for the desktop that is currently visible to the user.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="VDesktop"/> for the current desktop, or an empty instance of <see cref="VDesktop"/> on failure.</returns>
|
||||
public VDesktop GetCurrentDesktop()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return CreateVDesktopInstance(currentDesktop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a desktop is currently visible to the user.
|
||||
/// </summary>
|
||||
/// <param name="desktop">The guid of the desktop to check.</param>
|
||||
/// <returns>A value indicating if the guid belongs to the currently visible desktop.</returns>
|
||||
public bool IsDesktopVisible(Guid desktop)
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return currentDesktop == desktop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number (position) of a desktop.
|
||||
/// </summary>
|
||||
/// <param name="desktop">The guid of the desktop.</param>
|
||||
/// <returns>Number of the desktop, if found. Otherwise a value of zero.</returns>
|
||||
public int GetDesktopNumber(Guid desktop)
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
// Adding +1 because index starts with zero and humans start counting with one.
|
||||
return availableDesktops.IndexOf(desktop) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of a desktop
|
||||
/// </summary>
|
||||
/// <param name="desktop">Guid of the desktop</param>
|
||||
/// <returns>Returns the name of the desktop or an empty string on failure.</returns>
|
||||
public string GetDesktopName(Guid desktop)
|
||||
{
|
||||
if (desktop == Guid.Empty || !GetDesktopIdList().Contains(desktop))
|
||||
{
|
||||
Log.Debug($"GetDesktopName() failed. Parameter contains an invalid desktop guid ({desktop}) that doesn't belongs to an available desktop. Maybe the guid belongs to the generic 'AllDesktops' view.", typeof(VirtualDesktopHelper));
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// If the desktop name was not changed by the user, it isn't saved to the registry. Then we need the default name for the desktop.
|
||||
var defaultName = string.Format(Resources.VirtualDesktopHelper_Desktop, GetDesktopNumber(desktop));
|
||||
|
||||
string registryPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops\\Desktops\\{" + desktop.ToString().ToUpper() + "}";
|
||||
RegistryKey deskSubKey = Registry.CurrentUser.OpenSubKey(registryPath, false);
|
||||
var desktopName = deskSubKey?.GetValue("Name");
|
||||
|
||||
return (desktopName != null) ? (string)desktopName : defaultName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the position type for a desktop.
|
||||
/// </summary>
|
||||
/// <param name="desktop">Guid of the desktop.</param>
|
||||
/// <returns>Type of desktop position.</returns>
|
||||
public VirtualDesktopPosition GetDesktopPositionType(Guid desktop)
|
||||
{
|
||||
int desktopNumber = GetDesktopNumber(desktop);
|
||||
int desktopCount = GetDesktopCount();
|
||||
|
||||
if (desktopNumber == 1)
|
||||
{
|
||||
return VirtualDesktopPosition.FirstDesktop;
|
||||
}
|
||||
else if (desktopNumber == desktopCount)
|
||||
{
|
||||
return VirtualDesktopPosition.LastDesktop;
|
||||
}
|
||||
else if (desktopNumber > 1 & desktopNumber < desktopCount)
|
||||
{
|
||||
return VirtualDesktopPosition.BetweenOtherDesktops;
|
||||
}
|
||||
else
|
||||
{
|
||||
return VirtualDesktopPosition.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the desktop id for a window.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <param name="desktopId">The guid of the desktop, where the window is shown.</param>
|
||||
/// <returns>HResult of the called method.</returns>
|
||||
public int GetWindowDesktopId(IntPtr hWindow, out Guid desktopId)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
Log.Error("GetWindowDesktopId() failed: The instance of <IVirtualDesktopHelper> isn't available.", typeof(VirtualDesktopHelper));
|
||||
desktopId = Guid.Empty;
|
||||
return unchecked((int)HRESULT.E_UNEXPECTED);
|
||||
}
|
||||
|
||||
return _virtualDesktopManager.GetWindowDesktopId(hWindow, out desktopId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of <see cref="VDesktop"/> for the desktop where the window is assigned to.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <returns>An instance of <see cref="VDesktop"/> for the desktop where the window is assigned to, or an empty instance of <see cref="VDesktop"/> on failure.</returns>
|
||||
public VDesktop GetWindowDesktop(IntPtr hWindow)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
Log.Error("GetWindowDesktopId() failed: The instance of <IVirtualDesktopHelper> isn't available.", typeof(VirtualDesktopHelper));
|
||||
return CreateVDesktopInstance(Guid.Empty);
|
||||
}
|
||||
|
||||
int hr = _virtualDesktopManager.GetWindowDesktopId(hWindow, out Guid desktopId);
|
||||
return (hr != (int)HRESULT.S_OK || desktopId == Guid.Empty) ? VDesktop.Empty : CreateVDesktopInstance(desktopId, hWindow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the desktop assignment type for a window.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <returns>Type of <see cref="DesktopAssignment"/>.</returns>
|
||||
public VirtualDesktopAssignmentType GetWindowDesktopAssignmentType(IntPtr hWindow)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
Log.Error("GetWindowDesktopAssignmentType() failed: The instance of <IVirtualDesktopHelper> isn't available.", typeof(VirtualDesktopHelper));
|
||||
return VirtualDesktopAssignmentType.Unknown;
|
||||
}
|
||||
|
||||
_ = _virtualDesktopManager.IsWindowOnCurrentVirtualDesktop(hWindow, out int isOnCurrentDesktop);
|
||||
int hResult = GetWindowDesktopId(hWindow, out Guid windowDesktopId);
|
||||
|
||||
if (hResult != (int)HRESULT.S_OK)
|
||||
{
|
||||
return VirtualDesktopAssignmentType.Unknown;
|
||||
}
|
||||
else if (windowDesktopId == Guid.Empty)
|
||||
{
|
||||
return VirtualDesktopAssignmentType.NotAssigned;
|
||||
}
|
||||
else if (isOnCurrentDesktop == 1 && !GetDesktopIdList().Contains(windowDesktopId))
|
||||
{
|
||||
// These windows are marked as visible on the current desktop, but the desktop id doesn't belongs to an existing desktop.
|
||||
// In this case the desktop id belongs to the generic view 'AllDesktops'.
|
||||
return VirtualDesktopAssignmentType.AllDesktops;
|
||||
}
|
||||
else if (isOnCurrentDesktop == 1)
|
||||
{
|
||||
return VirtualDesktopAssignmentType.CurrentDesktop;
|
||||
}
|
||||
else
|
||||
{
|
||||
return VirtualDesktopAssignmentType.OtherDesktop;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the window is assigned to a currently visible desktop.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle to the top level window.</param>
|
||||
/// <returns>True if the desktop with the window is visible or if the window is assigned to all desktops. False if the desktop is not visible and on failure,</returns>
|
||||
public bool IsWindowOnVisibleDesktop(IntPtr hWindow)
|
||||
{
|
||||
return GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.CurrentDesktop || GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.AllDesktops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the window is cloaked by VirtualDesktopManager.
|
||||
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <returns>A value indicating if the window is cloaked by Virtual Desktop Manager, because it is moved to an other desktop.</returns>
|
||||
public bool IsWindowCloakedByVirtualDesktopManager(IntPtr hWindow)
|
||||
{
|
||||
// If a window is hidden because it is moved to an other desktop, then DWM returns type "CloakedShell". If DWM returns an other type the window is not cloaked by shell or VirtualDesktopManager.
|
||||
_ = NativeMethods.DwmGetWindowAttribute(hWindow, (int)DwmWindowAttributes.Cloaked, out int dwmCloakedState, sizeof(uint));
|
||||
return GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.OtherDesktop && dwmCloakedState == (int)DwmWindowCloakStates.CloakedShell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the window to a specific desktop.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the top level window.</param>
|
||||
/// <param name="desktopId">Guid of the target desktop.</param>
|
||||
/// <returns>True on success and false on failure.</returns>
|
||||
public bool MoveWindowToDesktop(IntPtr hWindow, in Guid desktopId)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
Log.Error("MoveWindowToDesktop() failed: The instance of <IVirtualDesktopHelper> isn't available.", typeof(VirtualDesktopHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
int hr = _virtualDesktopManager.MoveWindowToDesktop(hWindow, desktopId);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
Log.Exception($"Failed to move the window ({hWindow}) to an other desktop ({desktopId}).", Marshal.GetExceptionForHR(hr), typeof(VirtualDesktopHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a window one desktop left.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the top level window.</param>
|
||||
/// <returns>True on success and false on failure.</returns>
|
||||
public bool MoveWindowOneDesktopLeft(IntPtr hWindow)
|
||||
{
|
||||
if (GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.Unknown || GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.NotAssigned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int hr = GetWindowDesktopId(hWindow, out Guid windowDesktop);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
Log.Error($"Failed to move the window ({hWindow}) one desktop left: Can't get current desktop of the window.", typeof(VirtualDesktopHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
int windowDesktopNumber = GetDesktopIdList().IndexOf(windowDesktop);
|
||||
if (windowDesktopNumber == 1)
|
||||
{
|
||||
Log.Error($"Failed to move the window ({hWindow}) one desktop left: The window is on the first desktop.", typeof(VirtualDesktopHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
Guid newDesktop = availableDesktops[windowDesktopNumber - 1];
|
||||
return MoveWindowToDesktop(hWindow, newDesktop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a window one desktop right.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the top level window.</param>
|
||||
/// <returns>True on success and false on failure.</returns>
|
||||
public bool MoveWindowOneDesktopRight(IntPtr hWindow)
|
||||
{
|
||||
if (GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.Unknown || GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.NotAssigned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int hr = GetWindowDesktopId(hWindow, out Guid windowDesktop);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
Log.Error($"Failed to move the window ({hWindow}) one desktop right: Can't get current desktop of the window.", typeof(VirtualDesktopHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
int windowDesktopNumber = GetDesktopIdList().IndexOf(windowDesktop);
|
||||
if (windowDesktopNumber == GetDesktopCount())
|
||||
{
|
||||
Log.Error($"Failed to move the window ({hWindow}) one desktop right: The window is on the last desktop.", typeof(VirtualDesktopHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
Guid newDesktop = availableDesktops[windowDesktopNumber + 1];
|
||||
return MoveWindowToDesktop(hWindow, newDesktop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of VDesktop for a Guid.
|
||||
/// </summary>
|
||||
/// <param name="desktop">Guid of the desktop.</param>
|
||||
/// <param name="hWindow">Handle of the window shown on the desktop. If this parameter is set we can detect if it is the AllDesktops view.</param>
|
||||
/// <returns>VDesktop instance.</returns>
|
||||
private VDesktop CreateVDesktopInstance(Guid desktop, IntPtr hWindow = default)
|
||||
{
|
||||
// Can be only detected if method is invoked with window handle parameter.
|
||||
bool isAllDesktops = (hWindow != default) && GetWindowDesktopAssignmentType(hWindow) == VirtualDesktopAssignmentType.AllDesktops;
|
||||
|
||||
return new VDesktop()
|
||||
{
|
||||
Id = desktop,
|
||||
Name = isAllDesktops ? Resources.VirtualDesktopHelper_AllDesktops : GetDesktopName(desktop),
|
||||
Number = GetDesktopNumber(desktop),
|
||||
IsVisible = IsDesktopVisible(desktop) || isAllDesktops,
|
||||
IsAllDesktopsView = isAllDesktops,
|
||||
Position = GetDesktopPositionType(desktop),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to show in which way a window is assigned to a desktop
|
||||
/// </summary>
|
||||
public enum VirtualDesktopAssignmentType
|
||||
{
|
||||
Unknown = -1,
|
||||
NotAssigned = 0,
|
||||
AllDesktops = 1,
|
||||
CurrentDesktop = 2,
|
||||
OtherDesktop = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to show the position of a desktop in the list of all desktops
|
||||
/// </summary>
|
||||
public enum VirtualDesktopPosition
|
||||
{
|
||||
FirstDesktop,
|
||||
BetweenOtherDesktops,
|
||||
LastDesktop,
|
||||
NotApplicable,
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,11 @@ namespace Wox.Plugin.Common.Win32
|
||||
/// Restores the window to its normal position and size.
|
||||
/// </summary>
|
||||
public const int SC_RESTORE = 0xf120;
|
||||
|
||||
/// <summary>
|
||||
/// Closes the window
|
||||
/// </summary>
|
||||
public const int SC_CLOSE = 0xF060;
|
||||
}
|
||||
|
||||
public enum HRESULT : uint
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Wox.Plugin.Properties {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
@@ -77,5 +77,23 @@ namespace Wox.Plugin.Properties {
|
||||
return ResourceManager.GetString("FailedToLoadPluginTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to On all Desktops.
|
||||
/// </summary>
|
||||
public static string VirtualDesktopHelper_AllDesktops {
|
||||
get {
|
||||
return ResourceManager.GetString("VirtualDesktopHelper_AllDesktops", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Desktop {0}.
|
||||
/// </summary>
|
||||
public static string VirtualDesktopHelper_Desktop {
|
||||
get {
|
||||
return ResourceManager.GetString("VirtualDesktopHelper_Desktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,5 +122,14 @@
|
||||
</data>
|
||||
<data name="FailedToLoadPluginTitle" xml:space="preserve">
|
||||
<value>Fail to Load {0} Plugin</value>
|
||||
<comment>Do not translate '{0}' as this is a place holder and will be converted in code.</comment>
|
||||
</data>
|
||||
<data name="VirtualDesktopHelper_AllDesktops" xml:space="preserve">
|
||||
<value>On all Desktops</value>
|
||||
<comment>The window is shown on all desktops.</comment>
|
||||
</data>
|
||||
<data name="VirtualDesktopHelper_Desktop" xml:space="preserve">
|
||||
<value>Desktop {0}</value>
|
||||
<comment>Default name for virtual desktops. Do not translate '{0}' as this is a place holder and will be converted in code.</comment>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user