Rename the [Ee]xts dir to ext (#38852)

**WARNING:** This PR will probably blow up all in-flight PRs

at some point in the early days of CmdPal, two of us created seperate
`Exts` and `exts` dirs. Depending on what the casing was on the branch
that you checked one of those out from, it'd get stuck like that on your
PC forever.

Windows didn't care, so we never noticed.

But GitHub does care, and now browsing the source on GitHub is basically
impossible.

Closes #38081
This commit is contained in:
Mike Griese
2025-04-15 06:07:22 -05:00
committed by GitHub
parent 60f50d853b
commit 2b5181b4c9
379 changed files with 35 additions and 35 deletions

View File

@@ -0,0 +1,44 @@
// 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 Microsoft.CmdPal.Ext.WindowWalker.Commands;
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
using Microsoft.CommandPalette.Extensions.Toolkit;
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 not Window windowData)
{
return [];
}
var contextMenu = new List<CommandContextItem>()
{
new(new CloseWindowCommand(windowData))
{
RequestedShortcut = KeyChordHelpers.FromModifiers(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 = KeyChordHelpers.FromModifiers(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,127 @@
// 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.Helpers;
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
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>
/// <returns>List of results</returns>
internal static List<WindowWalkerListItem> GetResultList(List<SearchResult> searchControllerResults, bool isKeywordSearch)
{
if (searchControllerResults == null || searchControllerResults.Count == 0)
{
return [];
}
var 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))
.ToList();
if (addExplorerInfo && !SettingsManager.Instance.HideExplorerSettingInfo)
{
resultsList.Insert(0, GetExplorerInfoResult());
}
return resultsList;
}
/// <summary>
/// Creates a Result object from a given SearchResult.
/// </summary>
/// <param name="searchResult">The SearchResult object to convert.</param>
/// <returns>A Result object populated with data from the SearchResult.</returns>
private static WindowWalkerListItem CreateResultFromSearchResult(SearchResult searchResult)
{
var item = new WindowWalkerListItem(searchResult.Result)
{
Title = searchResult.Result.Title,
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 is null or null)
{
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,
Foreground = 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()
{
return new WindowWalkerListItem(null)
{
Title = Resources.windowwalker_ExplorerInfoTitle,
Icon = new IconInfo("\uE946"), // Info
Subtitle = Resources.windowwalker_ExplorerInfoSubTitle,
Command = new ExplorerInfoResultCommand(),
};
}
}

View File

@@ -0,0 +1,150 @@
// 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;
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
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 ?? []).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");
var snapshotOfOpenWindows = OpenWindows.Instance.Windows;
searchMatches = string.IsNullOrWhiteSpace(SearchText) ? AllOpenWindows(snapshotOfOpenWindows) : 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 = [];
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 = [];
foreach (var window in openWindows)
{
if (window.Title.Length != 0)
{
result.Add(new SearchResult(window));
}
}
return SettingsManager.Instance.InMruOrder
? result.ToList()
: 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.CommandPalette.Extensions.Toolkit;
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 cannot 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;
}
}
}