Merge pull request #188 from zadjii-msft/joadoumie/window-walker

Migrate Window Walker from PT Run
This commit is contained in:
Jordi Adoumie
2024-12-13 11:27:04 -05:00
committed by GitHub
37 changed files with 4634 additions and 0 deletions

View File

@@ -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}

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
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();
}
}

View File

@@ -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,
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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(),
};
}
}

View File

@@ -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
{
}
}

View File

@@ -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,
}
}

View File

@@ -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;
}
}

View File

@@ -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];
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
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
{
}

View File

@@ -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);
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace 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;
}
}

View File

@@ -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() });
}
}
}

View File

@@ -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;
}
}

View File

@@ -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,
};
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}
}

View 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&apos;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 &quot;kill process&quot; 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&apos;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);
}
}
}
}

View File

@@ -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>

View File

@@ -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];
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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));
}
}