mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-18 10:07:56 +01:00
Merge pull request #188 from zadjii-msft/joadoumie/window-walker
Migrate Window Walker from PT Run
This commit is contained in:
@@ -707,6 +707,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewPlus.ShellExtension.win1
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.Shell", "src\modules\cmdpal\Exts\Microsoft.CmdPal.Ext.Shell\Microsoft.CmdPal.Ext.Shell.csproj", "{C0CE3B5E-16D3-495D-B335-CA791B660162}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.WindowWalker", "src\modules\cmdpal\Exts\Microsoft.CmdPal.Ext.WindowWalker\Microsoft.CmdPal.Ext.WindowWalker.csproj", "{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -3317,6 +3319,18 @@ Global
|
||||
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x64.Build.0 = Release|x64
|
||||
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x86.ActiveCfg = Release|x64
|
||||
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x86.Build.0 = Release|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|x64.Build.0 = Debug|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|x86.Build.0 = Debug|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|x64.ActiveCfg = Release|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|x64.Build.0 = Release|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|x86.ActiveCfg = Release|x64
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3587,6 +3601,7 @@ Global
|
||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class CloseWindowCommand : InvokableCommand
|
||||
{
|
||||
private readonly Window _window;
|
||||
|
||||
public CloseWindowCommand(Window window)
|
||||
{
|
||||
Icon = new("\xE8BB");
|
||||
Name = $"{Resources.windowwalker_Close}";
|
||||
_window = window;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (!_window.IsWindow)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Can not close the window '{_window.Title}' ({_window.Hwnd}), because it doesn't exist." });
|
||||
}
|
||||
|
||||
_window.CloseThisWindow();
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class ExplorerInfoResultCommand : InvokableCommand
|
||||
{
|
||||
public ExplorerInfoResultCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public static bool OpenInShell(string path, string? arguments = null, string? workingDir = null, ShellRunAsType runAs = ShellRunAsType.None, 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;
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
|
||||
if (runAs == ShellRunAsType.Administrator)
|
||||
{
|
||||
process.StartInfo.Verb = "RunAs";
|
||||
}
|
||||
else if (runAs == ShellRunAsType.OtherUser)
|
||||
{
|
||||
process.StartInfo.Verb = "RunAsUser";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
return true;
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Unable to open {path}: {ex.Message}" });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
OpenInShell("rundll32.exe", "shell32.dll,Options_RunDLL 7"); // "shell32.dll,Options_RunDLL 7" opens the view tab in folder options of explorer.
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
public enum ShellRunAsType
|
||||
{
|
||||
None,
|
||||
Administrator,
|
||||
OtherUser,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class KillProcessCommand : InvokableCommand
|
||||
{
|
||||
private readonly Window _window;
|
||||
|
||||
public KillProcessCommand(Window window)
|
||||
{
|
||||
Icon = new("\xE74D"); // Delete symbol
|
||||
Name = $"{Resources.windowwalker_Kill}";
|
||||
_window = window;
|
||||
}
|
||||
|
||||
/// <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 KillProcess(Window window)
|
||||
{
|
||||
// Validate process
|
||||
if (!window.IsWindow || !window.Process.DoesExist || string.IsNullOrEmpty(window.Process.Name) || !window.Process.Name.Equals(WindowProcess.GetProcessNameFromProcessID(window.Process.ProcessID), StringComparison.Ordinal))
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Can not kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}), because it doesn't exist." });
|
||||
|
||||
// TODO GH #86 -- need to figure out how to show status message once implemented on host
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request user confirmation
|
||||
if (SettingsManager.Instance.ConfirmKillProcess)
|
||||
{
|
||||
// TODO GH #138, #153 -- need to figure out how to confirm kill process? should this just be the same status thing... maybe not? Need message box? Could be nested context menu.
|
||||
/*
|
||||
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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Kill process
|
||||
window.Process.KillThisProcess(SettingsManager.Instance.KillProcessTree);
|
||||
return !SettingsManager.Instance.OpenAfterKillAndClose;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (KillProcess(_window))
|
||||
{
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class SwitchToWindowCommand : InvokableCommand
|
||||
{
|
||||
private readonly Window? _window;
|
||||
|
||||
public SwitchToWindowCommand(Window? window)
|
||||
{
|
||||
Name = Resources.window_walker_top_level_command_title;
|
||||
Icon = new(string.Empty);
|
||||
_window = window;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (_window is null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "Can not switch to the window, because it doesn't exist." });
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
_window.SwitchToWindow();
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using ABI.Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
internal sealed class ContextMenuHelper
|
||||
{
|
||||
internal static List<CommandContextItem> GetContextMenuResults(in WindowWalkerListItem listItem)
|
||||
{
|
||||
if (!(listItem?.Window is Window windowData))
|
||||
{
|
||||
return new List<CommandContextItem>(0);
|
||||
}
|
||||
|
||||
var contextMenu = new List<CommandContextItem>()
|
||||
{
|
||||
new(new CloseWindowCommand(windowData))
|
||||
{
|
||||
RequestedShortcut = new(true, false, false, false, (int)VirtualKey.F4, 0),
|
||||
},
|
||||
};
|
||||
|
||||
// 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 && string.Equals(windowData.Process.Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase))
|
||||
&& !(windowData.Process.IsFullAccessDenied && SettingsManager.Instance.HideKillProcessOnElevatedProcesses))
|
||||
{
|
||||
contextMenu.Add(new CommandContextItem(new KillProcessCommand(windowData))
|
||||
{
|
||||
RequestedShortcut = new(true, false, false, false, (int)VirtualKey.Delete, 0),
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Class housing fuzzy matching methods
|
||||
/// </summary>
|
||||
internal static class FuzzyMatching
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the best match (the one with the most
|
||||
/// number of letters adjacent to each other) and
|
||||
/// returns the index location of each of the letters
|
||||
/// of the matches
|
||||
/// </summary>
|
||||
/// <param name="text">The text to search inside of</param>
|
||||
/// <param name="searchText">the text to search for</param>
|
||||
/// <returns>returns the index location of each of the letters of the matches</returns>
|
||||
internal static List<int> FindBestFuzzyMatch(string text, string searchText)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(searchText);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(text);
|
||||
|
||||
// Using CurrentCulture since this is user facing
|
||||
searchText = searchText.ToLower(CultureInfo.CurrentCulture);
|
||||
text = text.ToLower(CultureInfo.CurrentCulture);
|
||||
|
||||
// Create a grid to march matches like
|
||||
// eg.
|
||||
// a b c a d e c f g
|
||||
// a x x
|
||||
// c x x
|
||||
var matches = new bool[text.Length, searchText.Length];
|
||||
for (var firstIndex = 0; firstIndex < text.Length; firstIndex++)
|
||||
{
|
||||
for (var secondIndex = 0; secondIndex < searchText.Length; secondIndex++)
|
||||
{
|
||||
matches[firstIndex, secondIndex] =
|
||||
searchText[secondIndex] == text[firstIndex] ?
|
||||
true :
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
||||
// use this table to get all the possible matches
|
||||
List<List<int>> allMatches = GetAllMatchIndexes(matches);
|
||||
|
||||
// return the score that is the max
|
||||
var maxScore = allMatches.Count > 0 ? CalculateScoreForMatches(allMatches[0]) : 0;
|
||||
List<int> bestMatch = allMatches.Count > 0 ? allMatches[0] : new List<int>();
|
||||
|
||||
foreach (var match in allMatches)
|
||||
{
|
||||
var score = CalculateScoreForMatches(match);
|
||||
if (score > maxScore)
|
||||
{
|
||||
bestMatch = match;
|
||||
maxScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the possible matches to the search string with in the text
|
||||
/// </summary>
|
||||
/// <param name="matches"> a table showing the matches as generated by
|
||||
/// a two dimensional array with the first dimension the text and the second
|
||||
/// 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>
|
||||
internal static List<List<int>> GetAllMatchIndexes(bool[,] matches)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(matches);
|
||||
|
||||
List<List<int>> results = new List<List<int>>();
|
||||
|
||||
for (var secondIndex = 0; secondIndex < matches.GetLength(1); secondIndex++)
|
||||
{
|
||||
for (var firstIndex = 0; firstIndex < matches.GetLength(0); firstIndex++)
|
||||
{
|
||||
if (secondIndex == 0 && matches[firstIndex, secondIndex])
|
||||
{
|
||||
results.Add(new List<int> { firstIndex });
|
||||
}
|
||||
else if (matches[firstIndex, secondIndex])
|
||||
{
|
||||
var tempList = results.Where(x => x.Count == secondIndex && x[x.Count - 1] < firstIndex).Select(x => x.ToList()).ToList();
|
||||
|
||||
foreach (var pathSofar in tempList)
|
||||
{
|
||||
pathSofar.Add(firstIndex);
|
||||
}
|
||||
|
||||
results.AddRange(tempList);
|
||||
}
|
||||
}
|
||||
|
||||
results = results.Where(x => x.Count == secondIndex + 1).ToList();
|
||||
}
|
||||
|
||||
return results.Where(x => x.Count == matches.GetLength(1)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for a string
|
||||
/// </summary>
|
||||
/// <param name="matches">the index of the matches</param>
|
||||
/// <returns>an integer representing the score</returns>
|
||||
internal static int CalculateScoreForMatches(List<int> matches)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(matches);
|
||||
|
||||
var score = 0;
|
||||
|
||||
for (var currentIndex = 1; currentIndex < matches.Count; currentIndex++)
|
||||
{
|
||||
var previousIndex = currentIndex - 1;
|
||||
|
||||
score -= matches[currentIndex] - matches[previousIndex];
|
||||
}
|
||||
|
||||
return score == 0 ? -10000 : score;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing methods to control the live preview
|
||||
/// </summary>
|
||||
internal sealed class LivePreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes sure that a window is excluded from the live preview
|
||||
/// </summary>
|
||||
/// <param name="hwnd">handle to the window to exclude</param>
|
||||
public static void SetWindowExclusionFromLivePreview(IntPtr hwnd)
|
||||
{
|
||||
var renderPolicy = (uint)DwmNCRenderingPolicies.Enabled;
|
||||
|
||||
_ = NativeMethods.DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
12,
|
||||
ref renderPolicy,
|
||||
sizeof(uint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the live preview
|
||||
/// </summary>
|
||||
/// <param name="targetWindow">the window to show by making all other windows transparent</param>
|
||||
/// <param name="windowToSpare">the window which should not be transparent but is not the target window</param>
|
||||
public static void ActivateLivePreview(IntPtr targetWindow, IntPtr windowToSpare)
|
||||
{
|
||||
_ = NativeMethods.DwmpActivateLivePreview(
|
||||
true,
|
||||
targetWindow,
|
||||
windowToSpare,
|
||||
LivePreviewTrigger.Superbar,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the live preview
|
||||
/// </summary>
|
||||
public static void DeactivateLivePreview()
|
||||
{
|
||||
_ = NativeMethods.DwmpActivateLivePreview(
|
||||
false,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
LivePreviewTrigger.AltTab,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Class that represents the state of the desktops windows
|
||||
/// </summary>
|
||||
internal sealed class OpenWindows
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to enforce single execution of EnumWindows
|
||||
/// </summary>
|
||||
private static readonly object _enumWindowsLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// PowerLauncher main executable
|
||||
/// </summary>
|
||||
private static readonly string? _powerLauncherExe = Path.GetFileName(Environment.ProcessPath);
|
||||
|
||||
/// <summary>
|
||||
/// List of all the open windows
|
||||
/// </summary>
|
||||
private readonly List<Window> windows = new();
|
||||
|
||||
/// <summary>
|
||||
/// An instance of the class OpenWindows
|
||||
/// </summary>
|
||||
private static OpenWindows? instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all open windows
|
||||
/// </summary>
|
||||
internal List<Window> Windows => new(windows);
|
||||
|
||||
/// <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
|
||||
/// </summary>
|
||||
internal static OpenWindows Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
instance ??= new OpenWindows();
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpenWindows"/> class.
|
||||
/// Private constructor to make sure there is never
|
||||
/// more than one instance of this class
|
||||
/// </summary>
|
||||
private OpenWindows()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the list of open windows
|
||||
/// </summary>
|
||||
internal void UpdateOpenWindowsList(CancellationToken cancellationToken)
|
||||
{
|
||||
var tokenHandle = GCHandle.Alloc(cancellationToken);
|
||||
try
|
||||
{
|
||||
var tokenHandleParam = GCHandle.ToIntPtr(tokenHandle);
|
||||
lock (_enumWindowsLock)
|
||||
{
|
||||
windows.Clear();
|
||||
EnumWindowsProc callbackptr = new EnumWindowsProc(WindowEnumerationCallBack);
|
||||
_ = NativeMethods.EnumWindows(callbackptr, tokenHandleParam);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (tokenHandle.IsAllocated)
|
||||
{
|
||||
tokenHandle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call back method for window enumeration
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the current window being enumerated</param>
|
||||
/// <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>
|
||||
internal bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
|
||||
{
|
||||
var tokenHandle = GCHandle.FromIntPtr(lParam);
|
||||
var target = (CancellationToken?)tokenHandle.Target ?? CancellationToken.None;
|
||||
var cancellationToken = target;
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Stop enumeration
|
||||
return false;
|
||||
}
|
||||
|
||||
Window newWindow = new Window(hwnd);
|
||||
|
||||
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
|
||||
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
|
||||
(newWindow.Desktop.IsVisible || !SettingsManager.Instance.ResultsFromVisibleDesktopOnly || WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.GetDesktopCount() < 2) &&
|
||||
newWindow.ClassName != "Windows.UI.Core.CoreWindow" && newWindow.Process.Name != _powerLauncherExe)
|
||||
{
|
||||
// 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,131 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.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<WindowWalkerListItem> GetResultList(List<SearchResult> searchControllerResults, bool isKeywordSearch, string icon, string infoIcon)
|
||||
{
|
||||
if (searchControllerResults == null || searchControllerResults.Count == 0)
|
||||
{
|
||||
return new List<WindowWalkerListItem>();
|
||||
}
|
||||
|
||||
List<WindowWalkerListItem> resultsList = new List<WindowWalkerListItem>(searchControllerResults.Count);
|
||||
var addExplorerInfo = searchControllerResults.Any(x =>
|
||||
string.Equals(x.Result.Process.Name, "explorer.exe", StringComparison.OrdinalIgnoreCase) &&
|
||||
x.Result.Process.IsShellProcess);
|
||||
|
||||
// Process each SearchResult to convert it into a Result.
|
||||
// Using parallel processing if the operation is CPU-bound and the list is large.
|
||||
resultsList = searchControllerResults
|
||||
.AsParallel()
|
||||
.Select(x => CreateResultFromSearchResult(x, icon))
|
||||
.ToList();
|
||||
|
||||
if (addExplorerInfo && !SettingsManager.Instance.HideExplorerSettingInfo)
|
||||
{
|
||||
resultsList.Insert(0, GetExplorerInfoResult(infoIcon));
|
||||
}
|
||||
|
||||
return resultsList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Result object from a given SearchResult.
|
||||
/// </summary>
|
||||
/// <param name="searchResult">The SearchResult object to convert.</param>
|
||||
/// <param name="icon">The path to the icon that should be used for the Result.</param>
|
||||
/// <returns>A Result object populated with data from the SearchResult.</returns>
|
||||
private static WindowWalkerListItem CreateResultFromSearchResult(SearchResult searchResult, string icon)
|
||||
{
|
||||
var item = new WindowWalkerListItem(searchResult.Result)
|
||||
{
|
||||
Title = searchResult.Result.Title,
|
||||
Icon = new(icon),
|
||||
Subtitle = GetSubtitle(searchResult.Result),
|
||||
Tags = GetTags(searchResult.Result),
|
||||
};
|
||||
item.MoreCommands = ContextMenuHelper.GetContextMenuResults(item).ToArray();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
var subtitleText = Resources.windowwalker_Running + ": " + window.Process.Name;
|
||||
|
||||
return subtitleText;
|
||||
}
|
||||
|
||||
private static Tag[] GetTags(Window window)
|
||||
{
|
||||
var tags = new List<Tag>();
|
||||
if (!window.Process.IsResponding)
|
||||
{
|
||||
tags.Add(new Tag
|
||||
{
|
||||
Text = Resources.windowwalker_NotResponding,
|
||||
Color = ColorHelpers.FromRgb(220, 20, 60),
|
||||
});
|
||||
}
|
||||
|
||||
if (SettingsManager.Instance.SubtitleShowPid)
|
||||
{
|
||||
tags.Add(new Tag
|
||||
{
|
||||
Text = $"{Resources.windowwalker_ProcessId}: {window.Process.ProcessID}",
|
||||
});
|
||||
}
|
||||
|
||||
if (SettingsManager.Instance.SubtitleShowDesktopName && WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
|
||||
{
|
||||
tags.Add(new Tag
|
||||
{
|
||||
Text = $"{Resources.windowwalker_Desktop}: {window.Desktop.Name}",
|
||||
});
|
||||
}
|
||||
|
||||
return tags.ToArray();
|
||||
}
|
||||
|
||||
private static WindowWalkerListItem GetExplorerInfoResult(string iIcon)
|
||||
{
|
||||
return new WindowWalkerListItem(null)
|
||||
{
|
||||
Title = Resources.windowwalker_ExplorerInfoTitle,
|
||||
Icon = new(iIcon),
|
||||
Subtitle = Resources.windowwalker_ExplorerInfoSubTitle,
|
||||
Command = new ExplorerInfoResultCommand(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for searching and finding matches for the strings provided.
|
||||
/// Essentially the UI independent model of the application
|
||||
/// </summary>
|
||||
internal sealed class SearchController
|
||||
{
|
||||
/// <summary>
|
||||
/// the current search text
|
||||
/// </summary>
|
||||
private string searchText;
|
||||
|
||||
/// <summary>
|
||||
/// Open window search results
|
||||
/// </summary>
|
||||
private List<SearchResult>? searchMatches;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton pattern
|
||||
/// </summary>
|
||||
private static SearchController? instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current search text
|
||||
/// </summary>
|
||||
internal string SearchText
|
||||
{
|
||||
get => searchText;
|
||||
|
||||
set =>
|
||||
searchText = value.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the open window search results
|
||||
/// </summary>
|
||||
internal List<SearchResult> SearchMatches => new List<SearchResult>(searchMatches ?? new List<SearchResult>()).OrderByDescending(x => x.Score).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets singleton Pattern
|
||||
/// </summary>
|
||||
internal static SearchController Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
instance ??= new SearchController();
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchController"/> class.
|
||||
/// Initializes the search controller object
|
||||
/// </summary>
|
||||
private SearchController()
|
||||
{
|
||||
searchText = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for when the search text has been updated
|
||||
/// </summary>
|
||||
internal void UpdateSearchText(string searchText)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SyncOpenWindowsWithModel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncs the open windows with the OpenWindows Model
|
||||
/// </summary>
|
||||
internal void SyncOpenWindowsWithModel()
|
||||
{
|
||||
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
|
||||
|
||||
List<Window> snapshotOfOpenWindows = OpenWindows.Instance.Windows;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
searchMatches = AllOpenWindows(snapshotOfOpenWindows);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchMatches = FuzzySearchOpenWindows(snapshotOfOpenWindows);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search method that matches the title of windows with the user search text
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private List<SearchResult> FuzzySearchOpenWindows(List<Window> openWindows)
|
||||
{
|
||||
List<SearchResult> result = new List<SearchResult>();
|
||||
var searchStrings = new SearchString(searchText, SearchResult.SearchType.Fuzzy);
|
||||
|
||||
foreach (var window in openWindows)
|
||||
{
|
||||
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchStrings.SearchText);
|
||||
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.Process.Name ?? string.Empty, searchStrings.SearchText);
|
||||
|
||||
if ((titleMatch.Count != 0 || processMatch.Count != 0) && window.Title.Length != 0)
|
||||
{
|
||||
result.Add(new SearchResult(window, titleMatch, processMatch, searchStrings.SearchType));
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Print("Found " + result.Count + " windows that match the search text");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search method that matches all the windows with a title
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private List<SearchResult> AllOpenWindows(List<Window> openWindows)
|
||||
{
|
||||
List<SearchResult> result = new List<SearchResult>();
|
||||
|
||||
foreach (var window in openWindows)
|
||||
{
|
||||
if (window.Title.Length != 0)
|
||||
{
|
||||
result.Add(new SearchResult(window));
|
||||
}
|
||||
}
|
||||
|
||||
return result.OrderBy(w => w.Result.Title).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for a window list update event
|
||||
/// </summary>
|
||||
internal sealed class SearchResultUpdateEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Contains search result windows with each window including the reason why the result was included
|
||||
/// </summary>
|
||||
internal sealed class SearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actual window reference for the search result
|
||||
/// </summary>
|
||||
internal Window Result
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the title window
|
||||
/// </summary>
|
||||
internal List<int> SearchMatchesInTitle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the
|
||||
/// name of the process
|
||||
/// </summary>
|
||||
internal List<int> SearchMatchesInProcessName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of match (shortcut, fuzzy or nothing)
|
||||
/// </summary>
|
||||
internal SearchType SearchResultMatchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a score indicating how well this matches what we are looking for
|
||||
/// </summary>
|
||||
internal int Score
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source of where the best score was found
|
||||
/// </summary>
|
||||
internal TextType BestScoreSource
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
internal SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = matchesInTitle;
|
||||
SearchMatchesInProcessName = matchesInProcessName;
|
||||
SearchResultMatchType = matchType;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// </summary>
|
||||
internal SearchResult(Window window)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = new List<int>();
|
||||
SearchMatchesInProcessName = new List<int>();
|
||||
SearchResultMatchType = SearchType.Empty;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for how closely this window matches the search string
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Higher Score is better
|
||||
/// </remarks>
|
||||
private void CalculateScore()
|
||||
{
|
||||
if (FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName) >
|
||||
FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle))
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName);
|
||||
BestScoreSource = TextType.ProcessName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle);
|
||||
BestScoreSource = TextType.WindowTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of text that a string represents
|
||||
/// </summary>
|
||||
internal enum TextType
|
||||
{
|
||||
ProcessName,
|
||||
WindowTitle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of search
|
||||
/// </summary>
|
||||
internal enum SearchType
|
||||
{
|
||||
/// <summary>
|
||||
/// the search string is empty, which means all open windows are
|
||||
/// going to be returned
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// Regular fuzzy match search
|
||||
/// </summary>
|
||||
Fuzzy,
|
||||
|
||||
/// <summary>
|
||||
/// The user has entered text that has been matched to a shortcut
|
||||
/// and the shortcut is now being searched
|
||||
/// </summary>
|
||||
Shortcut,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A class to represent a search string
|
||||
/// </summary>
|
||||
/// <remarks>Class was added inorder to be able to attach various context data to
|
||||
/// a search string</remarks>
|
||||
internal sealed class SearchString
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets where is the search string coming from (is it a shortcut
|
||||
/// or direct string, etc...)
|
||||
/// </summary>
|
||||
internal SearchResult.SearchType SearchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual text we are searching for
|
||||
/// </summary>
|
||||
internal string SearchText
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchString"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="searchText">text from search</param>
|
||||
/// <param name="searchType">type of search</param>
|
||||
internal SearchString(string searchText, SearchResult.SearchType searchType)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SearchType = searchType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a specific open window
|
||||
/// </summary>
|
||||
internal sealed class Window
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle to the window
|
||||
/// </summary>
|
||||
private readonly IntPtr hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// A static cache for the process data of all known windows
|
||||
/// that we don't have to query the data every time
|
||||
/// </summary>
|
||||
private static readonly Dictionary<IntPtr, WindowProcess> _handlesToProcessCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// An instance of <see cref="WindowProcess"/> that contains the process information for the window
|
||||
/// </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>
|
||||
internal string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
var sizeOfTitle = NativeMethods.GetWindowTextLength(hwnd);
|
||||
if (sizeOfTitle++ > 0)
|
||||
{
|
||||
StringBuilder titleBuffer = new StringBuilder(sizeOfTitle);
|
||||
var numCharactersWritten = NativeMethods.GetWindowText(hwnd, titleBuffer, sizeOfTitle);
|
||||
if (numCharactersWritten == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return titleBuffer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the window
|
||||
/// </summary>
|
||||
internal IntPtr Hwnd => hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of with the process information of the window
|
||||
/// </summary>
|
||||
internal WindowProcess Process => processInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of with the desktop information of the window
|
||||
/// </summary>
|
||||
internal VDesktop Desktop => desktopInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the class for the window represented
|
||||
/// </summary>
|
||||
internal string ClassName => GetWindowClassName(Hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is visible (might return false if it is a hidden IE tab)
|
||||
/// </summary>
|
||||
internal bool Visible => NativeMethods.IsWindowVisible(Hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
internal bool IsCloaked => GetWindowCloakState() != WindowCloakState.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified window handle identifies an existing window.
|
||||
/// </summary>
|
||||
internal bool IsWindow => NativeMethods.IsWindow(Hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is a toolwindow
|
||||
/// </summary>
|
||||
internal bool IsToolWindow => (NativeMethods.GetWindowLong(Hwnd, Win32Constants.GWL_EXSTYLE) &
|
||||
(uint)ExtendedWindowStyles.WS_EX_TOOLWINDOW) ==
|
||||
(uint)ExtendedWindowStyles.WS_EX_TOOLWINDOW;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is an appwindow
|
||||
/// </summary>
|
||||
internal bool IsAppWindow => (NativeMethods.GetWindowLong(Hwnd, Win32Constants.GWL_EXSTYLE) &
|
||||
(uint)ExtendedWindowStyles.WS_EX_APPWINDOW) ==
|
||||
(uint)ExtendedWindowStyles.WS_EX_APPWINDOW;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window has ITaskList_Deleted property
|
||||
/// </summary>
|
||||
internal bool TaskListDeleted => NativeMethods.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified windows is the owner (i.e. doesn't have an owner)
|
||||
/// </summary>
|
||||
internal bool IsOwner => NativeMethods.GetWindow(Hwnd, GetWindowCmd.GW_OWNER) == IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is minimized
|
||||
/// </summary>
|
||||
internal bool Minimized => GetWindowSizeState() == WindowSizeState.Minimized;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Window"/> class.
|
||||
/// Initializes a new Window representation
|
||||
/// </summary>
|
||||
/// <param name="hwnd">the handle to the window we are representing</param>
|
||||
internal Window(IntPtr hwnd)
|
||||
{
|
||||
// TODO: Add verification as to whether the window handle is valid
|
||||
this.hwnd = hwnd;
|
||||
processInfo = CreateWindowProcessInstance(hwnd);
|
||||
desktopInfo = WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.GetWindowDesktop(hwnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches desktop focus to the window
|
||||
/// </summary>
|
||||
internal void SwitchToWindow()
|
||||
{
|
||||
// The following block is necessary because
|
||||
// 1) There is a weird flashing behavior when trying
|
||||
// to use ShowWindow for switching tabs in IE
|
||||
// 2) SetForegroundWindow fails on minimized windows
|
||||
// Using Ordinal since this is internal
|
||||
if (processInfo.Name?.ToUpperInvariant().Equals("IEXPLORE.EXE", StringComparison.Ordinal) == true || !Minimized)
|
||||
{
|
||||
NativeMethods.SetForegroundWindow(Hwnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!NativeMethods.ShowWindow(Hwnd, ShowWindowCommand.Restore))
|
||||
{
|
||||
// ShowWindow doesn't work if the process is running elevated: fallback to SendMessage
|
||||
_ = NativeMethods.SendMessage(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_RESTORE);
|
||||
}
|
||||
}
|
||||
|
||||
NativeMethods.FlashWindow(Hwnd, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to close the window
|
||||
/// </summary>
|
||||
internal void CloseThisWindowHelper()
|
||||
{
|
||||
_ = NativeMethods.SendMessageTimeout(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_CLOSE, 0, 0x0000, 5000, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the window
|
||||
/// </summary>
|
||||
internal void CloseThisWindow()
|
||||
{
|
||||
Thread thread = new(new ThreadStart(CloseThisWindowHelper));
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the window name to string along with the process name
|
||||
/// </summary>
|
||||
/// <returns>The title of the window</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
// Using CurrentCulture since this is user facing
|
||||
return Title + " (" + processInfo.Name?.ToUpper(CultureInfo.CurrentCulture) + ")";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns what the window size is
|
||||
/// </summary>
|
||||
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
|
||||
internal WindowSizeState GetWindowSizeState()
|
||||
{
|
||||
NativeMethods.GetWindowPlacement(Hwnd, out WINDOWPLACEMENT placement);
|
||||
|
||||
switch (placement.ShowCmd)
|
||||
{
|
||||
case ShowWindowCommand.Normal:
|
||||
return WindowSizeState.Normal;
|
||||
case ShowWindowCommand.Minimize:
|
||||
case ShowWindowCommand.ShowMinimized:
|
||||
return WindowSizeState.Minimized;
|
||||
case ShowWindowCommand.Maximize: // No need for ShowMaximized here since its also of value 3
|
||||
return WindowSizeState.Maximized;
|
||||
default:
|
||||
// throw new Exception("Don't know how to handle window state = " + placement.ShowCmd);
|
||||
return WindowSizeState.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to simplify the state of the window
|
||||
/// </summary>
|
||||
internal enum WindowSizeState
|
||||
{
|
||||
Normal,
|
||||
Minimized,
|
||||
Maximized,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the window cloak state from DWM
|
||||
/// (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>
|
||||
internal WindowCloakState GetWindowCloakState()
|
||||
{
|
||||
_ = NativeMethods.DwmGetWindowAttribute(Hwnd, (int)DwmWindowAttributes.Cloaked, out var isCloakedState, sizeof(uint));
|
||||
|
||||
switch (isCloakedState)
|
||||
{
|
||||
case (int)DwmWindowCloakStates.None:
|
||||
return WindowCloakState.None;
|
||||
case (int)DwmWindowCloakStates.CloakedApp:
|
||||
return WindowCloakState.App;
|
||||
case (int)DwmWindowCloakStates.CloakedShell:
|
||||
return WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.IsWindowCloakedByVirtualDesktopManager(hwnd, Desktop.Id) ? WindowCloakState.OtherDesktop : WindowCloakState.Shell;
|
||||
case (int)DwmWindowCloakStates.CloakedInherited:
|
||||
return WindowCloakState.Inherited;
|
||||
default:
|
||||
return WindowCloakState.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to simplify the cloak state of the window
|
||||
/// </summary>
|
||||
internal enum WindowCloakState
|
||||
{
|
||||
None,
|
||||
App,
|
||||
Shell,
|
||||
Inherited,
|
||||
OtherDesktop,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the class name of a window.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">Handle to the window.</param>
|
||||
/// <returns>Class name</returns>
|
||||
private static string GetWindowClassName(IntPtr hwnd)
|
||||
{
|
||||
StringBuilder windowClassName = new StringBuilder(300);
|
||||
var numCharactersWritten = NativeMethods.GetClassName(hwnd, windowClassName, windowClassName.MaxCapacity);
|
||||
|
||||
if (numCharactersWritten == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return windowClassName.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of <see cref="WindowProcess"/> form process cache or creates a new one. A new one will be added to the cache.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">The handle to the window</param>
|
||||
/// <returns>A new Instance of type <see cref="WindowProcess"/></returns>
|
||||
private static WindowProcess CreateWindowProcessInstance(IntPtr hWindow)
|
||||
{
|
||||
lock (_handlesToProcessCache)
|
||||
{
|
||||
if (_handlesToProcessCache.Count > 7000)
|
||||
{
|
||||
Debug.Print("Clearing Process Cache because it's size is " + _handlesToProcessCache.Count);
|
||||
_handlesToProcessCache.Clear();
|
||||
}
|
||||
|
||||
// Add window's process to cache if missing
|
||||
if (!_handlesToProcessCache.ContainsKey(hWindow))
|
||||
{
|
||||
// Get process ID and name
|
||||
var processId = WindowProcess.GetProcessIDFromWindowHandle(hWindow);
|
||||
var threadId = WindowProcess.GetThreadIDFromWindowHandle(hWindow);
|
||||
var processName = WindowProcess.GetProcessNameFromProcessID(processId);
|
||||
|
||||
if (processName.Length != 0)
|
||||
{
|
||||
_handlesToProcessCache.Add(hWindow, new WindowProcess(processId, threadId, processName));
|
||||
}
|
||||
else
|
||||
{
|
||||
// For the dwm process we can not receive the name. This is no problem because the window isn't part of result list.
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Invalid process {processId} ({processName}) for window handle {hWindow}." });
|
||||
_handlesToProcessCache.Add(hWindow, new WindowProcess(0, 0, string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
// Correct the process data if the window belongs to a uwp app hosted by 'ApplicationFrameHost.exe'
|
||||
// (This only works if the window isn't minimized. For minimized windows the required child window isn't assigned.)
|
||||
if (string.Equals(_handlesToProcessCache[hWindow].Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
new Task(() =>
|
||||
{
|
||||
EnumWindowsProc callbackptr = new EnumWindowsProc((IntPtr hwnd, IntPtr lParam) =>
|
||||
{
|
||||
// Every uwp app main window has at least three child windows. Only the one we are interested in has a class starting with "Windows.UI.Core." and is assigned to the real app process.
|
||||
// (The other ones have a class name that begins with the string "ApplicationFrame".)
|
||||
if (GetWindowClassName(hwnd).StartsWith("Windows.UI.Core.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var childProcessId = WindowProcess.GetProcessIDFromWindowHandle(hwnd);
|
||||
var childThreadId = WindowProcess.GetThreadIDFromWindowHandle(hwnd);
|
||||
var childProcessName = WindowProcess.GetProcessNameFromProcessID(childProcessId);
|
||||
|
||||
// Update process info in cache
|
||||
_handlesToProcessCache[hWindow].UpdateProcessInfo(childProcessId, childThreadId, childProcessName);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
_ = NativeMethods.EnumChildWindows(hWindow, callbackptr, 0);
|
||||
}).Start();
|
||||
}
|
||||
|
||||
return _handlesToProcessCache[hWindow];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.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>
|
||||
internal sealed class WindowProcess
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum size of a file name
|
||||
/// </summary>
|
||||
private const int MaximumFileNameLength = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// An indicator if the window belongs to an 'Universal Windows Platform (UWP)' process
|
||||
/// </summary>
|
||||
private readonly bool _isUwpApp;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the id of the process
|
||||
/// </summary>
|
||||
internal uint ProcessID
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the process is responding or not
|
||||
/// </summary>
|
||||
internal bool IsResponding
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return Process.GetProcessById((int)ProcessID).Responding;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Thrown when process not exist.
|
||||
return true;
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// Thrown when process is not running locally.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the id of the thread
|
||||
/// </summary>
|
||||
internal uint ThreadID
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the process
|
||||
/// </summary>
|
||||
internal string? Name
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window belongs to an 'Universal Windows Platform (UWP)' process
|
||||
/// </summary>
|
||||
internal bool IsUwpApp => _isUwpApp;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
internal bool IsShellProcess
|
||||
{
|
||||
get
|
||||
{
|
||||
var hShellWindow = NativeMethods.GetShellWindow();
|
||||
return GetProcessIDFromWindowHandle(hShellWindow) == ProcessID;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the process exists on the machine
|
||||
/// </summary>
|
||||
internal bool DoesExist
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = Process.GetProcessById((int)ProcessID);
|
||||
p.Dispose();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Thrown when process not exist.
|
||||
return false;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Thrown when process not exist.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether full access to the process is denied or not
|
||||
/// </summary>
|
||||
internal bool IsFullAccessDenied
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowProcess"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pid">New process id.</param>
|
||||
/// <param name="tid">New thread id.</param>
|
||||
/// <param name="name">New process name.</param>
|
||||
internal WindowProcess(uint pid, uint tid, string name)
|
||||
{
|
||||
UpdateProcessInfo(pid, tid, name);
|
||||
_isUwpApp = string.Equals(Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the process information of the <see cref="WindowProcess"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="pid">New process id.</param>
|
||||
/// <param name="tid">New thread id.</param>
|
||||
/// <param name="name">New process name.</param>
|
||||
internal void UpdateProcessInfo(uint pid, uint tid, string name)
|
||||
{
|
||||
// TODO: Add verification as to whether the process id and thread id is valid
|
||||
ProcessID = pid;
|
||||
ThreadID = tid;
|
||||
Name = name;
|
||||
|
||||
// Process can be elevated only if process id is not 0 (Dummy value on error)
|
||||
IsFullAccessDenied = (pid != 0) ? TestProcessAccessUsingAllAccessFlag(pid) : false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process ID for the window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The process ID</returns>
|
||||
internal static uint GetProcessIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
_ = NativeMethods.GetWindowThreadProcessId(hwnd, out var processId);
|
||||
return processId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the thread ID for the window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The thread ID</returns>
|
||||
internal static uint GetThreadIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
var threadId = NativeMethods.GetWindowThreadProcessId(hwnd, out _);
|
||||
return threadId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process name for the process ID
|
||||
/// </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>
|
||||
internal static string GetProcessNameFromProcessID(uint pid)
|
||||
{
|
||||
var processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryLimitedInformation, true, (int)pid);
|
||||
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
|
||||
|
||||
if (NativeMethods.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0)
|
||||
{
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return processName.ToString().Split('\\').Reverse().ToArray()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var killTree = killProcessTree ? " /t" : string.Empty;
|
||||
ExplorerInfoResultCommand.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, ExplorerInfoResultCommand.ShellRunAsType.Administrator, 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>
|
||||
/// <param name="pid">The process ID of the process</param>
|
||||
/// <returns>True if denied and false if not.</returns>
|
||||
private static bool TestProcessAccessUsingAllAccessFlag(uint pid)
|
||||
{
|
||||
var processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.AllAccess, true, (int)pid);
|
||||
|
||||
if (Win32Helpers.GetLastError() == 5)
|
||||
{
|
||||
// Error 5 = ERROR_ACCESS_DENIED
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Virtual Desktop Manager class
|
||||
/// Code used from <see href="https://learn.microsoft.com/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10"./>
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
|
||||
internal class CVirtualDesktopManager
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for accessing Virtual Desktop Manager.
|
||||
/// Code used from <see href="https://learn.microsoft.com/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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public static class OSVersionHelper
|
||||
{
|
||||
public static bool IsWindows11()
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 22000;
|
||||
}
|
||||
|
||||
public static bool IsGreaterThanWindows11_21H2()
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build > 22000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public class SettingsManager
|
||||
{
|
||||
private readonly string _filePath;
|
||||
private readonly Settings _settings = new();
|
||||
|
||||
private static SettingsManager? instance;
|
||||
|
||||
private readonly ToggleSetting _resultsFromVisibleDesktopOnly = new(
|
||||
nameof(ResultsFromVisibleDesktopOnly),
|
||||
Resources.windowwalker_SettingResultsVisibleDesktop,
|
||||
Resources.windowwalker_SettingResultsVisibleDesktop,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _subtitleShowPid = new(
|
||||
nameof(SubtitleShowPid),
|
||||
Resources.windowwalker_SettingTagPid,
|
||||
Resources.windowwalker_SettingTagPid,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _subtitleShowDesktopName = new(
|
||||
nameof(SubtitleShowDesktopName),
|
||||
Resources.windowwalker_SettingTagDesktopName,
|
||||
Resources.windowwalker_SettingSubtitleDesktopName_Description,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _confirmKillProcess = new(
|
||||
nameof(ConfirmKillProcess),
|
||||
Resources.windowwalker_SettingConfirmKillProcess,
|
||||
Resources.windowwalker_SettingConfirmKillProcess,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _killProcessTree = new(
|
||||
nameof(KillProcessTree),
|
||||
Resources.windowwalker_SettingKillProcessTree,
|
||||
Resources.windowwalker_SettingKillProcessTree_Description,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _openAfterKillAndClose = new(
|
||||
nameof(OpenAfterKillAndClose),
|
||||
Resources.windowwalker_SettingOpenAfterKillAndClose,
|
||||
Resources.windowwalker_SettingOpenAfterKillAndClose_Description,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _hideKillProcessOnElevatedProcesses = new(
|
||||
nameof(HideKillProcessOnElevatedProcesses),
|
||||
Resources.windowwalker_SettingHideKillProcess,
|
||||
Resources.windowwalker_SettingHideKillProcess,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _hideExplorerSettingInfo = new(
|
||||
nameof(HideExplorerSettingInfo),
|
||||
Resources.windowwalker_SettingExplorerSettingInfo,
|
||||
Resources.windowwalker_SettingExplorerSettingInfo_Description,
|
||||
false);
|
||||
|
||||
public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value;
|
||||
|
||||
public bool SubtitleShowPid => _subtitleShowPid.Value;
|
||||
|
||||
public bool SubtitleShowDesktopName => _subtitleShowDesktopName.Value;
|
||||
|
||||
public bool ConfirmKillProcess => _confirmKillProcess.Value;
|
||||
|
||||
public bool KillProcessTree => _killProcessTree.Value;
|
||||
|
||||
public bool OpenAfterKillAndClose => _openAfterKillAndClose.Value;
|
||||
|
||||
public bool HideKillProcessOnElevatedProcesses => _hideKillProcessOnElevatedProcesses.Value;
|
||||
|
||||
public bool HideExplorerSettingInfo => _hideExplorerSettingInfo.Value;
|
||||
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "window_walker_state.json");
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
_filePath = SettingsJsonPath();
|
||||
|
||||
_settings.Add(_resultsFromVisibleDesktopOnly);
|
||||
_settings.Add(_subtitleShowPid);
|
||||
_settings.Add(_subtitleShowDesktopName);
|
||||
_settings.Add(_confirmKillProcess);
|
||||
_settings.Add(_killProcessTree);
|
||||
_settings.Add(_openAfterKillAndClose);
|
||||
_settings.Add(_hideKillProcessOnElevatedProcesses);
|
||||
_settings.Add(_hideExplorerSettingInfo);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
internal static SettingsManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
instance ??= new SettingsManager();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public Settings GetSettings()
|
||||
{
|
||||
return _settings;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Serialize the main dictionary to JSON and save it to the file
|
||||
var settingsJson = _settings.ToJson();
|
||||
|
||||
File.WriteAllText(_filePath, settingsJson);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "The provided settings file does not exist" });
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Read the JSON content from the file
|
||||
var jsonContent = File.ReadAllText(_filePath);
|
||||
|
||||
// Is it valid JSON?
|
||||
if (JsonNode.Parse(jsonContent) is JsonObject savedSettings)
|
||||
{
|
||||
_settings.Update(jsonContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "Failed to parse settings file as JsonObject." });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public static class ShellCommand
|
||||
{
|
||||
public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
|
||||
|
||||
private static bool containsSecurityWindow;
|
||||
|
||||
public static Process? RunAsDifferentUser(ProcessStartInfo processStartInfo)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(processStartInfo);
|
||||
|
||||
processStartInfo.Verb = "RunAsUser";
|
||||
var process = Process.Start(processStartInfo);
|
||||
|
||||
containsSecurityWindow = false;
|
||||
|
||||
// wait for windows to bring up the "Windows Security" dialog
|
||||
while (!containsSecurityWindow)
|
||||
{
|
||||
CheckSecurityWindow();
|
||||
Thread.Sleep(25);
|
||||
}
|
||||
|
||||
// while this process contains a "Windows Security" dialog, stay open
|
||||
while (containsSecurityWindow)
|
||||
{
|
||||
containsSecurityWindow = false;
|
||||
CheckSecurityWindow();
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
private static void CheckSecurityWindow()
|
||||
{
|
||||
ProcessThreadCollection ptc = Process.GetCurrentProcess().Threads;
|
||||
for (var i = 0; i < ptc.Count; i++)
|
||||
{
|
||||
NativeMethods.EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CheckSecurityThread(IntPtr hwnd, IntPtr lParam)
|
||||
{
|
||||
if (GetWindowTitle(hwnd) == "Windows Security")
|
||||
{
|
||||
containsSecurityWindow = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string GetWindowTitle(IntPtr hwnd)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(NativeMethods.GetWindowTextLength(hwnd) + 1);
|
||||
_ = NativeMethods.GetWindowText(hwnd, sb, sb.Capacity);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "")
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
WorkingDirectory = workingDirectory,
|
||||
Arguments = arguments,
|
||||
Verb = verb,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <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 => new()
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Name = string.Empty,
|
||||
Number = 0,
|
||||
IsVisible = true, // Setting this always to true to simulate a visible desktop
|
||||
IsAllDesktopsView = false,
|
||||
Position = VirtualDesktopPosition.NotApplicable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
// 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 System.Text;
|
||||
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to work with Virtual Desktops.
|
||||
/// This helper uses only public available and documented COM-Interfaces or information 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://learn.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ivirtualdesktopmanager">Documentation of IVirtualDesktopManager interface</SeeAlso>
|
||||
/// <SeeAlso href="https://learn.microsoft.com/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10">CSharp example code for IVirtualDesktopManager</SeeAlso>
|
||||
public class VirtualDesktopHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Are we running on Windows 11
|
||||
/// </summary>
|
||||
private readonly bool _isWindowsEleven;
|
||||
|
||||
/// <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 readonly List<Guid> _availableDesktops = [];
|
||||
|
||||
/// <summary>
|
||||
/// Id of the current visible Desktop.
|
||||
/// </summary>
|
||||
private Guid _currentDesktop;
|
||||
|
||||
private static readonly CompositeFormat VirtualDesktopHelperDesktop = System.Text.CompositeFormat.Parse(Properties.Resources.VirtualDesktopHelper_Desktop);
|
||||
|
||||
/// <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)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Initialization of <VirtualDesktopHelper> failed: An exception was thrown when creating the instance of COM interface <IVirtualDesktopManager>. {ex} " });
|
||||
return;
|
||||
}
|
||||
|
||||
_isWindowsEleven = OSVersionHelper.IsWindows11();
|
||||
_desktopListAutoUpdate = desktopListUpdate;
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Virtual Desktop Manager is initialized successfully
|
||||
/// </summary>
|
||||
public bool VirtualDesktopManagerInitialized => _virtualDesktopManager != null;
|
||||
|
||||
/// <summary>
|
||||
/// Method to update the list of Virtual Desktops from Registry
|
||||
/// The data in the registry are always up to date
|
||||
/// </summary>
|
||||
/// <remarks>If we can not read from registry, we set the list/guid to empty values.</remarks>
|
||||
public void UpdateDesktopList()
|
||||
{
|
||||
var userSessionId = Process.GetCurrentProcess().SessionId;
|
||||
var registrySessionVirtualDesktops = $"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SessionInfo\\{userSessionId}\\VirtualDesktops"; // Windows 10
|
||||
var registryExplorerVirtualDesktops = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops"; // Windows 11
|
||||
|
||||
// List of all desktops
|
||||
using RegistryKey? virtualDesktopKey = Registry.CurrentUser.OpenSubKey(registryExplorerVirtualDesktops, false);
|
||||
if (virtualDesktopKey != null)
|
||||
{
|
||||
var allDeskValue = (byte[]?)virtualDesktopKey.GetValue("VirtualDesktopIDs", null) ?? Array.Empty<byte>();
|
||||
if (allDeskValue != null)
|
||||
{
|
||||
// We clear only, if we can read from registry. Otherwise we keep the existing values.
|
||||
_availableDesktops.Clear();
|
||||
|
||||
// Each guid has a length of 16 elements
|
||||
var numberOfDesktops = allDeskValue.Length / 16;
|
||||
for (var i = 0; i < numberOfDesktops; i++)
|
||||
{
|
||||
var guidArray = new byte[16];
|
||||
Array.ConstrainedCopy(allDeskValue, i * 16, guidArray, 0, 16);
|
||||
_availableDesktops.Add(new Guid(guidArray));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.UpdateDesktopList() failed to read the list of existing desktops form registry." });
|
||||
}
|
||||
}
|
||||
|
||||
// Guid for current desktop
|
||||
var virtualDesktopsKeyName = _isWindowsEleven ? registryExplorerVirtualDesktops : registrySessionVirtualDesktops;
|
||||
using RegistryKey? virtualDesktopsKey = Registry.CurrentUser.OpenSubKey(virtualDesktopsKeyName, false);
|
||||
if (virtualDesktopsKey != null)
|
||||
{
|
||||
var currentVirtualDesktopValue = virtualDesktopsKey.GetValue("CurrentVirtualDesktop", null);
|
||||
if (currentVirtualDesktopValue != null)
|
||||
{
|
||||
_currentDesktop = new Guid((byte[])currentVirtualDesktopValue);
|
||||
}
|
||||
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.
|
||||
// We can only set it to desktop one, if we have at least one desktop in the desktops list. Otherwise we keep the existing value.
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.UpdateDesktopList() failed to read the id for the current desktop form registry." });
|
||||
_currentDesktop = _availableDesktops.Count >= 1 ? _availableDesktops[0] : _currentDesktop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>The <see cref="Guid"/> of the current desktop. Or <see cref="Guid.Empty"/> on failure and if we don't know the current desktop.</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><see langword="True"/> if the guid belongs to the currently visible desktop. <see langword="False"/> if not or if we don't know the 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 <see cref="string.Empty"/> on failure.</returns>
|
||||
public string GetDesktopName(Guid desktop)
|
||||
{
|
||||
if (desktop == Guid.Empty || !GetDesktopIdList().Contains(desktop))
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.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." });
|
||||
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(System.Globalization.CultureInfo.InvariantCulture, VirtualDesktopHelperDesktop, GetDesktopNumber(desktop));
|
||||
|
||||
var registryPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops\\Desktops\\{" + desktop.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "}";
|
||||
using 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 <see cref="VirtualDesktopPosition"/>. On failure we return <see cref="VirtualDesktopPosition.Unknown"/>.</returns>
|
||||
public VirtualDesktopPosition GetDesktopPositionType(Guid desktop)
|
||||
{
|
||||
var desktopNumber = GetDesktopNumber(desktop);
|
||||
var desktopCount = GetDesktopCount();
|
||||
|
||||
if (desktopCount == 0 || desktop == Guid.Empty)
|
||||
{
|
||||
// On failure or empty guid
|
||||
return VirtualDesktopPosition.NotApplicable;
|
||||
}
|
||||
else if (desktopNumber == 1)
|
||||
{
|
||||
return VirtualDesktopPosition.FirstDesktop;
|
||||
}
|
||||
else if (desktopNumber == desktopCount)
|
||||
{
|
||||
return VirtualDesktopPosition.LastDesktop;
|
||||
}
|
||||
else if (desktopNumber > 1 & desktopNumber < desktopCount)
|
||||
{
|
||||
return VirtualDesktopPosition.BetweenOtherDesktops;
|
||||
}
|
||||
else
|
||||
{
|
||||
// All desktops view or a guid that doesn't belong to an existing desktop
|
||||
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 as integer.</returns>
|
||||
public int GetWindowDesktopId(IntPtr hWindow, out Guid desktopId)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktopId() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
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)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktop() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
return CreateVDesktopInstance(Guid.Empty);
|
||||
}
|
||||
|
||||
var 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>
|
||||
/// <param name="desktop">Optional the desktop id if known</param>
|
||||
/// <returns>Type of <see cref="VirtualDesktopAssignmentType"/>.</returns>
|
||||
public VirtualDesktopAssignmentType GetWindowDesktopAssignmentType(IntPtr hWindow, Guid? desktop = null)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktopAssignmentType() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
return VirtualDesktopAssignmentType.Unknown;
|
||||
}
|
||||
|
||||
_ = _virtualDesktopManager.IsWindowOnCurrentVirtualDesktop(hWindow, out var isOnCurrentDesktop);
|
||||
Guid windowDesktopId = desktop ?? Guid.Empty; // Prepare variable in case we have no input parameter for desktop
|
||||
var hResult = desktop is null ? GetWindowDesktopId(hWindow, out windowDesktopId) : 0;
|
||||
|
||||
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>
|
||||
/// <param name="desktop">Optional the desktop id if known</param>
|
||||
/// <returns><see langword="True"/> if the desktop with the window is visible or if the window is assigned to all desktops. <see langword="False"/> if the desktop is not visible and on failure,</returns>
|
||||
public bool IsWindowOnVisibleDesktop(IntPtr hWindow, Guid? desktop = null)
|
||||
{
|
||||
return GetWindowDesktopAssignmentType(hWindow, desktop) == VirtualDesktopAssignmentType.CurrentDesktop || GetWindowDesktopAssignmentType(hWindow, desktop) == 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>
|
||||
/// <param name="desktop">Optional the desktop id if known</param>
|
||||
/// <returns>A value indicating if the window is cloaked by Virtual Desktop Manager, because it is moved to another desktop.</returns>
|
||||
public bool IsWindowCloakedByVirtualDesktopManager(IntPtr hWindow, Guid? desktop = null)
|
||||
{
|
||||
// If a window is hidden because it is moved to another desktop, then DWM returns type "CloakedShell". If DWM returns another type the window is not cloaked by shell or VirtualDesktopManager.
|
||||
_ = NativeMethods.DwmGetWindowAttribute(hWindow, (int)DwmWindowAttributes.Cloaked, out var dwmCloakedState, sizeof(uint));
|
||||
return GetWindowDesktopAssignmentType(hWindow, desktop) == 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><see langword="True"/> on success and <see langword="false"/> on failure.</returns>
|
||||
public bool MoveWindowToDesktop(IntPtr hWindow, in Guid desktopId)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.MoveWindowToDesktop() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
return false;
|
||||
}
|
||||
|
||||
var hr = _virtualDesktopManager.MoveWindowToDesktop(hWindow, desktopId);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.MoveWindowToDesktop() failed: An exception was thrown when moving the window ({hWindow}) to another desktop ({desktopId})." });
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a window one desktop left.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the top level window.</param>
|
||||
/// <returns><see langword="True"/> on success and <see langword="false"/> on failure.</returns>
|
||||
public bool MoveWindowOneDesktopLeft(IntPtr hWindow)
|
||||
{
|
||||
var hr = GetWindowDesktopId(hWindow, out Guid windowDesktop);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopLeft() failed when moving the window ({hWindow}) one desktop left: Can't get current desktop of the window." });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetDesktopIdList().Count == 0 || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.Unknown || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.NotAssigned)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopLeft() failed when moving the window ({hWindow}) one desktop left: We can't find the target desktop. This can happen if the desktop list is empty or if the window isn't assigned to a specific desktop." });
|
||||
return false;
|
||||
}
|
||||
|
||||
var windowDesktopNumber = GetDesktopIdList().IndexOf(windowDesktop);
|
||||
if (windowDesktopNumber == 1)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopLeft() failed when moving the window ({hWindow}) one desktop left: The window is on the first desktop." });
|
||||
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><see langword="True"/> on success and <see langword="false"/> on failure.</returns>
|
||||
public bool MoveWindowOneDesktopRight(IntPtr hWindow)
|
||||
{
|
||||
var hr = GetWindowDesktopId(hWindow, out Guid windowDesktop);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopRight() failed when moving the window ({hWindow}) one desktop right: Can't get current desktop of the window." });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetDesktopIdList().Count == 0 || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.Unknown || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.NotAssigned)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopRight() failed when moving the window ({hWindow}) one desktop right: We can't find the target desktop. This can happen if the desktop list is empty or if the window isn't assigned to a specific desktop." });
|
||||
return false;
|
||||
}
|
||||
|
||||
var windowDesktopNumber = GetDesktopIdList().IndexOf(windowDesktop);
|
||||
if (windowDesktopNumber == GetDesktopCount())
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopRight() failed when moving the window ({hWindow}) one desktop right: The window is on the last desktop." });
|
||||
return false;
|
||||
}
|
||||
|
||||
Guid newDesktop = _availableDesktops[windowDesktopNumber + 1];
|
||||
return MoveWindowToDesktop(hWindow, newDesktop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of <see cref="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>A <see cref="VDesktop"/> instance. If the parameter desktop is <see cref="Guid.Empty"/>, we return an empty <see cref="VDesktop"/> instance.</returns>
|
||||
private VDesktop CreateVDesktopInstance(Guid desktop, IntPtr hWindow = default)
|
||||
{
|
||||
if (desktop == Guid.Empty)
|
||||
{
|
||||
return VDesktop.Empty;
|
||||
}
|
||||
|
||||
// Can be only detected if method is invoked with window handle parameter.
|
||||
VirtualDesktopAssignmentType desktopType = (hWindow != default) ? GetWindowDesktopAssignmentType(hWindow, desktop) : VirtualDesktopAssignmentType.Unknown;
|
||||
var isAllDesktops = (hWindow != default) && desktopType == VirtualDesktopAssignmentType.AllDesktops;
|
||||
var isDesktopVisible = (hWindow != default) ? (isAllDesktops || desktopType == VirtualDesktopAssignmentType.CurrentDesktop) : IsDesktopVisible(desktop);
|
||||
|
||||
return new VDesktop()
|
||||
{
|
||||
Id = desktop,
|
||||
Name = isAllDesktops ? Resources.VirtualDesktopHelper_AllDesktops : GetDesktopName(desktop),
|
||||
Number = GetDesktopNumber(desktop),
|
||||
IsVisible = isDesktopVisible || 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, // If not applicable or unknown
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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 Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public static class Win32Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects the type of system firmware which is equal to the boot type by calling the method <see cref="NativeMethods.GetFirmwareType"/>.
|
||||
/// </summary>
|
||||
/// <returns>Firmware type like Uefi or Bios.</returns>
|
||||
public static FirmwareType GetSystemFirmwareType()
|
||||
{
|
||||
FirmwareType firmwareType = default;
|
||||
_ = NativeMethods.GetFirmwareType(ref firmwareType);
|
||||
return firmwareType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last Win32 Error code thrown by a native method if enabled for this method.
|
||||
/// </summary>
|
||||
/// <returns>The error code as int value.</returns>
|
||||
public static int GetLastError()
|
||||
{
|
||||
return Marshal.GetLastPInvokeError();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the handle is not null and close it.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle to close.</param>
|
||||
/// <returns>Zero if native method fails and nonzero if the native method succeeds.</returns>
|
||||
public static bool CloseHandleIfNotNull(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
// Return true if there is nothing to close.
|
||||
return true;
|
||||
}
|
||||
|
||||
return NativeMethods.CloseHandle(handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description for an HRESULT error code.
|
||||
/// </summary>
|
||||
/// <param name="hr">The HRESULT number</param>
|
||||
/// <returns>A string containing the description.</returns>
|
||||
public static string MessageFromHResult(int hr)
|
||||
{
|
||||
return Marshal.GetExceptionForHR(hr)?.Message ?? string.Empty;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 761 B |
Binary file not shown.
|
After Width: | Height: | Size: 710 B |
Binary file not shown.
|
After Width: | Height: | Size: 564 B |
Binary file not shown.
|
After Width: | Height: | Size: 536 B |
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.WindowWalker</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CmdPal.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Images\windowwalker.dark.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\windowwalker.light.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Pages;
|
||||
|
||||
internal sealed partial class SettingsPage : FormPage
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly SettingsManager _settingsManager;
|
||||
|
||||
public override IForm[] Forms()
|
||||
{
|
||||
var s = _settings.ToForms();
|
||||
return s;
|
||||
}
|
||||
|
||||
public SettingsPage()
|
||||
{
|
||||
Name = Resources.windowwalker_settings_name;
|
||||
Icon = new("\uE713"); // Settings icon
|
||||
_settingsManager = SettingsManager.Instance;
|
||||
_settings = _settingsManager.GetSettings();
|
||||
|
||||
_settings.SettingsChanged += SettingsChanged;
|
||||
}
|
||||
|
||||
private void SettingsChanged(object sender, Settings args)
|
||||
{
|
||||
_settingsManager.SaveSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Pages;
|
||||
|
||||
internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposable
|
||||
{
|
||||
private System.Threading.CancellationTokenSource _cancellationTokenSource = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public WindowWalkerListPage()
|
||||
{
|
||||
Name = Resources.windowwalker_name;
|
||||
Id = "com.microsoft.cmdpal.windowwalker";
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
RaiseItemsChanged(0);
|
||||
}
|
||||
|
||||
public List<WindowWalkerListItem> Query(string query)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new System.Threading.CancellationTokenSource();
|
||||
|
||||
WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.UpdateDesktopList();
|
||||
OpenWindows.Instance.UpdateOpenWindowsList(_cancellationTokenSource.Token);
|
||||
SearchController.Instance.UpdateSearchText(query);
|
||||
List<SearchResult> searchControllerResults = SearchController.Instance.SearchMatches;
|
||||
|
||||
return ResultHelper.GetResultList(searchControllerResults, !string.IsNullOrEmpty(query), string.Empty, "\uE946");
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return Query(SearchText).ToArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
360
src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
generated
Normal file
360
src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,360 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// 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", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.WindowWalker.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Switch between open windows.
|
||||
/// </summary>
|
||||
public static string window_walker_top_level_command_title {
|
||||
get {
|
||||
return ResourceManager.GetString("window_walker_top_level_command_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Close window.
|
||||
/// </summary>
|
||||
public static string windowwalker_Close {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Close", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Desktop.
|
||||
/// </summary>
|
||||
public static string windowwalker_Desktop {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Desktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Folder windows do not run in separate processes. (Click to open Explorer properties.).
|
||||
/// </summary>
|
||||
public static string windowwalker_ExplorerInfoSubTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_ExplorerInfoSubTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Info: Killing the Explorer process isn't possible..
|
||||
/// </summary>
|
||||
public static string windowwalker_ExplorerInfoTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_ExplorerInfoTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process.
|
||||
/// </summary>
|
||||
public static string windowwalker_Kill {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Kill", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your are going to kill the following process:.
|
||||
/// </summary>
|
||||
public static string windowwalker_KillMessage {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_KillMessage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Continue?.
|
||||
/// </summary>
|
||||
public static string windowwalker_KillMessageQuestion {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_KillMessageQuestion", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process confirmation.
|
||||
/// </summary>
|
||||
public static string windowwalker_KillMessageTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("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 windowwalker_KillMessageUwp {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_KillMessageUwp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Window Walker.
|
||||
/// </summary>
|
||||
public static string windowwalker_name {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Not Responding.
|
||||
/// </summary>
|
||||
public static string windowwalker_NotResponding {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_NotResponding", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No..
|
||||
/// </summary>
|
||||
public static string windowwalker_Number {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Number", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to pid.
|
||||
/// </summary>
|
||||
public static string windowwalker_pid {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_pid", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Switches between open windows.
|
||||
/// </summary>
|
||||
public static string windowwalker_plugin_description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_plugin_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Process name.
|
||||
/// </summary>
|
||||
public static string windowwalker_Process {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Process", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Process id.
|
||||
/// </summary>
|
||||
public static string windowwalker_ProcessId {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_ProcessId", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Running.
|
||||
/// </summary>
|
||||
public static string windowwalker_Running {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Running", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Request confirmation when killing a process.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingConfirmKillProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingConfirmKillProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide Explorer process information.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingExplorerSettingInfo {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingExplorerSettingInfo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This is an optional message that informs users about explorer behavior.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingExplorerSettingInfo_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingExplorerSettingInfo_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide "kill process" button if additional permissions required.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingHideKillProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingHideKillProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process and its child processes.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingKillProcessTree {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingKillProcessTree", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Be careful when activating this. Killing the whole process tree can lead to problematic application crashes..
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingKillProcessTree_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingKillProcessTree_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Stay open after closing windows and killing processes.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingOpenAfterKillAndClose {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingOpenAfterKillAndClose", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This feature won't work if the kill process confirmation is enabled..
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingOpenAfterKillAndClose_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingOpenAfterKillAndClose_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show only results from visible desktop.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingResultsVisibleDesktop {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingResultsVisibleDesktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Settings.
|
||||
/// </summary>
|
||||
public static string windowwalker_settings_name {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_settings_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This information is only shown in subtitle and tool tip, if you have at least two desktops..
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingSubtitleDesktopName_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingSubtitleDesktopName_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show desktop as a tag on the list item.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingTagDesktopName {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingTagDesktopName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show process id as a tag on the list item.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingTagPid {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingTagPid", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="windowwalker_name" xml:space="preserve">
|
||||
<value>Window Walker</value>
|
||||
</data>
|
||||
<data name="windowwalker_plugin_description" xml:space="preserve">
|
||||
<value>Switches between open windows</value>
|
||||
</data>
|
||||
<data name="windowwalker_Running" xml:space="preserve">
|
||||
<value>Running</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingConfirmKillProcess" xml:space="preserve">
|
||||
<value>Request confirmation when killing a process</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingExplorerSettingInfo" xml:space="preserve">
|
||||
<value>Hide Explorer process information</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="windowwalker_SettingHideKillProcess" xml:space="preserve">
|
||||
<value>Hide "kill process" button if additional permissions required</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingResultsVisibleDesktop" xml:space="preserve">
|
||||
<value>Show only results from visible desktop</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingTagDesktopName" xml:space="preserve">
|
||||
<value>Show desktop as a tag on the list item</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingTagPid" xml:space="preserve">
|
||||
<value>Show process id as a tag on the list item</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingOpenAfterKillAndClose" xml:space="preserve">
|
||||
<value>Stay open after closing windows and killing processes</value>
|
||||
</data>
|
||||
<data name="windowwalker_Desktop" xml:space="preserve">
|
||||
<value>Desktop</value>
|
||||
</data>
|
||||
<data name="windowwalker_Number" xml:space="preserve">
|
||||
<value>No.</value>
|
||||
<comment>Short version of "Number"</comment>
|
||||
</data>
|
||||
<data name="windowwalker_Process" xml:space="preserve">
|
||||
<value>Process name</value>
|
||||
</data>
|
||||
<data name="windowwalker_ProcessId" xml:space="preserve">
|
||||
<value>Process id</value>
|
||||
</data>
|
||||
<data name="windowwalker_ExplorerInfoSubTitle" xml:space="preserve">
|
||||
<value>Folder windows do not run in separate processes. (Click to open Explorer properties.)</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="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="windowwalker_Close" xml:space="preserve">
|
||||
<value>Close window</value>
|
||||
</data>
|
||||
<data name="windowwalker_Kill" xml:space="preserve">
|
||||
<value>Kill process</value>
|
||||
</data>
|
||||
<data name="windowwalker_KillMessage" xml:space="preserve">
|
||||
<value>Your are going to kill the following process:</value>
|
||||
</data>
|
||||
<data name="windowwalker_KillMessageQuestion" xml:space="preserve">
|
||||
<value>Continue?</value>
|
||||
</data>
|
||||
<data name="windowwalker_KillMessageTitle" xml:space="preserve">
|
||||
<value>Kill process confirmation</value>
|
||||
</data>
|
||||
<data name="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="windowwalker_SettingKillProcessTree" xml:space="preserve">
|
||||
<value>Kill process and its child processes</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingExplorerSettingInfo_Description" xml:space="preserve">
|
||||
<value>This is an optional message that informs users about explorer behavior</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingKillProcessTree_Description" xml:space="preserve">
|
||||
<value>Be careful when activating this. Killing the whole process tree can lead to problematic application crashes.</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingOpenAfterKillAndClose_Description" xml:space="preserve">
|
||||
<value>This feature won't work if the kill process confirmation is enabled.</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingSubtitleDesktopName_Description" xml:space="preserve">
|
||||
<value>This information is only shown in subtitle and tool tip, if you have at least two desktops.</value>
|
||||
</data>
|
||||
<data name="windowwalker_NotResponding" xml:space="preserve">
|
||||
<value>Not Responding</value>
|
||||
</data>
|
||||
<data name="window_walker_top_level_command_title" xml:space="preserve">
|
||||
<value>Switch between open windows</value>
|
||||
</data>
|
||||
<data name="VirtualDesktopHelper_AllDesktops" xml:space="preserve">
|
||||
<value>On all Desktops</value>
|
||||
</data>
|
||||
<data name="VirtualDesktopHelper_Desktop" xml:space="preserve">
|
||||
<value>Desktop {0}</value>
|
||||
</data>
|
||||
<data name="windowwalker_settings_name" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="windowwalker_pid" xml:space="preserve">
|
||||
<value>pid</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Pages;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker;
|
||||
|
||||
public partial class WindowWalkerCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly CommandItem _windowWalkerPageItem;
|
||||
private readonly SettingsManager _settingsManager = new();
|
||||
|
||||
internal static readonly VirtualDesktopHelper VirtualDesktopHelperInstance = new();
|
||||
|
||||
public WindowWalkerCommandsProvider()
|
||||
{
|
||||
DisplayName = Resources.windowwalker_name;
|
||||
_windowWalkerPageItem = new CommandItem(new WindowWalkerListPage())
|
||||
{
|
||||
Icon = new("\uE8B0"), // Window icon
|
||||
Title = Resources.window_walker_top_level_command_title,
|
||||
Subtitle = Resources.windowwalker_name,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new SettingsPage()),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_windowWalkerPageItem];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker;
|
||||
|
||||
internal sealed partial class WindowWalkerListItem : ListItem
|
||||
{
|
||||
private readonly Window? _window;
|
||||
|
||||
public Window? Window => _window;
|
||||
|
||||
public WindowWalkerListItem(Window? window)
|
||||
: base(new SwitchToWindowCommand(window))
|
||||
{
|
||||
_window = window;
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,7 @@
|
||||
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.WindowsServices\Microsoft.CmdPal.Ext.WindowsServices.csproj" />
|
||||
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.WindowsSettings\Microsoft.CmdPal.Ext.WindowsSettings.csproj" />
|
||||
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.WindowsTerminal\Microsoft.CmdPal.Ext.WindowsTerminal.csproj" />
|
||||
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.WindowWalker\Microsoft.CmdPal.Ext.WindowWalker.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj">
|
||||
<ReferenceOutputAssembly>True</ReferenceOutputAssembly>
|
||||
|
||||
@@ -12,6 +12,7 @@ using Microsoft.CmdPal.Ext.Shell;
|
||||
using Microsoft.CmdPal.Ext.WindowsServices;
|
||||
using Microsoft.CmdPal.Ext.WindowsSettings;
|
||||
using Microsoft.CmdPal.Ext.WindowsTerminal;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
@@ -63,6 +64,7 @@ public sealed class MainViewModel : IDisposable
|
||||
BuiltInCommands.Add(new RegistryCommandsProvider());
|
||||
BuiltInCommands.Add(new WindowsSettingsCommandsProvider());
|
||||
BuiltInCommands.Add(new ShellCommandsProvider());
|
||||
BuiltInCommands.Add(new WindowWalkerCommandsProvider());
|
||||
|
||||
ResetTopLevel();
|
||||
|
||||
@@ -162,5 +164,6 @@ public sealed class MainViewModel : IDisposable
|
||||
this.AddAlias(new CommandAlias("$", "com.microsoft.cmdpal.windowsSettings", true));
|
||||
this.AddAlias(new CommandAlias("=", "com.microsoft.cmdpal.calculator", true));
|
||||
this.AddAlias(new CommandAlias(">", "com.microsoft.cmdpal.shell", true));
|
||||
this.AddAlias(new CommandAlias("<", "com.microsoft.cmdpal.windowwalker", true));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user