mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
mid merge -- doesn't build
This commit is contained in:
@@ -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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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 CloseWindowCommand : InvokableCommand
|
||||
{
|
||||
private readonly Window _window;
|
||||
|
||||
public CloseWindowCommand(Window window)
|
||||
{
|
||||
_window = window;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (!_window.IsWindow)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Can not close the window '{_window.Title}' ({_window.Hwnd}), because it doesn't exist." });
|
||||
}
|
||||
|
||||
_window.CloseThisWindow();
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
internal 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))
|
||||
{
|
||||
// AcceleratorKey = Key.F4,
|
||||
// AcceleratorModifiers = ModifierKeys.Control,
|
||||
Icon = new("\xE8BB"),
|
||||
Title = $"{Resources.wox_plugin_windowwalker_Close} (Ctrl+F4)",
|
||||
},
|
||||
};
|
||||
|
||||
// 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 ContextMenuResult
|
||||
{
|
||||
AcceleratorKey = Key.Delete,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
FontFamily = "Segoe Fluent Icons,Segoe MDL2 Assets",
|
||||
Glyph = "\xE74D", // E74D => Symbol: Delete
|
||||
Title = $"{Resources.wox_plugin_windowwalker_Kill} (Ctrl+Delete)",
|
||||
Action = _ => KillProcessCommand(windowData),
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to initiate killing the process of a window
|
||||
/// </summary>
|
||||
/// <param name="window">Window data</param>
|
||||
/// <returns>True if the PT Run window should close, otherwise false.</returns>
|
||||
private static bool KillProcessCommand(Window window)
|
||||
{
|
||||
// Validate process
|
||||
if (!window.IsWindow || !window.Process.DoesExist || !window.Process.Name.Equals(WindowProcess.GetProcessNameFromProcessID(window.Process.ProcessID), StringComparison.Ordinal))
|
||||
{
|
||||
Log.Debug($"Can not kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}), because it doesn't exist.", typeof(ContextMenuHelper));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request user confirmation
|
||||
if (WindowWalkerSettings.Instance.ConfirmKillProcess)
|
||||
{
|
||||
string messageBody = $"{Resources.wox_plugin_windowwalker_KillMessage}\n"
|
||||
+ $"{window.Process.Name} ({window.Process.ProcessID})\n\n"
|
||||
+ $"{(window.Process.IsUwpApp ? Resources.wox_plugin_windowwalker_KillMessageUwp : Resources.wox_plugin_windowwalker_KillMessageQuestion)}";
|
||||
MessageBoxResult messageBoxResult = MessageBox.Show(
|
||||
messageBody,
|
||||
Resources.wox_plugin_windowwalker_plugin_name + " - " + Resources.wox_plugin_windowwalker_KillMessageTitle,
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning);
|
||||
|
||||
if (messageBoxResult == MessageBoxResult.No)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Kill process
|
||||
window.Process.KillThisProcess(WindowWalkerSettings.Instance.KillProcessTree);
|
||||
return !WindowWalkerSettings.Instance.OpenAfterKillAndClose;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// 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.Plugin.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
|
||||
bool[,] matches = new bool[text.Length, searchText.Length];
|
||||
for (int firstIndex = 0; firstIndex < text.Length; firstIndex++)
|
||||
{
|
||||
for (int 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
|
||||
int maxScore = allMatches.Count > 0 ? CalculateScoreForMatches(allMatches[0]) : 0;
|
||||
List<int> bestMatch = allMatches.Count > 0 ? allMatches[0] : new List<int>();
|
||||
|
||||
foreach (var match in allMatches)
|
||||
{
|
||||
int 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 (int secondIndex = 0; secondIndex < matches.GetLength(1); secondIndex++)
|
||||
{
|
||||
for (int 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 (int currentIndex = 1; currentIndex < matches.Count; currentIndex++)
|
||||
{
|
||||
var previousIndex = currentIndex - 1;
|
||||
|
||||
score -= matches[currentIndex] - matches[previousIndex];
|
||||
}
|
||||
|
||||
return score == 0 ? -10000 : score;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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 Wox.Plugin.Common.Win32;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing methods to control the live preview
|
||||
/// </summary>
|
||||
internal 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)
|
||||
{
|
||||
uint renderPolicy = (uint)DwmNCRenderingPolicies.Enabled;
|
||||
|
||||
_ = NativeMethods.DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
12,
|
||||
ref renderPolicy,
|
||||
sizeof(uint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the live preview
|
||||
/// </summary>
|
||||
/// <param name="targetWindow">the window to show by making all other windows transparent</param>
|
||||
/// <param name="windowToSpare">the window which should not be transparent but is not the target window</param>
|
||||
public static void ActivateLivePreview(IntPtr targetWindow, IntPtr windowToSpare)
|
||||
{
|
||||
_ = NativeMethods.DwmpActivateLivePreview(
|
||||
true,
|
||||
targetWindow,
|
||||
windowToSpare,
|
||||
LivePreviewTrigger.Superbar,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the live preview
|
||||
/// </summary>
|
||||
public static void DeactivateLivePreview()
|
||||
{
|
||||
_ = NativeMethods.DwmpActivateLivePreview(
|
||||
false,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
LivePreviewTrigger.AltTab,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// 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 Wox.Plugin.Common.Win32;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that represents the state of the desktops windows
|
||||
/// </summary>
|
||||
internal 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 List<Window>();
|
||||
|
||||
/// <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 List<Window>(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
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
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 cancellationToken = (CancellationToken)tokenHandle.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 || !WindowWalkerSettings.Instance.ResultsFromVisibleDesktopOnly || Main.VirtualDesktopHelperInstance.GetDesktopCount() < 2) &&
|
||||
newWindow.ClassName != "Windows.UI.Core.CoreWindow" && newWindow.Process.Name != _powerLauncherExe)
|
||||
{
|
||||
// To hide (not add) preloaded uwp app windows that are invisible to the user and other cloaked windows, we check the cloak state. (Issue #13637.)
|
||||
// (If user asking to see cloaked uwp app windows again we can add an optional plugin setting in the future.)
|
||||
if (!newWindow.IsCloaked || newWindow.GetWindowCloakState() == Window.WindowCloakState.OtherDesktop)
|
||||
{
|
||||
windows.Add(newWindow);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// 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.Plugin.WindowWalker.Properties;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to work with results
|
||||
/// </summary>
|
||||
internal static class ResultHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a list of all results for the query.
|
||||
/// </summary>
|
||||
/// <param name="searchControllerResults">List with all search controller matches</param>
|
||||
/// <param name="icon">The path to the result icon</param>
|
||||
/// <returns>List of results</returns>
|
||||
internal static List<Result> GetResultList(List<SearchResult> searchControllerResults, bool isKeywordSearch, string icon, string infoIcon)
|
||||
{
|
||||
if (searchControllerResults == null || searchControllerResults.Count == 0)
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
List<Result> resultsList = new List<Result>(searchControllerResults.Count);
|
||||
bool 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 && isKeywordSearch && !WindowWalkerSettings.Instance.HideExplorerSettingInfo)
|
||||
{
|
||||
resultsList.Add(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 Result CreateResultFromSearchResult(SearchResult searchResult, string icon)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = searchResult.Result.Title,
|
||||
IcoPath = icon,
|
||||
SubTitle = GetSubtitle(searchResult.Result),
|
||||
ContextData = searchResult.Result,
|
||||
Action = c =>
|
||||
{
|
||||
searchResult.Result.SwitchToWindow();
|
||||
return true;
|
||||
},
|
||||
|
||||
// For debugging you can set the second parameter to true to see more information.
|
||||
ToolTipData = GetToolTip(searchResult.Result, false),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the subtitle for a result
|
||||
/// </summary>
|
||||
/// <param name="window">The window properties of the result</param>
|
||||
/// <returns>String with the subtitle</returns>
|
||||
private static string GetSubtitle(Window window)
|
||||
{
|
||||
if (window == null || !(window is Window))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string subtitleText = Resources.wox_plugin_windowwalker_Running + ": " + window.Process.Name;
|
||||
|
||||
if (WindowWalkerSettings.Instance.SubtitleShowPid)
|
||||
{
|
||||
subtitleText += $" ({window.Process.ProcessID})";
|
||||
}
|
||||
|
||||
if (!window.Process.IsResponding)
|
||||
{
|
||||
subtitleText += $" [{Resources.wox_plugin_windowwalker_NotResponding}]";
|
||||
}
|
||||
|
||||
if (WindowWalkerSettings.Instance.SubtitleShowDesktopName && Main.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
|
||||
{
|
||||
subtitleText += $" - {Resources.wox_plugin_windowwalker_Desktop}: {window.Desktop.Name}";
|
||||
}
|
||||
|
||||
return subtitleText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tool tip for a result
|
||||
/// </summary>
|
||||
/// <param name="window">The window properties of the result</param>
|
||||
/// <param name="debugToolTip">Value indicating if a detailed debug tooltip should be returned</param>
|
||||
/// <returns>Tooltip for the result or null of failure</returns>
|
||||
private static ToolTipData GetToolTip(Window window, bool debugToolTip)
|
||||
{
|
||||
if (window == null || !(window is Window))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!debugToolTip)
|
||||
{
|
||||
string text = $"{Resources.wox_plugin_windowwalker_Process}: {window.Process.Name}";
|
||||
text += $"\n{Resources.wox_plugin_windowwalker_ProcessId}: {window.Process.ProcessID}";
|
||||
|
||||
if (Main.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
|
||||
{
|
||||
text += $"\n{Resources.wox_plugin_windowwalker_Desktop}: {window.Desktop.Name}";
|
||||
|
||||
if (!window.Desktop.IsAllDesktopsView)
|
||||
{
|
||||
text += $" ({Resources.wox_plugin_windowwalker_Number} {window.Desktop.Number})";
|
||||
}
|
||||
}
|
||||
|
||||
return new ToolTipData(window.Title, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
string text = $"hWnd: {window.Hwnd}\n" +
|
||||
$"Window class: {window.ClassName}\n" +
|
||||
$"Process ID: {window.Process.ProcessID}\n" +
|
||||
$"Thread ID: {window.Process.ThreadID}\n" +
|
||||
$"Process: {window.Process.Name}\n" +
|
||||
$"Process exists: {window.Process.DoesExist}\n" +
|
||||
$"Is full access denied: {window.Process.IsFullAccessDenied}\n" +
|
||||
$"Is uwp app: {window.Process.IsUwpApp}\n" +
|
||||
$"Is ShellProcess: {window.Process.IsShellProcess}\n" +
|
||||
$"Is window cloaked: {window.IsCloaked}\n" +
|
||||
$"Window cloak state: {window.GetWindowCloakState()}\n" +
|
||||
$"Desktop id: {window.Desktop.Id}\n" +
|
||||
$"Desktop name: {window.Desktop.Name}\n" +
|
||||
$"Desktop number: {window.Desktop.Number}\n" +
|
||||
$"Desktop is visible: {window.Desktop.IsVisible}\n" +
|
||||
$"Desktop position: {window.Desktop.Position}\n" +
|
||||
$"Is AllDesktops view: {window.Desktop.IsAllDesktopsView}\n" +
|
||||
$"Responding: {window.Process.IsResponding}";
|
||||
|
||||
return new ToolTipData(window.Title, text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an information result about the explorer setting
|
||||
/// </summary>
|
||||
/// <param name="iIcon">The path to the info icon.</param>
|
||||
/// <returns>An object of the type <see cref="Result"/> with the information.</returns>
|
||||
private static Result GetExplorerInfoResult(string iIcon)
|
||||
{
|
||||
return new Result()
|
||||
{
|
||||
Title = Resources.wox_plugin_windowwalker_ExplorerInfoTitle,
|
||||
IcoPath = iIcon,
|
||||
SubTitle = Resources.wox_plugin_windowwalker_ExplorerInfoSubTitle,
|
||||
Action = c =>
|
||||
{
|
||||
Helper.OpenInShell("rundll32.exe", "shell32.dll,Options_RunDLL 7"); // "shell32.dll,Options_RunDLL 7" opens the view tab in folder options of explorer.
|
||||
return true;
|
||||
},
|
||||
Score = 100_000,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// 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.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for searching and finding matches for the strings provided.
|
||||
/// Essentially the UI independent model of the application
|
||||
/// </summary>
|
||||
internal 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
|
||||
{
|
||||
return searchText;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
// Using CurrentCulture since this is user facing
|
||||
searchText = value.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the open window search results
|
||||
/// </summary>
|
||||
internal List<SearchResult> SearchMatches
|
||||
{
|
||||
get { return new List<SearchResult>(searchMatches).OrderByDescending(x => x.Score).ToList(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets singleton Pattern
|
||||
/// </summary>
|
||||
internal static SearchController Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
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, 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 class SearchResultUpdateEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains search result windows with each window including the reason why the result was included
|
||||
/// </summary>
|
||||
internal class SearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actual window reference for the search result
|
||||
/// </summary>
|
||||
internal Window Result
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the title window
|
||||
/// </summary>
|
||||
internal List<int> SearchMatchesInTitle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the
|
||||
/// name of the process
|
||||
/// </summary>
|
||||
internal List<int> SearchMatchesInProcessName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of match (shortcut, fuzzy or nothing)
|
||||
/// </summary>
|
||||
internal SearchType SearchResultMatchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a score indicating how well this matches what we are looking for
|
||||
/// </summary>
|
||||
internal int Score
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source of where the best score was found
|
||||
/// </summary>
|
||||
internal TextType BestScoreSource
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
internal SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = matchesInTitle;
|
||||
SearchMatchesInProcessName = matchesInProcessName;
|
||||
SearchResultMatchType = matchType;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// </summary>
|
||||
internal SearchResult(Window window)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = new List<int>();
|
||||
SearchMatchesInProcessName = new List<int>();
|
||||
SearchResultMatchType = SearchType.Empty;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for how closely this window matches the search string
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Higher Score is better
|
||||
/// </remarks>
|
||||
private void CalculateScore()
|
||||
{
|
||||
if (FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName) >
|
||||
FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle))
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName);
|
||||
BestScoreSource = TextType.ProcessName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle);
|
||||
BestScoreSource = TextType.WindowTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of text that a string represents
|
||||
/// </summary>
|
||||
internal enum TextType
|
||||
{
|
||||
ProcessName,
|
||||
WindowTitle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of search
|
||||
/// </summary>
|
||||
internal enum SearchType
|
||||
{
|
||||
/// <summary>
|
||||
/// the search string is empty, which means all open windows are
|
||||
/// going to be returned
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// Regular fuzzy match search
|
||||
/// </summary>
|
||||
Fuzzy,
|
||||
|
||||
/// <summary>
|
||||
/// The user has entered text that has been matched to a shortcut
|
||||
/// and the shortcut is now being searched
|
||||
/// </summary>
|
||||
Shortcut,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
namespace Microsoft.Plugin.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 class SearchString
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets where is the search string coming from (is it a shortcut
|
||||
/// or direct string, etc...)
|
||||
/// </summary>
|
||||
internal SearchResult.SearchType SearchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual text we are searching for
|
||||
/// </summary>
|
||||
internal string SearchText
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchString"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="searchText">text from search</param>
|
||||
/// <param name="searchType">type of search</param>
|
||||
internal SearchString(string searchText, SearchResult.SearchType searchType)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SearchType = searchType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// 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 Wox.Plugin.Common.VirtualDesktop.Helper;
|
||||
using Wox.Plugin.Common.Win32;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a specific open window
|
||||
/// </summary>
|
||||
internal 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 Dictionary<IntPtr, WindowProcess>();
|
||||
|
||||
/// <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
|
||||
{
|
||||
int 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
|
||||
{
|
||||
get { return hwnd; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of with the process information of the window
|
||||
/// </summary>
|
||||
internal WindowProcess Process
|
||||
{
|
||||
get { return processInfo; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of with the desktop information of the window
|
||||
/// </summary>
|
||||
internal VDesktop Desktop
|
||||
{
|
||||
get { return desktopInfo; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the class for the window represented
|
||||
/// </summary>
|
||||
internal string ClassName
|
||||
{
|
||||
get
|
||||
{
|
||||
return 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
|
||||
{
|
||||
get
|
||||
{
|
||||
return 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
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetWindowCloakState() != WindowCloakState.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified window handle identifies an existing window.
|
||||
/// </summary>
|
||||
internal bool IsWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return NativeMethods.IsWindow(Hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is a toolwindow
|
||||
/// </summary>
|
||||
internal bool IsToolWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return (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
|
||||
{
|
||||
get
|
||||
{
|
||||
return (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
|
||||
{
|
||||
get
|
||||
{
|
||||
return 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
|
||||
{
|
||||
get
|
||||
{
|
||||
return NativeMethods.GetWindow(Hwnd, GetWindowCmd.GW_OWNER) == IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is minimized
|
||||
/// </summary>
|
||||
internal bool Minimized
|
||||
{
|
||||
get
|
||||
{
|
||||
return 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 = Main.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) || !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 int isCloakedState, sizeof(uint));
|
||||
|
||||
switch (isCloakedState)
|
||||
{
|
||||
case (int)DwmWindowCloakStates.None:
|
||||
return WindowCloakState.None;
|
||||
case (int)DwmWindowCloakStates.CloakedApp:
|
||||
return WindowCloakState.App;
|
||||
case (int)DwmWindowCloakStates.CloakedShell:
|
||||
return Main.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.
|
||||
Log.Debug($"Invalid process {processId} ({processName}) for window handle {hWindow}.", typeof(Window));
|
||||
_handlesToProcessCache.Add(hWindow, new WindowProcess(0, 0, string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
// Correct the process data if the window belongs to a uwp app hosted by 'ApplicationFrameHost.exe'
|
||||
// (This only works if the window isn't minimized. For minimized windows the required child window isn't assigned.)
|
||||
if (string.Equals(_handlesToProcessCache[hWindow].Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
new Task(() =>
|
||||
{
|
||||
EnumWindowsProc callbackptr = new EnumWindowsProc((IntPtr hwnd, IntPtr lParam) =>
|
||||
{
|
||||
// Every uwp app main window has at least three child windows. Only the one we are interested in has a class starting with "Windows.UI.Core." and is assigned to the real app process.
|
||||
// (The other ones have a class name that begins with the string "ApplicationFrame".)
|
||||
if (GetWindowClassName(hwnd).StartsWith("Windows.UI.Core.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var childProcessId = WindowProcess.GetProcessIDFromWindowHandle(hwnd);
|
||||
var childThreadId = WindowProcess.GetThreadIDFromWindowHandle(hwnd);
|
||||
var childProcessName = WindowProcess.GetProcessNameFromProcessID(childProcessId);
|
||||
|
||||
// Update process info in cache
|
||||
_handlesToProcessCache[hWindow].UpdateProcessInfo(childProcessId, childThreadId, childProcessName);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
_ = NativeMethods.EnumChildWindows(hWindow, callbackptr, 0);
|
||||
}).Start();
|
||||
}
|
||||
|
||||
return _handlesToProcessCache[hWindow];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
// 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 Wox.Infrastructure;
|
||||
using Wox.Plugin.Common.Win32;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the process data of an open window. This class is used in the process cache and for the process object of the open window
|
||||
/// </summary>
|
||||
internal 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
|
||||
{
|
||||
get { return _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
|
||||
{
|
||||
IntPtr 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 uint 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)
|
||||
{
|
||||
uint 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)
|
||||
{
|
||||
IntPtr 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)
|
||||
{
|
||||
string killTree = killProcessTree ? " /t" : string.Empty;
|
||||
Helper.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, Helper.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)
|
||||
{
|
||||
IntPtr 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,15 @@ public class SettingsManager
|
||||
private readonly string _filePath;
|
||||
private readonly Settings _settings = new();
|
||||
|
||||
private static SettingsManager? instance;
|
||||
|
||||
private readonly ToggleSetting _resultsFromVisibleDesktopOnly = new(nameof(ResultsFromVisibleDesktopOnly), Resources.wox_plugin_windowwalker_SettingResultsVisibleDesktop, Resources.wox_plugin_windowwalker_SettingResultsVisibleDesktop, false);
|
||||
private readonly ToggleSetting _subtitleShowPid = new(nameof(SubtitleShowPid), Resources.wox_plugin_windowwalker_SettingSubtitlePid, Resources.wox_plugin_windowwalker_SettingSubtitlePid, false);
|
||||
private readonly ToggleSetting _subtitleShowDesktopName = new(nameof(SubtitleShowDesktopName), Resources.wox_plugin_windowwalker_SettingSubtitleDesktopName, Resources.wox_plugin_windowwalker_SettingSubtitleDesktopName_Description, true);
|
||||
private readonly ToggleSetting _confirmKillProcess = new(nameof(ConfirmKillProcess), Resources.wox_plugin_windowwalker_SettingConfirmKillProcess, Resources.wox_plugin_windowwalker_SettingConfirmKillProcess, true);
|
||||
private readonly ToggleSetting _killProcessTree = new(nameof(KillProcessTree), Resources.wox_plugin_windowwalker_SettingKillProcessTree, Resources.wox_plugin_windowwalker_SettingKillProcessTree_Description, false);
|
||||
private readonly ToggleSetting _openAfterKillAndClose = new(nameof(OpenAfterKillAndClose), Resources.wox_plugin_windowwalker_SettingOpenAfterKillAndClose, Resources.wox_plugin_windowwalker_SettingOpenAfterKillAndClose_Description, false);
|
||||
private readonly ToggleSetting _hideKillProcessOnElevatedProcess = new(nameof(HideKillProcessOnElevatedProcess), Resources.wox_plugin_windowwalker_SettingHideKillProcess, Resources.wox_plugin_windowwalker_SettingHideKillProcess, false);
|
||||
private readonly ToggleSetting _hideKillProcessOnElevatedProcesses = new(nameof(HideKillProcessOnElevatedProcesses), Resources.wox_plugin_windowwalker_SettingHideKillProcess, Resources.wox_plugin_windowwalker_SettingHideKillProcess, false);
|
||||
private readonly ToggleSetting _hideExplorerSettingInfo = new(nameof(HideExplorerSettingInfo), Resources.wox_plugin_windowwalker_SettingExplorerSettingInfo, Resources.wox_plugin_windowwalker_SettingExplorerSettingInfo_Description, false);
|
||||
|
||||
public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value;
|
||||
@@ -40,7 +42,7 @@ public class SettingsManager
|
||||
|
||||
public bool OpenAfterKillAndClose => _openAfterKillAndClose.Value;
|
||||
|
||||
public bool HideKillProcessOnElevatedProcess => _hideKillProcessOnElevatedProcess.Value;
|
||||
public bool HideKillProcessOnElevatedProcesses => _hideKillProcessOnElevatedProcesses.Value;
|
||||
|
||||
public bool HideExplorerSettingInfo => _hideExplorerSettingInfo.Value;
|
||||
|
||||
@@ -79,6 +81,15 @@ public class SettingsManager
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
internal static SettingsManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
instance ??= new SettingsManager();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public Settings GetSettings()
|
||||
{
|
||||
return _settings;
|
||||
|
||||
@@ -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(SettingsManager settingsManager)
|
||||
{
|
||||
Name = "Settings Page NEED TO MAKE RESOURCES FOR THIS";
|
||||
Icon = new("\uE713"); // Settings icon
|
||||
_settings = settingsManager.GetSettings();
|
||||
_settingsManager = settingsManager;
|
||||
|
||||
_settings.SettingsChanged += SettingsChanged;
|
||||
}
|
||||
|
||||
private void SettingsChanged(object sender, Settings args)
|
||||
{
|
||||
_settingsManager.SaveSettings();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
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;
|
||||
@@ -19,5 +20,8 @@ public partial class WalkerTopLevelCommandItem : CommandItem
|
||||
{
|
||||
Title = Resources.window_walker_top_level_command_title;
|
||||
Subtitle = Resources.wox_plugin_windowwalker_plugin_name;
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new SettingsPage(settingsManager)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker;
|
||||
|
||||
internal partial class WindowWalkerListItem : ListItem
|
||||
{
|
||||
private readonly Window _window;
|
||||
|
||||
public Window Window => _window;
|
||||
|
||||
public WindowWalkerListItem(Window window)
|
||||
: base(new NoOpCommand())
|
||||
{
|
||||
_window = window;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user