[PT Run] VirtualDesktopHelper & WindowWalker improvements (#16325)

* Import vdh from poc

* last changes

* push changes

* small change

* add error handling to vdh

* last changes

* make spellchecker happy

* last changes

* last changes

* spell check

* fix settings defaults

* Improve WindowWalkerSettings class

* add comment

* New settings and improvements

* new features

* subtitle and tool tip

* spell fixes

* small fixes

* fixes

* Explorer info

* spell fixes

* fixes and CloseWindow feature

* last changes

* first part of implementing KillProcess

* killProcess Part 2 & Fixes

* text fix and installer

* update access modifiers

* some fixes

* update dev docs

* fix dev docs

* dev doc change

* dev docs: add missed infos

* dev docs: add link

* Update src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowWalkerSettings.cs

* fix build

* resolve feedback

* fix settings

* add tests
This commit is contained in:
Heiko
2022-03-07 12:45:29 +01:00
committed by GitHub
parent 27611593bd
commit e8363a3be1
33 changed files with 1764 additions and 103 deletions

View File

@@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using Microsoft.Plugin.WindowWalker.Properties;
using Wox.Plugin;
using Wox.Plugin.Logger;
namespace Microsoft.Plugin.WindowWalker.Components
{
internal class ContextMenuHelper
{
/// <summary>
/// Returns a list of all <see cref="ContextMenuResult"/>s for the selected <see cref="Result"/>.
/// </summary>
/// <param name="result">Selected result</param>
/// <returns>List of context menu results</returns>
internal static List<ContextMenuResult> GetContextMenuResults(in Result result)
{
if (!(result?.ContextData is Window windowData))
{
return new List<ContextMenuResult>(0);
}
var contextMenu = new List<ContextMenuResult>()
{
new ContextMenuResult
{
AcceleratorKey = Key.F4,
AcceleratorModifiers = ModifierKeys.Control,
FontFamily = "Segoe MDL2 Assets",
Glyph = "\xE8BB", // E8B8 => Symbol: ChromeClose
Title = $"{Resources.wox_plugin_windowwalker_Close} (Ctrl+F4)",
Action = _ =>
{
if (!windowData.IsWindow)
{
Log.Debug($"Can not close the window '{windowData.Title}' ({windowData.Hwnd}), because it doesn't exist.", typeof(ContextMenuHelper));
return false;
}
// As a workaround to close PT Run after executing the context menu command, we switch to the window before closing it (Issue #16601).
// We use the setting OpenAfterKillAndClose to detect if we have to switch.
windowData.CloseThisWindow(!WindowWalkerSettings.Instance.OpenAfterKillAndClose);
return !WindowWalkerSettings.Instance.OpenAfterKillAndClose;
},
},
};
// Hide menu if Explorer.exe is the shell process or the process name is ApplicationFrameHost.exe
// In the first case we would crash the windows ui and in the second case we would kill the generic process for uwp apps.
if (!windowData.Process.IsShellProcess && !(windowData.Process.IsUwpApp & windowData.Process.Name.ToLower() == "applicationframehost.exe")
&& !(windowData.Process.IsFullAccessDenied & WindowWalkerSettings.Instance.HideKillProcessOnElevatedProcesses))
{
contextMenu.Add(new ContextMenuResult
{
AcceleratorKey = Key.Delete,
AcceleratorModifiers = ModifierKeys.Control,
FontFamily = "Segoe MDL2 Assets",
Glyph = "\xE74D", // E74D => Symbol: Delete
Title = $"{Resources.wox_plugin_windowwalker_Kill} (Ctrl+Delete)",
Action = _ => KillProcessCommand(windowData),
});
}
return contextMenu;
}
/// <summary>
/// Method to initiate killing the process of a window
/// </summary>
/// <param name="window">Window data</param>
/// <returns>True if the PT Run window should close, otherwise false.</returns>
private static bool KillProcessCommand(Window window)
{
// Validate process
if (!window.IsWindow || !window.Process.DoesExist || !window.Process.Name.Equals(WindowProcess.GetProcessNameFromProcessID(window.Process.ProcessID)) )
{
Log.Debug($"Can not kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}), because it doesn't exist.", typeof(ContextMenuHelper));
return false;
}
// Request user confirmation
if (WindowWalkerSettings.Instance.ConfirmKillProcess)
{
string messageBody = $"{Resources.wox_plugin_windowwalker_KillMessage}\n"
+ $"{window.Process.Name} ({window.Process.ProcessID})\n\n"
+ $"{(window.Process.IsUwpApp ? Resources.wox_plugin_windowwalker_KillMessageUwp : Resources.wox_plugin_windowwalker_KillMessageQuestion)}";
MessageBoxResult messageBoxResult = MessageBox.Show(
messageBody,
Resources.wox_plugin_windowwalker_plugin_name + " - " + Resources.wox_plugin_windowwalker_KillMessageTitle,
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (messageBoxResult == MessageBoxResult.No)
{
return false;
}
}
// As a workaround to close PT Run before executing the command, we switch to the window before killing it's process
if (!WindowWalkerSettings.Instance.OpenAfterKillAndClose)
{
window.SwitchToWindow();
}
// Kill process
window.Process.KillThisProcess(WindowWalkerSettings.Instance.KillProcessTree);
return !WindowWalkerSettings.Instance.OpenAfterKillAndClose;
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Class housing fuzzy matching methods
/// </summary>
public static class FuzzyMatching
internal static class FuzzyMatching
{
/// <summary>
/// Finds the best match (the one with the most
@@ -25,7 +25,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <param name="searchText">the text to search for</param>
/// <returns>returns the index location of each of the letters of the matches</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1814:Prefer jagged arrays over multidimensional", Justification = "matches does not waste space with the current implementation, however this could probably be optimized to store the indices of matches instead of boolean values. Currently there are no unit tests for this, but we could refactor if memory/perf becomes an issue. ")]
public static List<int> FindBestFuzzyMatch(string text, string searchText)
internal static List<int> FindBestFuzzyMatch(string text, string searchText)
{
if (searchText == null)
{
@@ -86,7 +86,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// one the search string and each cell marked as an intersection between the two</param>
/// <returns>a list of the possible combinations that match the search text</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1814:Prefer jagged arrays over multidimensional", Justification = "matches does not waste space with the current implementation, however this could probably be optimized to store the indices of matches instead of boolean values. Currently there are no unit tests for this, but we could refactor if memory/perf becomes an issue. ")]
public static List<List<int>> GetAllMatchIndexes(bool[,] matches)
internal static List<List<int>> GetAllMatchIndexes(bool[,] matches)
{
if (matches == null)
{
@@ -127,7 +127,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// </summary>
/// <param name="matches">the index of the matches</param>
/// <returns>an integer representing the score</returns>
public static int CalculateScoreForMatches(List<int> matches)
internal static int CalculateScoreForMatches(List<int> matches)
{
if (matches == null)
{

View File

@@ -34,7 +34,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the list of all open windows
/// </summary>
public List<Window> Windows
internal List<Window> Windows
{
get { return new List<Window>(windows); }
}
@@ -44,7 +44,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// the first instance gets created and that all the requests
/// end up at that one instance
/// </summary>
public static OpenWindows Instance
internal static OpenWindows Instance
{
get
{
@@ -69,7 +69,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Updates the list of open windows
/// </summary>
public void UpdateOpenWindowsList()
internal void UpdateOpenWindowsList()
{
windows.Clear();
EnumWindowsProc callbackptr = new EnumWindowsProc(WindowEnumerationCallBack);
@@ -83,22 +83,21 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <param name="lParam">Value being passed from the caller (we don't use this but might come in handy
/// in the future</param>
/// <returns>true to make sure to continue enumeration</returns>
public bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
internal bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
{
Window newWindow = new Window(hwnd);
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
newWindow.ClassName != "Windows.UI.Core.CoreWindow" && newWindow.ProcessInfo.Name != _powerLauncherExe)
(newWindow.Desktop.IsVisible || !WindowWalkerSettings.Instance.ResultsFromVisibleDesktopOnly) &&
newWindow.ClassName != "Windows.UI.Core.CoreWindow" && newWindow.Process.Name != _powerLauncherExe)
{
// To hide (not add) preloaded uwp app windows that are invisible to the user we check the cloak state in DWM to be "none". (Issue #13637.)
// (If user asking to see these windows again we can add an optional plugin setting in the future.)
// [@htcfreek, 2022-02-01: Removed the IsCloaked check to list windows from virtual desktops other than the current one again (#15887). In a second PR I will fix it re-implement it with improved code again.]
// if (!newWindow.IsCloaked)
// {
windows.Add(newWindow);
// }
// To hide (not add) preloaded uwp app windows that are invisible to the user and other cloaked windows, we check the cloak state. (Issue #13637.)
// (If user asking to see cloaked uwp app windows again we can add an optional plugin setting in the future.)
if (!newWindow.IsCloaked || newWindow.GetWindowCloakState() == Window.WindowCloakState.OtherDesktop)
{
windows.Add(newWindow);
}
}
return true;

View File

@@ -0,0 +1,160 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.Plugin.WindowWalker.Properties;
using Wox.Infrastructure;
using Wox.Plugin;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Helper class to work with results
/// </summary>
internal static class ResultHelper
{
/// <summary>
/// Returns a list of all results for the query.
/// </summary>
/// <param name="searchControllerResults">List with all search controller matches</param>
/// <param name="icon">The path to the result icon</param>
/// <returns>List of results</returns>
internal static List<Result> GetResultList(List<SearchResult> searchControllerResults, bool isKeywordSearch, string icon, string infoIcon)
{
bool addExplorerInfo = false;
List<Result> resultsList = new List<Result>();
foreach (SearchResult x in searchControllerResults)
{
if (x.Result.Process.Name.ToLower() == "explorer.exe" && x.Result.Process.IsShellProcess)
{
addExplorerInfo = true;
}
resultsList.Add(new Result()
{
Title = x.Result.Title,
IcoPath = icon,
SubTitle = GetSubtitle(x.Result),
ContextData = x.Result,
Action = c =>
{
x.Result.SwitchToWindow();
return true;
},
// For debugging you can set the second parameter to true to see more informations.
ToolTipData = GetToolTip(x.Result, false),
});
}
if (addExplorerInfo && isKeywordSearch && !WindowWalkerSettings.Instance.HideExplorerSettingInfo)
{
resultsList.Add(GetExplorerInfoResult(infoIcon));
}
return resultsList;
}
/// <summary>
/// Returns the subtitle for a result
/// </summary>
/// <param name="window">The window properties of the result</param>
/// <returns>String with the subtitle</returns>
private static string GetSubtitle(Window window)
{
if (window == null || !(window is Window))
{
return string.Empty;
}
string subtitleText = Resources.wox_plugin_windowwalker_Running + ": " + window.Process.Name;
if (WindowWalkerSettings.Instance.SubtitleShowPid)
{
subtitleText += $" ({window.Process.ProcessID})";
}
if (WindowWalkerSettings.Instance.SubtitleShowDesktopName && Main.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
{
subtitleText += $" - {Resources.wox_plugin_windowwalker_Desktop}: {window.Desktop.Name}";
}
return subtitleText;
}
/// <summary>
/// Returns the tool tip for a result
/// </summary>
/// <param name="window">The window properties of the result</param>
/// <param name="debugToolTip">Value indicating if a detailed debug tooltip should be returned</param>
/// <returns>Tooltip for the result or null of failure</returns>
private static ToolTipData GetToolTip(Window window, bool debugToolTip)
{
if (window == null || !(window is Window))
{
return null;
}
if (!debugToolTip)
{
string text = $"{Resources.wox_plugin_windowwalker_Process}: {window.Process.Name}";
text += $"\n{Resources.wox_plugin_windowwalker_ProcessId}: {window.Process.ProcessID}";
if (Main.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
{
text += $"\n{Resources.wox_plugin_windowwalker_Desktop}: {window.Desktop.Name}";
if (!window.Desktop.IsAllDesktopsView)
{
text += $" ({Resources.wox_plugin_windowwalker_Number} {window.Desktop.Number})";
}
}
return new ToolTipData(window.Title, text);
}
else
{
string text = $"hWnd: {window.Hwnd}\n" +
$"Window class: {window.ClassName}\n" +
$"Process ID: {window.Process.ProcessID}\n" +
$"Thread ID: {window.Process.ThreadID}\n" +
$"Process: {window.Process.Name}\n" +
$"Process exists: {window.Process.DoesExist}\n" +
$"Is full access denied: {window.Process.IsFullAccessDenied}\n" +
$"Is uwp app: {window.Process.IsUwpApp}\n" +
$"Is ShellProcess: {window.Process.IsShellProcess}\n" +
$"Is window cloaked: {window.IsCloaked}\n" +
$"Window cloak state: {window.GetWindowCloakState()}\n" +
$"Desktop name: {window.Desktop.Name}" +
$"Desktop number: {window.Desktop.Number}\n" +
$"Desktop is visible: {window.Desktop.IsVisible}\n" +
$"Desktop position: {window.Desktop.Position}\n" +
$"Is AllDesktops view: {window.Desktop.IsAllDesktopsView}";
return new ToolTipData(window.Title, text);
}
}
/// <summary>
/// Returns an information result about the explorer setting
/// </summary>
/// <param name="iIcon">The path to the info icon.</param>
/// <returns>An object of the type <see cref="Result"/> with the information.</returns>
private static Result GetExplorerInfoResult(string iIcon)
{
return new Result()
{
Title = Resources.wox_plugin_windowwalker_ExplorerInfoTitle,
IcoPath = iIcon,
SubTitle = Resources.wox_plugin_windowwalker_ExplorerInfoSubTitle,
Action = c =>
{
Helper.OpenInShell("rundll32.exe", "shell32.dll,Options_RunDLL 7"); // "shell32.dll,Options_RunDLL 7" opens the view tab in folder options of explorer.
return true;
},
Score = 100_000,
};
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets or sets the current search text
/// </summary>
public string SearchText
internal string SearchText
{
get
{
@@ -51,7 +51,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the open window search results
/// </summary>
public List<SearchResult> SearchMatches
internal List<SearchResult> SearchMatches
{
get { return new List<SearchResult>(searchMatches).OrderByDescending(x => x.Score).ToList(); }
}
@@ -59,7 +59,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets singleton Pattern
/// </summary>
public static SearchController Instance
internal static SearchController Instance
{
get
{
@@ -84,7 +84,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Event handler for when the search text has been updated
/// </summary>
public void UpdateSearchText(string searchText)
internal void UpdateSearchText(string searchText)
{
SearchText = searchText;
SyncOpenWindowsWithModel();
@@ -93,7 +93,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Syncs the open windows with the OpenWindows Model
/// </summary>
public void SyncOpenWindowsWithModel()
internal void SyncOpenWindowsWithModel()
{
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
@@ -126,7 +126,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
foreach (var window in openWindows)
{
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchString.SearchText);
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.ProcessInfo.Name, searchString.SearchText);
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.Process.Name, searchString.SearchText);
if ((titleMatch.Count != 0 || processMatch.Count != 0) &&
window.Title.Length != 0)
@@ -145,7 +145,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Event args for a window list update event
/// </summary>
public class SearchResultUpdateEventArgs : EventArgs
internal class SearchResultUpdateEventArgs : EventArgs
{
}
}

View File

@@ -10,12 +10,12 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Contains search result windows with each window including the reason why the result was included
/// </summary>
public class SearchResult
internal class SearchResult
{
/// <summary>
/// Gets the actual window reference for the search result
/// </summary>
public Window Result
internal Window Result
{
get;
private set;
@@ -24,7 +24,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the list of indexes of the matching characters for the search in the title window
/// </summary>
public List<int> SearchMatchesInTitle
internal List<int> SearchMatchesInTitle
{
get;
private set;
@@ -34,7 +34,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// Gets the list of indexes of the matching characters for the search in the
/// name of the process
/// </summary>
public List<int> SearchMatchesInProcessName
internal List<int> SearchMatchesInProcessName
{
get;
private set;
@@ -43,7 +43,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the type of match (shortcut, fuzzy or nothing)
/// </summary>
public SearchType SearchResultMatchType
internal SearchType SearchResultMatchType
{
get;
private set;
@@ -52,7 +52,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a score indicating how well this matches what we are looking for
/// </summary>
public int Score
internal int Score
{
get;
private set;
@@ -61,7 +61,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the source of where the best score was found
/// </summary>
public TextType BestScoreSource
internal TextType BestScoreSource
{
get;
private set;
@@ -71,7 +71,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// Initializes a new instance of the <see cref="SearchResult"/> class.
/// Constructor
/// </summary>
public SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
internal SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
{
Result = window;
SearchMatchesInTitle = matchesInTitle;
@@ -104,7 +104,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// The type of text that a string represents
/// </summary>
public enum TextType
internal enum TextType
{
ProcessName,
WindowTitle,
@@ -113,7 +113,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// The type of search
/// </summary>
public enum SearchType
internal enum SearchType
{
/// <summary>
/// the search string is empty, which means all open windows are

View File

@@ -16,7 +16,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// Gets where is the search string coming from (is it a shortcut
/// or direct string, etc...)
/// </summary>
public SearchResult.SearchType SearchType
internal SearchResult.SearchType SearchType
{
get;
private set;
@@ -25,7 +25,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the actual text we are searching for
/// </summary>
public string SearchText
internal string SearchText
{
get;
private set;
@@ -37,7 +37,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// </summary>
/// <param name="searchText">text from search</param>
/// <param name="searchType">type of search</param>
public SearchString(string searchText, SearchResult.SearchType searchType)
internal SearchString(string searchText, SearchResult.SearchType searchType)
{
SearchText = searchText;
SearchType = searchType;

View File

@@ -9,6 +9,7 @@ using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using Wox.Plugin.Common.VirtualDesktop.Helper;
using Wox.Plugin.Common.Win32;
using Wox.Plugin.Logger;
@@ -17,7 +18,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Represents a specific open window
/// </summary>
public class Window
internal class Window
{
/// <summary>
/// The handle to the window
@@ -35,10 +36,15 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// </summary>
private readonly WindowProcess processInfo;
/// <summary>
/// An instance of <see cref="VDesktop"/> that contains the desktop information for the window
/// </summary>
private readonly VDesktop desktopInfo;
/// <summary>
/// Gets the title of the window (the string displayed at the top of the window)
/// </summary>
public string Title
internal string Title
{
get
{
@@ -64,7 +70,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the handle to the window
/// </summary>
public IntPtr Hwnd
internal IntPtr Hwnd
{
get { return hwnd; }
}
@@ -72,15 +78,23 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the object of with the process information of the window
/// </summary>
public WindowProcess ProcessInfo
internal WindowProcess Process
{
get { return processInfo; }
}
/// <summary>
/// Gets the object of with the desktop information of the window
/// </summary>
internal VDesktop Desktop
{
get { return desktopInfo; }
}
/// <summary>
/// Gets the name of the class for the window represented
/// </summary>
public string ClassName
internal string ClassName
{
get
{
@@ -91,7 +105,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the window is visible (might return false if it is a hidden IE tab)
/// </summary>
public bool Visible
internal bool Visible
{
get
{
@@ -103,7 +117,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// Gets a value indicating whether the window is cloaked (true) or not (false).
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
/// </summary>
public bool IsCloaked
internal bool IsCloaked
{
get
{
@@ -114,7 +128,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the specified window handle identifies an existing window.
/// </summary>
public bool IsWindow
internal bool IsWindow
{
get
{
@@ -125,7 +139,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the window is a toolwindow
/// </summary>
public bool IsToolWindow
internal bool IsToolWindow
{
get
{
@@ -138,7 +152,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the window is an appwindow
/// </summary>
public bool IsAppWindow
internal bool IsAppWindow
{
get
{
@@ -151,7 +165,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the window has ITaskList_Deleted property
/// </summary>
public bool TaskListDeleted
internal bool TaskListDeleted
{
get
{
@@ -162,7 +176,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the specified windows is the owner (i.e. doesn't have an owner)
/// </summary>
public bool IsOwner
internal bool IsOwner
{
get
{
@@ -173,7 +187,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the window is minimized
/// </summary>
public bool Minimized
internal bool Minimized
{
get
{
@@ -186,17 +200,18 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// Initializes a new Window representation
/// </summary>
/// <param name="hwnd">the handle to the window we are representing</param>
public Window(IntPtr hwnd)
internal Window(IntPtr hwnd)
{
// TODO: Add verification as to whether the window handle is valid
this.hwnd = hwnd;
processInfo = CreateWindowProcessInstance(hwnd);
desktopInfo = Main.VirtualDesktopHelperInstance.GetWindowDesktop(hwnd);
}
/// <summary>
/// Switches desktop focus to the window
/// </summary>
public void SwitchToWindow()
internal void SwitchToWindow()
{
// The following block is necessary because
// 1) There is a weird flashing behavior when trying
@@ -219,6 +234,19 @@ namespace Microsoft.Plugin.WindowWalker.Components
NativeMethods.FlashWindow(Hwnd, true);
}
/// <summary>
/// Closes the window
/// </summary>
internal void CloseThisWindow(bool switchBeforeClose)
{
if (switchBeforeClose)
{
SwitchToWindow();
}
_ = NativeMethods.SendMessage(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_CLOSE);
}
/// <summary>
/// Converts the window name to string along with the process name
/// </summary>
@@ -233,7 +261,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// Returns what the window size is
/// </summary>
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
public WindowSizeState GetWindowSizeState()
internal WindowSizeState GetWindowSizeState()
{
NativeMethods.GetWindowPlacement(Hwnd, out WINDOWPLACEMENT placement);
@@ -255,7 +283,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Enum to simplify the state of the window
/// </summary>
public enum WindowSizeState
internal enum WindowSizeState
{
Normal,
Minimized,
@@ -268,7 +296,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
/// </summary>
/// <returns>The state (none, app, ...) of the window</returns>
public WindowCloakState GetWindowCloakState()
internal WindowCloakState GetWindowCloakState()
{
_ = NativeMethods.DwmGetWindowAttribute(Hwnd, (int)DwmWindowAttributes.Cloaked, out int isCloakedState, sizeof(uint));
@@ -279,7 +307,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
case (int)DwmWindowCloakStates.CloakedApp:
return WindowCloakState.App;
case (int)DwmWindowCloakStates.CloakedShell:
return WindowCloakState.Shell;
return Main.VirtualDesktopHelperInstance.IsWindowCloakedByVirtualDesktopManager(hwnd) ? WindowCloakState.OtherDesktop : WindowCloakState.Shell;
case (int)DwmWindowCloakStates.CloakedInherited:
return WindowCloakState.Inherited;
default:
@@ -290,12 +318,13 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Enum to simplify the cloak state of the window
/// </summary>
public enum WindowCloakState
internal enum WindowCloakState
{
None,
App,
Shell,
Inherited,
OtherDesktop,
Unknown,
}

View File

@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Wox.Infrastructure;
using Wox.Plugin.Common.Win32;
namespace Microsoft.Plugin.WindowWalker.Components
@@ -12,7 +13,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Represents the process data of an open window. This class is used in the process cache and for the process object of the open window
/// </summary>
public class WindowProcess
internal class WindowProcess
{
/// <summary>
/// Maximum size of a file name
@@ -27,7 +28,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the id of the process
/// </summary>
public uint ProcessID
internal uint ProcessID
{
get; private set;
}
@@ -35,7 +36,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the id of the thread
/// </summary>
public uint ThreadID
internal uint ThreadID
{
get; private set;
}
@@ -43,7 +44,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the name of the process
/// </summary>
public string Name
internal string Name
{
get; private set;
}
@@ -51,7 +52,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the window belongs to an 'Universal Windows Platform (UWP)' process
/// </summary>
public bool IsUwpApp
internal bool IsUwpApp
{
get { return _isUwpApp; }
}
@@ -60,7 +61,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// Gets a value indicating whether this is the shell process or not
/// The shell process (like explorer.exe) hosts parts of the user interface (like taskbar, start menu, ...)
/// </summary>
public bool IsShellProcess
internal bool IsShellProcess
{
get
{
@@ -72,7 +73,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether the process exists on the machine
/// </summary>
public bool DoesExist
internal bool DoesExist
{
get
{
@@ -98,7 +99,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets a value indicating whether full access to the process is denied or not
/// </summary>
public bool IsFullAccessDenied
internal bool IsFullAccessDenied
{
get; private set;
}
@@ -109,7 +110,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <param name="pid">New process id.</param>
/// <param name="tid">New thread id.</param>
/// <param name="name">New process name.</param>
public WindowProcess(uint pid, uint tid, string name)
internal WindowProcess(uint pid, uint tid, string name)
{
UpdateProcessInfo(pid, tid, name);
_isUwpApp = Name.ToUpperInvariant().Equals("APPLICATIONFRAMEHOST.EXE", StringComparison.Ordinal);
@@ -121,7 +122,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <param name="pid">New process id.</param>
/// <param name="tid">New thread id.</param>
/// <param name="name">New process name.</param>
public void UpdateProcessInfo(uint pid, uint tid, string name)
internal void UpdateProcessInfo(uint pid, uint tid, string name)
{
// TODO: Add verification as to wether the process id and thread id is valid
ProcessID = pid;
@@ -137,7 +138,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// </summary>
/// <param name="hwnd">The handle to the window</param>
/// <returns>The process ID</returns>
public static uint GetProcessIDFromWindowHandle(IntPtr hwnd)
internal static uint GetProcessIDFromWindowHandle(IntPtr hwnd)
{
_ = NativeMethods.GetWindowThreadProcessId(hwnd, out uint processId);
return processId;
@@ -148,7 +149,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// </summary>
/// <param name="hwnd">The handle to the window</param>
/// <returns>The thread ID</returns>
public static uint GetThreadIDFromWindowHandle(IntPtr hwnd)
internal static uint GetThreadIDFromWindowHandle(IntPtr hwnd)
{
uint threadId = NativeMethods.GetWindowThreadProcessId(hwnd, out _);
return threadId;
@@ -159,7 +160,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// </summary>
/// <param name="pid">The id of the process/param>
/// <returns>A string representing the process name or an empty string if the function fails</returns>
public static string GetProcessNameFromProcessID(uint pid)
internal static string GetProcessNameFromProcessID(uint pid)
{
IntPtr processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryLimitedInformation, true, (int)pid);
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
@@ -176,6 +177,23 @@ namespace Microsoft.Plugin.WindowWalker.Components
}
}
/// <summary>
/// Kills the process by it's id. If permissions are required, they will be requested.
/// </summary>
/// <param name="killProcessTree">Kill process and sub processes.</param>
internal void KillThisProcess(bool killProcessTree)
{
if (IsFullAccessDenied)
{
string killTree = killProcessTree ? " /t" : string.Empty;
Helper.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, true, true);
}
else
{
Process.GetProcessById((int)ProcessID).Kill(killProcessTree);
}
}
/// <summary>
/// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not.
/// </summary>

View File

@@ -0,0 +1,204 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.Plugin.WindowWalker.Properties;
using Microsoft.PowerToys.Settings.UI.Library;
[assembly: InternalsVisibleTo("Microsoft.Plugin.WindowWalker.UnitTests")]
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Additional settings for the WindowWalker plugin.
/// </summary>
/// <remarks>Some code parts reused from TimeZone plugin.</remarks>
internal sealed class WindowWalkerSettings
{
/// <summary>
/// Are the class properties initialized with default values
/// </summary>
private readonly bool _initialized;
/// <summary>
/// An instance of the class <see cref="WindowWalkerSettings"></see>
/// </summary>
private static WindowWalkerSettings instance;
/// <summary>
/// Gets a value indicating whether we only search for windows on the currently visible desktop or on all desktops.
/// </summary>
internal bool ResultsFromVisibleDesktopOnly { get; private set; }
/// <summary>
/// Gets a value indicating whether the process id is shown in the subtitle.
/// </summary>
internal bool SubtitleShowPid { get; private set; }
/// <summary>
/// Gets a value indicating whether the desktop name is shown in the subtitle.
/// We don't show the desktop name if there is only one desktop.
/// </summary>
internal bool SubtitleShowDesktopName { get; private set; }
/// <summary>
/// Gets a value indicating whether we request a confirmation when the user kills a process.
/// </summary>
internal bool ConfirmKillProcess { get; private set; }
/// <summary>
/// Gets a value indicating whether to kill the entire process tree or the selected process only.
/// </summary>
internal bool KillProcessTree { get; private set; }
/// <summary>
/// Gets a value indicating whether PowerToys run should stay open after executing killing process and closing window.
/// </summary>
internal bool OpenAfterKillAndClose { get; private set; }
/// <summary>
/// Gets a value indicating whether the "kill process" command is hidden on processes that require additional permissions (UAC).
/// </summary>
internal bool HideKillProcessOnElevatedProcesses { get; private set; }
/// <summary>
/// Gets a value indicating whether we show the explorer settings info or not.
/// </summary>
internal bool HideExplorerSettingInfo { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="WindowWalkerSettings"/> class.
/// Private constructor to make sure there is never more than one instance of this class
/// </summary>
private WindowWalkerSettings()
{
// Init class properties with default values
UpdateSettings(null);
_initialized = true;
}
/// <summary>
/// Gets an instance property of this class that makes sure that the first instance gets created
/// and that all the requests end up at that one instance.
/// The benefit of this is that we don't need additional variables/parameters
/// to communicate the settings between plugin's classes/methods.
/// We can simply access this one instance, whenever we need the actual settings.
/// </summary>
internal static WindowWalkerSettings Instance
{
get
{
if (instance == null)
{
instance = new WindowWalkerSettings();
}
return instance;
}
}
/// <summary>
/// Return a list with all additional plugin options.
/// </summary>
/// <returns>A list with all additional plugin options.</returns>
internal static List<PluginAdditionalOption> GetAdditionalOptions()
{
var optionList = new List<PluginAdditionalOption>
{
new PluginAdditionalOption
{
Key = nameof(ResultsFromVisibleDesktopOnly),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingResultsVisibleDesktop,
Value = false,
},
new PluginAdditionalOption
{
Key = nameof(SubtitleShowPid),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingSubtitlePid,
Value = false,
},
new PluginAdditionalOption
{
// ToDo: When description property is implemented (#15853), move the note in brackets to description and update to: "The information is only shown, if more than one desktop exists."
Key = nameof(SubtitleShowDesktopName),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingSubtitleDesktopName,
Value = true,
},
new PluginAdditionalOption
{
Key = nameof(ConfirmKillProcess),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingConfirmKillProcess,
Value = true,
},
new PluginAdditionalOption
{
// ToDo: When description property is implemented (#15853), add description: "Be careful when activating this."
Key = nameof(KillProcessTree),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingKillProcessTree,
Value = false,
},
new PluginAdditionalOption
{
// ToDo: When description property is implemented (#15853), move the note in brackets to description.
Key = nameof(OpenAfterKillAndClose),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingOpenAfterKillAndClose,
Value = false,
},
new PluginAdditionalOption
{
Key = nameof(HideKillProcessOnElevatedProcesses),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingHideKillProcess,
Value = false,
},
new PluginAdditionalOption
{
// ToDo: When description property is implemented (#15853), add description: "Message is only shown when searching with direct activation command."
Key = nameof(HideExplorerSettingInfo),
DisplayLabel = Resources.wox_plugin_windowwalker_SettingExplorerSettingInfo,
Value = false,
},
};
return optionList;
}
/// <summary>
/// Update this settings.
/// </summary>
/// <param name="settings">The settings for all power launcher plugins.</param>
internal void UpdateSettings(PowerLauncherPluginSettings settings)
{
if ((settings is null || settings.AdditionalOptions is null) & _initialized)
{
return;
}
ResultsFromVisibleDesktopOnly = GetSettingOrDefault(settings, nameof(ResultsFromVisibleDesktopOnly));
SubtitleShowPid = GetSettingOrDefault(settings, nameof(SubtitleShowPid));
SubtitleShowDesktopName = GetSettingOrDefault(settings, nameof(SubtitleShowDesktopName));
ConfirmKillProcess = GetSettingOrDefault(settings, nameof(ConfirmKillProcess));
KillProcessTree = GetSettingOrDefault(settings, nameof(KillProcessTree));
OpenAfterKillAndClose = GetSettingOrDefault(settings, nameof(OpenAfterKillAndClose));
HideKillProcessOnElevatedProcesses = GetSettingOrDefault(settings, nameof(HideKillProcessOnElevatedProcesses));
HideExplorerSettingInfo = GetSettingOrDefault(settings, nameof(HideExplorerSettingInfo));
}
/// <summary>
/// Return one <see cref="bool"/> setting of the given settings list with the given name.
/// </summary>
/// <param name="settings">The object that contain all settings.</param>
/// <param name="name">The name of the setting.</param>
/// <returns>A settings value.</returns>
private static bool GetSettingOrDefault(PowerLauncherPluginSettings settings, string name)
{
var option = settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == name);
// If a setting isn't available, we use the value defined in the method GetAdditionalOptions() as fallback.
// We can use First() instead of FirstOrDefault() because the values must exist. Otherwise, we made a mistake when defining the settings.
return option?.Value ?? GetAdditionalOptions().First(x => x.Key == name).Value;
}
}
}