mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +02:00
Rename the [Ee]xts dir to ext (#38852)
**WARNING:** This PR will probably blow up all in-flight PRs at some point in the early days of CmdPal, two of us created seperate `Exts` and `exts` dirs. Depending on what the casing was on the branch that you checked one of those out from, it'd get stuck like that on your PC forever. Windows didn't care, so we never noticed. But GitHub does care, and now browsing the source on GitHub is basically impossible. Closes #38081
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,34 @@
|
||||
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="3" width="16" height="10" rx="2" fill="url(#paint0_linear_1900_17890)"/>
|
||||
<g filter="url(#filter0_dd_1900_17890)">
|
||||
<path d="M14.375 10.0137H10.625C10.2798 10.0137 10 9.78981 10 9.51367V6.51367C10 6.23753 10.2798 6.01367 10.625 6.01367H14.375C14.7202 6.01367 15 6.23753 15 6.51367V9.51367C15 9.78981 14.7202 10.0137 14.375 10.0137Z" fill="url(#paint1_linear_1900_17890)"/>
|
||||
</g>
|
||||
<rect x="2" y="7.01367" width="3" height="2" rx="0.5" fill="#CAD2D9"/>
|
||||
<path d="M6 7.51367C6 7.23753 6.22386 7.01367 6.5 7.01367H8.5C8.77614 7.01367 9 7.23753 9 7.51367V8.51367C9 8.78981 8.77614 9.01367 8.5 9.01367H6.5C6.22386 9.01367 6 8.78981 6 8.51367V7.51367Z" fill="#CAD2D9"/>
|
||||
<defs>
|
||||
<filter id="filter0_dd_1900_17890" x="7" y="4.01367" width="11" height="10" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1900_17890"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_1900_17890" result="effect2_dropShadow_1900_17890"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1900_17890" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_1900_17890" x1="14.4562" y1="13.2418" x2="10.9314" y2="0.217619" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#626F7A"/>
|
||||
<stop offset="1" stop-color="#8B9299"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1900_17890" x1="13.4016" y1="12.3863" x2="8.18482" y2="9.15927" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#28AFEA"/>
|
||||
<stop offset="0.37387" stop-color="#3CCAF4"/>
|
||||
<stop offset="0.74949" stop-color="#4BDFFC"/>
|
||||
<stop offset="1" stop-color="#50E6FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class CloseWindowCommand : InvokableCommand
|
||||
{
|
||||
private readonly Window _window;
|
||||
|
||||
public CloseWindowCommand(Window window)
|
||||
{
|
||||
Icon = new IconInfo("\xE8BB");
|
||||
Name = $"{Resources.windowwalker_Close}";
|
||||
_window = window;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (!_window.IsWindow)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Cannot close the window '{_window.Title}' ({_window.Hwnd}), because it doesn't exist." });
|
||||
}
|
||||
|
||||
_window.CloseThisWindow();
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class ExplorerInfoResultCommand : InvokableCommand
|
||||
{
|
||||
public ExplorerInfoResultCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public static bool OpenInShell(string path, string? arguments = null, string? workingDir = null, ShellRunAsType runAs = ShellRunAsType.None, bool runWithHiddenWindow = false)
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo.FileName = path;
|
||||
process.StartInfo.WorkingDirectory = string.IsNullOrWhiteSpace(workingDir) ? string.Empty : workingDir;
|
||||
process.StartInfo.Arguments = string.IsNullOrWhiteSpace(arguments) ? string.Empty : arguments;
|
||||
process.StartInfo.WindowStyle = runWithHiddenWindow ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal;
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
|
||||
if (runAs == ShellRunAsType.Administrator)
|
||||
{
|
||||
process.StartInfo.Verb = "RunAs";
|
||||
}
|
||||
else if (runAs == ShellRunAsType.OtherUser)
|
||||
{
|
||||
process.StartInfo.Verb = "RunAsUser";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
return true;
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Unable to open {path}: {ex.Message}" });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
OpenInShell("rundll32.exe", "shell32.dll,Options_RunDLL 7"); // "shell32.dll,Options_RunDLL 7" opens the view tab in folder options of explorer.
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
public enum ShellRunAsType
|
||||
{
|
||||
None,
|
||||
Administrator,
|
||||
OtherUser,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class KillProcessCommand : InvokableCommand
|
||||
{
|
||||
private readonly Window _window;
|
||||
|
||||
public KillProcessCommand(Window window)
|
||||
{
|
||||
Icon = new IconInfo("\xE74D"); // Delete symbol
|
||||
Name = $"{Resources.windowwalker_Kill}";
|
||||
_window = window;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to initiate killing the process of a window
|
||||
/// </summary>
|
||||
/// <param name="window">Window data</param>
|
||||
/// <returns>True if the PT Run window should close, otherwise false.</returns>
|
||||
private static bool KillProcess(Window window)
|
||||
{
|
||||
// Validate process
|
||||
if (!window.IsWindow || !window.Process.DoesExist || string.IsNullOrEmpty(window.Process.Name) || !window.Process.Name.Equals(WindowProcess.GetProcessNameFromProcessID(window.Process.ProcessID), StringComparison.Ordinal))
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Cannot kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}), because it doesn't exist." });
|
||||
|
||||
// TODO GH #86 -- need to figure out how to show status message once implemented on host
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request user confirmation
|
||||
if (SettingsManager.Instance.ConfirmKillProcess)
|
||||
{
|
||||
// TODO GH #138, #153 -- need to figure out how to confirm kill process? should this just be the same status thing... maybe not? Need message box? Could be nested context menu.
|
||||
/*
|
||||
string messageBody = $"{Resources.wox_plugin_windowwalker_KillMessage}\n"
|
||||
+ $"{window.Process.Name} ({window.Process.ProcessID})\n\n"
|
||||
+ $"{(window.Process.IsUwpApp ? Resources.wox_plugin_windowwalker_KillMessageUwp : Resources.wox_plugin_windowwalker_KillMessageQuestion)}";
|
||||
MessageBoxResult messageBoxResult = MessageBox.Show(
|
||||
messageBody,
|
||||
Resources.wox_plugin_windowwalker_plugin_name + " - " + Resources.wox_plugin_windowwalker_KillMessageTitle,
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning);
|
||||
|
||||
if (messageBoxResult == MessageBoxResult.No)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Kill process
|
||||
window.Process.KillThisProcess(SettingsManager.Instance.KillProcessTree);
|
||||
return !SettingsManager.Instance.OpenAfterKillAndClose;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (KillProcess(_window))
|
||||
{
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.Diagnostics;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
|
||||
internal sealed partial class SwitchToWindowCommand : InvokableCommand
|
||||
{
|
||||
private readonly Window? _window;
|
||||
|
||||
public SwitchToWindowCommand(Window? window)
|
||||
{
|
||||
Name = Resources.switch_to_command_title;
|
||||
_window = window;
|
||||
if (_window != null)
|
||||
{
|
||||
var p = Process.GetProcessById((int)_window.Process.ProcessID);
|
||||
if (p != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var processFileName = p.MainModule?.FileName;
|
||||
Icon = new IconInfo(processFileName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (_window is null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "Cannot switch to the window, because it doesn't exist." });
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
_window.SwitchToWindow();
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
internal sealed class ContextMenuHelper
|
||||
{
|
||||
internal static List<CommandContextItem> GetContextMenuResults(in WindowWalkerListItem listItem)
|
||||
{
|
||||
if (listItem?.Window is not Window windowData)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var contextMenu = new List<CommandContextItem>()
|
||||
{
|
||||
new(new CloseWindowCommand(windowData))
|
||||
{
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(true, false, false, false, (int)VirtualKey.F4, 0),
|
||||
},
|
||||
};
|
||||
|
||||
// Hide menu if Explorer.exe is the shell process or the process name is ApplicationFrameHost.exe
|
||||
// In the first case we would crash the windows ui and in the second case we would kill the generic process for uwp apps.
|
||||
if (!windowData.Process.IsShellProcess && !(windowData.Process.IsUwpApp && string.Equals(windowData.Process.Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase))
|
||||
&& !(windowData.Process.IsFullAccessDenied && SettingsManager.Instance.HideKillProcessOnElevatedProcesses))
|
||||
{
|
||||
contextMenu.Add(new CommandContextItem(new KillProcessCommand(windowData))
|
||||
{
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(true, false, false, false, (int)VirtualKey.Delete, 0),
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Class housing fuzzy matching methods
|
||||
/// </summary>
|
||||
internal static class FuzzyMatching
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the best match (the one with the most
|
||||
/// number of letters adjacent to each other) and
|
||||
/// returns the index location of each of the letters
|
||||
/// of the matches
|
||||
/// </summary>
|
||||
/// <param name="text">The text to search inside of</param>
|
||||
/// <param name="searchText">the text to search for</param>
|
||||
/// <returns>returns the index location of each of the letters of the matches</returns>
|
||||
internal static List<int> FindBestFuzzyMatch(string text, string searchText)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(searchText);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(text);
|
||||
|
||||
// Using CurrentCulture since this is user facing
|
||||
searchText = searchText.ToLower(CultureInfo.CurrentCulture);
|
||||
text = text.ToLower(CultureInfo.CurrentCulture);
|
||||
|
||||
// Create a grid to march matches like
|
||||
// eg.
|
||||
// a b c a d e c f g
|
||||
// a x x
|
||||
// c x x
|
||||
var matches = new bool[text.Length, searchText.Length];
|
||||
for (var firstIndex = 0; firstIndex < text.Length; firstIndex++)
|
||||
{
|
||||
for (var secondIndex = 0; secondIndex < searchText.Length; secondIndex++)
|
||||
{
|
||||
matches[firstIndex, secondIndex] =
|
||||
searchText[secondIndex] == text[firstIndex] ?
|
||||
true :
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
||||
// use this table to get all the possible matches
|
||||
List<List<int>> allMatches = GetAllMatchIndexes(matches);
|
||||
|
||||
// return the score that is the max
|
||||
var maxScore = allMatches.Count > 0 ? CalculateScoreForMatches(allMatches[0]) : 0;
|
||||
List<int> bestMatch = allMatches.Count > 0 ? allMatches[0] : new List<int>();
|
||||
|
||||
foreach (var match in allMatches)
|
||||
{
|
||||
var score = CalculateScoreForMatches(match);
|
||||
if (score > maxScore)
|
||||
{
|
||||
bestMatch = match;
|
||||
maxScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the possible matches to the search string with in the text
|
||||
/// </summary>
|
||||
/// <param name="matches"> a table showing the matches as generated by
|
||||
/// a two dimensional array with the first dimension the text and the second
|
||||
/// one the search string and each cell marked as an intersection between the two</param>
|
||||
/// <returns>a list of the possible combinations that match the search text</returns>
|
||||
internal static List<List<int>> GetAllMatchIndexes(bool[,] matches)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(matches);
|
||||
|
||||
List<List<int>> results = new List<List<int>>();
|
||||
|
||||
for (var secondIndex = 0; secondIndex < matches.GetLength(1); secondIndex++)
|
||||
{
|
||||
for (var firstIndex = 0; firstIndex < matches.GetLength(0); firstIndex++)
|
||||
{
|
||||
if (secondIndex == 0 && matches[firstIndex, secondIndex])
|
||||
{
|
||||
results.Add(new List<int> { firstIndex });
|
||||
}
|
||||
else if (matches[firstIndex, secondIndex])
|
||||
{
|
||||
var tempList = results.Where(x => x.Count == secondIndex && x[x.Count - 1] < firstIndex).Select(x => x.ToList()).ToList();
|
||||
|
||||
foreach (var pathSofar in tempList)
|
||||
{
|
||||
pathSofar.Add(firstIndex);
|
||||
}
|
||||
|
||||
results.AddRange(tempList);
|
||||
}
|
||||
}
|
||||
|
||||
results = results.Where(x => x.Count == secondIndex + 1).ToList();
|
||||
}
|
||||
|
||||
return results.Where(x => x.Count == matches.GetLength(1)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for a string
|
||||
/// </summary>
|
||||
/// <param name="matches">the index of the matches</param>
|
||||
/// <returns>an integer representing the score</returns>
|
||||
internal static int CalculateScoreForMatches(List<int> matches)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(matches);
|
||||
|
||||
var score = 0;
|
||||
|
||||
for (var currentIndex = 1; currentIndex < matches.Count; currentIndex++)
|
||||
{
|
||||
var previousIndex = currentIndex - 1;
|
||||
|
||||
score -= matches[currentIndex] - matches[previousIndex];
|
||||
}
|
||||
|
||||
return score == 0 ? -10000 : score;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing methods to control the live preview
|
||||
/// </summary>
|
||||
internal sealed class LivePreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes sure that a window is excluded from the live preview
|
||||
/// </summary>
|
||||
/// <param name="hwnd">handle to the window to exclude</param>
|
||||
public static void SetWindowExclusionFromLivePreview(IntPtr hwnd)
|
||||
{
|
||||
var renderPolicy = (uint)DwmNCRenderingPolicies.Enabled;
|
||||
|
||||
_ = NativeMethods.DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
12,
|
||||
ref renderPolicy,
|
||||
sizeof(uint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the live preview
|
||||
/// </summary>
|
||||
/// <param name="targetWindow">the window to show by making all other windows transparent</param>
|
||||
/// <param name="windowToSpare">the window which should not be transparent but is not the target window</param>
|
||||
public static void ActivateLivePreview(IntPtr targetWindow, IntPtr windowToSpare)
|
||||
{
|
||||
_ = NativeMethods.DwmpActivateLivePreview(
|
||||
true,
|
||||
targetWindow,
|
||||
windowToSpare,
|
||||
LivePreviewTrigger.Superbar,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the live preview
|
||||
/// </summary>
|
||||
public static void DeactivateLivePreview()
|
||||
{
|
||||
_ = NativeMethods.DwmpActivateLivePreview(
|
||||
false,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
LivePreviewTrigger.AltTab,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Class that represents the state of the desktops windows
|
||||
/// </summary>
|
||||
internal sealed class OpenWindows
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to enforce single execution of EnumWindows
|
||||
/// </summary>
|
||||
private static readonly object _enumWindowsLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// PowerLauncher main executable
|
||||
/// </summary>
|
||||
private static readonly string? _powerLauncherExe = Path.GetFileName(Environment.ProcessPath);
|
||||
|
||||
/// <summary>
|
||||
/// List of all the open windows
|
||||
/// </summary>
|
||||
private readonly List<Window> windows = new();
|
||||
|
||||
/// <summary>
|
||||
/// An instance of the class OpenWindows
|
||||
/// </summary>
|
||||
private static OpenWindows? instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all open windows
|
||||
/// </summary>
|
||||
internal List<Window> Windows => new(windows);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance property of this class that makes sure that
|
||||
/// the first instance gets created and that all the requests
|
||||
/// end up at that one instance
|
||||
/// </summary>
|
||||
internal static OpenWindows Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
instance ??= new OpenWindows();
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpenWindows"/> class.
|
||||
/// Private constructor to make sure there is never
|
||||
/// more than one instance of this class
|
||||
/// </summary>
|
||||
private OpenWindows()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the list of open windows
|
||||
/// </summary>
|
||||
internal void UpdateOpenWindowsList(CancellationToken cancellationToken)
|
||||
{
|
||||
var tokenHandle = GCHandle.Alloc(cancellationToken);
|
||||
try
|
||||
{
|
||||
var tokenHandleParam = GCHandle.ToIntPtr(tokenHandle);
|
||||
lock (_enumWindowsLock)
|
||||
{
|
||||
windows.Clear();
|
||||
EnumWindowsProc callbackptr = new EnumWindowsProc(WindowEnumerationCallBack);
|
||||
_ = NativeMethods.EnumWindows(callbackptr, tokenHandleParam);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (tokenHandle.IsAllocated)
|
||||
{
|
||||
tokenHandle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call back method for window enumeration
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the current window being enumerated</param>
|
||||
/// <param name="lParam">Value being passed from the caller (we don't use this but might come in handy
|
||||
/// in the future</param>
|
||||
/// <returns>true to make sure to continue enumeration</returns>
|
||||
internal bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
|
||||
{
|
||||
var tokenHandle = GCHandle.FromIntPtr(lParam);
|
||||
var target = (CancellationToken?)tokenHandle.Target ?? CancellationToken.None;
|
||||
var cancellationToken = target;
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Stop enumeration
|
||||
return false;
|
||||
}
|
||||
|
||||
Window newWindow = new Window(hwnd);
|
||||
|
||||
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
|
||||
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
|
||||
(newWindow.Desktop.IsVisible || !SettingsManager.Instance.ResultsFromVisibleDesktopOnly || WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.GetDesktopCount() < 2) &&
|
||||
newWindow.ClassName != "Windows.UI.Core.CoreWindow" && newWindow.Process.Name != _powerLauncherExe)
|
||||
{
|
||||
// To hide (not add) preloaded uwp app windows that are invisible to the user and other cloaked windows, we check the cloak state. (Issue #13637.)
|
||||
// (If user asking to see cloaked uwp app windows again we can add an optional plugin setting in the future.)
|
||||
if (!newWindow.IsCloaked || newWindow.GetWindowCloakState() == Window.WindowCloakState.OtherDesktop)
|
||||
{
|
||||
windows.Add(newWindow);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to work with results
|
||||
/// </summary>
|
||||
internal static class ResultHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a list of all results for the query.
|
||||
/// </summary>
|
||||
/// <param name="searchControllerResults">List with all search controller matches</param>
|
||||
/// <returns>List of results</returns>
|
||||
internal static List<WindowWalkerListItem> GetResultList(List<SearchResult> searchControllerResults, bool isKeywordSearch)
|
||||
{
|
||||
if (searchControllerResults == null || searchControllerResults.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var resultsList = new List<WindowWalkerListItem>(searchControllerResults.Count);
|
||||
var addExplorerInfo = searchControllerResults.Any(x =>
|
||||
string.Equals(x.Result.Process.Name, "explorer.exe", StringComparison.OrdinalIgnoreCase) &&
|
||||
x.Result.Process.IsShellProcess);
|
||||
|
||||
// Process each SearchResult to convert it into a Result.
|
||||
// Using parallel processing if the operation is CPU-bound and the list is large.
|
||||
resultsList = searchControllerResults
|
||||
.AsParallel()
|
||||
.Select(x => CreateResultFromSearchResult(x))
|
||||
.ToList();
|
||||
|
||||
if (addExplorerInfo && !SettingsManager.Instance.HideExplorerSettingInfo)
|
||||
{
|
||||
resultsList.Insert(0, GetExplorerInfoResult());
|
||||
}
|
||||
|
||||
return resultsList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Result object from a given SearchResult.
|
||||
/// </summary>
|
||||
/// <param name="searchResult">The SearchResult object to convert.</param>
|
||||
/// <returns>A Result object populated with data from the SearchResult.</returns>
|
||||
private static WindowWalkerListItem CreateResultFromSearchResult(SearchResult searchResult)
|
||||
{
|
||||
var item = new WindowWalkerListItem(searchResult.Result)
|
||||
{
|
||||
Title = searchResult.Result.Title,
|
||||
Subtitle = GetSubtitle(searchResult.Result),
|
||||
Tags = GetTags(searchResult.Result),
|
||||
};
|
||||
item.MoreCommands = ContextMenuHelper.GetContextMenuResults(item).ToArray();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the subtitle for a result
|
||||
/// </summary>
|
||||
/// <param name="window">The window properties of the result</param>
|
||||
/// <returns>String with the subtitle</returns>
|
||||
private static string GetSubtitle(Window window)
|
||||
{
|
||||
if (window is null or null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var subtitleText = Resources.windowwalker_Running + ": " + window.Process.Name;
|
||||
|
||||
return subtitleText;
|
||||
}
|
||||
|
||||
private static Tag[] GetTags(Window window)
|
||||
{
|
||||
var tags = new List<Tag>();
|
||||
if (!window.Process.IsResponding)
|
||||
{
|
||||
tags.Add(new Tag
|
||||
{
|
||||
Text = Resources.windowwalker_NotResponding,
|
||||
Foreground = ColorHelpers.FromRgb(220, 20, 60),
|
||||
});
|
||||
}
|
||||
|
||||
if (SettingsManager.Instance.SubtitleShowPid)
|
||||
{
|
||||
tags.Add(new Tag
|
||||
{
|
||||
Text = $"{Resources.windowwalker_ProcessId}: {window.Process.ProcessID}",
|
||||
});
|
||||
}
|
||||
|
||||
if (SettingsManager.Instance.SubtitleShowDesktopName && WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.GetDesktopCount() > 1)
|
||||
{
|
||||
tags.Add(new Tag
|
||||
{
|
||||
Text = $"{Resources.windowwalker_Desktop}: {window.Desktop.Name}",
|
||||
});
|
||||
}
|
||||
|
||||
return tags.ToArray();
|
||||
}
|
||||
|
||||
private static WindowWalkerListItem GetExplorerInfoResult()
|
||||
{
|
||||
return new WindowWalkerListItem(null)
|
||||
{
|
||||
Title = Resources.windowwalker_ExplorerInfoTitle,
|
||||
Icon = new IconInfo("\uE946"), // Info
|
||||
Subtitle = Resources.windowwalker_ExplorerInfoSubTitle,
|
||||
Command = new ExplorerInfoResultCommand(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for searching and finding matches for the strings provided.
|
||||
/// Essentially the UI independent model of the application
|
||||
/// </summary>
|
||||
internal sealed class SearchController
|
||||
{
|
||||
/// <summary>
|
||||
/// the current search text
|
||||
/// </summary>
|
||||
private string searchText;
|
||||
|
||||
/// <summary>
|
||||
/// Open window search results
|
||||
/// </summary>
|
||||
private List<SearchResult>? searchMatches;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton pattern
|
||||
/// </summary>
|
||||
private static SearchController? instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current search text
|
||||
/// </summary>
|
||||
internal string SearchText
|
||||
{
|
||||
get => searchText;
|
||||
|
||||
set =>
|
||||
searchText = value.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the open window search results
|
||||
/// </summary>
|
||||
internal List<SearchResult> SearchMatches => new List<SearchResult>(searchMatches ?? []).OrderByDescending(x => x.Score).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets singleton Pattern
|
||||
/// </summary>
|
||||
internal static SearchController Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
instance ??= new SearchController();
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchController"/> class.
|
||||
/// Initializes the search controller object
|
||||
/// </summary>
|
||||
private SearchController()
|
||||
{
|
||||
searchText = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for when the search text has been updated
|
||||
/// </summary>
|
||||
internal void UpdateSearchText(string searchText)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SyncOpenWindowsWithModel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncs the open windows with the OpenWindows Model
|
||||
/// </summary>
|
||||
internal void SyncOpenWindowsWithModel()
|
||||
{
|
||||
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
|
||||
|
||||
var snapshotOfOpenWindows = OpenWindows.Instance.Windows;
|
||||
|
||||
searchMatches = string.IsNullOrWhiteSpace(SearchText) ? AllOpenWindows(snapshotOfOpenWindows) : FuzzySearchOpenWindows(snapshotOfOpenWindows);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search method that matches the title of windows with the user search text
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private List<SearchResult> FuzzySearchOpenWindows(List<Window> openWindows)
|
||||
{
|
||||
List<SearchResult> result = [];
|
||||
var searchStrings = new SearchString(searchText, SearchResult.SearchType.Fuzzy);
|
||||
|
||||
foreach (var window in openWindows)
|
||||
{
|
||||
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchStrings.SearchText);
|
||||
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.Process.Name ?? string.Empty, searchStrings.SearchText);
|
||||
|
||||
if ((titleMatch.Count != 0 || processMatch.Count != 0) && window.Title.Length != 0)
|
||||
{
|
||||
result.Add(new SearchResult(window, titleMatch, processMatch, searchStrings.SearchType));
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Print("Found " + result.Count + " windows that match the search text");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search method that matches all the windows with a title
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private List<SearchResult> AllOpenWindows(List<Window> openWindows)
|
||||
{
|
||||
List<SearchResult> result = [];
|
||||
|
||||
foreach (var window in openWindows)
|
||||
{
|
||||
if (window.Title.Length != 0)
|
||||
{
|
||||
result.Add(new SearchResult(window));
|
||||
}
|
||||
}
|
||||
|
||||
return SettingsManager.Instance.InMruOrder
|
||||
? result.ToList()
|
||||
: result
|
||||
.OrderBy(w => w.Result.Title)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for a window list update event
|
||||
/// </summary>
|
||||
internal sealed class SearchResultUpdateEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Contains search result windows with each window including the reason why the result was included
|
||||
/// </summary>
|
||||
internal sealed class SearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actual window reference for the search result
|
||||
/// </summary>
|
||||
internal Window Result
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the title window
|
||||
/// </summary>
|
||||
internal List<int> SearchMatchesInTitle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the
|
||||
/// name of the process
|
||||
/// </summary>
|
||||
internal List<int> SearchMatchesInProcessName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of match (shortcut, fuzzy or nothing)
|
||||
/// </summary>
|
||||
internal SearchType SearchResultMatchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a score indicating how well this matches what we are looking for
|
||||
/// </summary>
|
||||
internal int Score
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source of where the best score was found
|
||||
/// </summary>
|
||||
internal TextType BestScoreSource
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
internal SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = matchesInTitle;
|
||||
SearchMatchesInProcessName = matchesInProcessName;
|
||||
SearchResultMatchType = matchType;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// </summary>
|
||||
internal SearchResult(Window window)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = new List<int>();
|
||||
SearchMatchesInProcessName = new List<int>();
|
||||
SearchResultMatchType = SearchType.Empty;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for how closely this window matches the search string
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Higher Score is better
|
||||
/// </remarks>
|
||||
private void CalculateScore()
|
||||
{
|
||||
if (FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName) >
|
||||
FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle))
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName);
|
||||
BestScoreSource = TextType.ProcessName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle);
|
||||
BestScoreSource = TextType.WindowTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of text that a string represents
|
||||
/// </summary>
|
||||
internal enum TextType
|
||||
{
|
||||
ProcessName,
|
||||
WindowTitle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of search
|
||||
/// </summary>
|
||||
internal enum SearchType
|
||||
{
|
||||
/// <summary>
|
||||
/// the search string is empty, which means all open windows are
|
||||
/// going to be returned
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// Regular fuzzy match search
|
||||
/// </summary>
|
||||
Fuzzy,
|
||||
|
||||
/// <summary>
|
||||
/// The user has entered text that has been matched to a shortcut
|
||||
/// and the shortcut is now being searched
|
||||
/// </summary>
|
||||
Shortcut,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A class to represent a search string
|
||||
/// </summary>
|
||||
/// <remarks>Class was added inorder to be able to attach various context data to
|
||||
/// a search string</remarks>
|
||||
internal sealed class SearchString
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets where is the search string coming from (is it a shortcut
|
||||
/// or direct string, etc...)
|
||||
/// </summary>
|
||||
internal SearchResult.SearchType SearchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual text we are searching for
|
||||
/// </summary>
|
||||
internal string SearchText
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchString"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="searchText">text from search</param>
|
||||
/// <param name="searchType">type of search</param>
|
||||
internal SearchString(string searchText, SearchResult.SearchType searchType)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SearchType = searchType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a specific open window
|
||||
/// </summary>
|
||||
internal sealed class Window
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle to the window
|
||||
/// </summary>
|
||||
private readonly IntPtr hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// A static cache for the process data of all known windows
|
||||
/// that we don't have to query the data every time
|
||||
/// </summary>
|
||||
private static readonly Dictionary<IntPtr, WindowProcess> _handlesToProcessCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// An instance of <see cref="WindowProcess"/> that contains the process information for the window
|
||||
/// </summary>
|
||||
private readonly WindowProcess processInfo;
|
||||
|
||||
/// <summary>
|
||||
/// An instance of <see cref="VDesktop"/> that contains the desktop information for the window
|
||||
/// </summary>
|
||||
private readonly VDesktop desktopInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the window (the string displayed at the top of the window)
|
||||
/// </summary>
|
||||
internal string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
var sizeOfTitle = NativeMethods.GetWindowTextLength(hwnd);
|
||||
if (sizeOfTitle++ > 0)
|
||||
{
|
||||
StringBuilder titleBuffer = new StringBuilder(sizeOfTitle);
|
||||
var numCharactersWritten = NativeMethods.GetWindowText(hwnd, titleBuffer, sizeOfTitle);
|
||||
if (numCharactersWritten == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return titleBuffer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the window
|
||||
/// </summary>
|
||||
internal IntPtr Hwnd => hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of with the process information of the window
|
||||
/// </summary>
|
||||
internal WindowProcess Process => processInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of with the desktop information of the window
|
||||
/// </summary>
|
||||
internal VDesktop Desktop => desktopInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the class for the window represented
|
||||
/// </summary>
|
||||
internal string ClassName => GetWindowClassName(Hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is visible (might return false if it is a hidden IE tab)
|
||||
/// </summary>
|
||||
internal bool Visible => NativeMethods.IsWindowVisible(Hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is cloaked (true) or not (false).
|
||||
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
|
||||
/// </summary>
|
||||
internal bool IsCloaked => GetWindowCloakState() != WindowCloakState.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified window handle identifies an existing window.
|
||||
/// </summary>
|
||||
internal bool IsWindow => NativeMethods.IsWindow(Hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is a toolwindow
|
||||
/// </summary>
|
||||
internal bool IsToolWindow => (NativeMethods.GetWindowLong(Hwnd, Win32Constants.GWL_EXSTYLE) &
|
||||
(uint)ExtendedWindowStyles.WS_EX_TOOLWINDOW) ==
|
||||
(uint)ExtendedWindowStyles.WS_EX_TOOLWINDOW;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is an appwindow
|
||||
/// </summary>
|
||||
internal bool IsAppWindow => (NativeMethods.GetWindowLong(Hwnd, Win32Constants.GWL_EXSTYLE) &
|
||||
(uint)ExtendedWindowStyles.WS_EX_APPWINDOW) ==
|
||||
(uint)ExtendedWindowStyles.WS_EX_APPWINDOW;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window has ITaskList_Deleted property
|
||||
/// </summary>
|
||||
internal bool TaskListDeleted => NativeMethods.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified windows is the owner (i.e. doesn't have an owner)
|
||||
/// </summary>
|
||||
internal bool IsOwner => NativeMethods.GetWindow(Hwnd, GetWindowCmd.GW_OWNER) == IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window is minimized
|
||||
/// </summary>
|
||||
internal bool Minimized => GetWindowSizeState() == WindowSizeState.Minimized;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Window"/> class.
|
||||
/// Initializes a new Window representation
|
||||
/// </summary>
|
||||
/// <param name="hwnd">the handle to the window we are representing</param>
|
||||
internal Window(IntPtr hwnd)
|
||||
{
|
||||
// TODO: Add verification as to whether the window handle is valid
|
||||
this.hwnd = hwnd;
|
||||
processInfo = CreateWindowProcessInstance(hwnd);
|
||||
desktopInfo = WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.GetWindowDesktop(hwnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches desktop focus to the window
|
||||
/// </summary>
|
||||
internal void SwitchToWindow()
|
||||
{
|
||||
// The following block is necessary because
|
||||
// 1) There is a weird flashing behavior when trying
|
||||
// to use ShowWindow for switching tabs in IE
|
||||
// 2) SetForegroundWindow fails on minimized windows
|
||||
// Using Ordinal since this is internal
|
||||
if (processInfo.Name?.ToUpperInvariant().Equals("IEXPLORE.EXE", StringComparison.Ordinal) == true || !Minimized)
|
||||
{
|
||||
NativeMethods.SetForegroundWindow(Hwnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!NativeMethods.ShowWindow(Hwnd, ShowWindowCommand.Restore))
|
||||
{
|
||||
// ShowWindow doesn't work if the process is running elevated: fallback to SendMessage
|
||||
_ = NativeMethods.SendMessage(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_RESTORE);
|
||||
}
|
||||
}
|
||||
|
||||
NativeMethods.FlashWindow(Hwnd, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to close the window
|
||||
/// </summary>
|
||||
internal void CloseThisWindowHelper()
|
||||
{
|
||||
_ = NativeMethods.SendMessageTimeout(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_CLOSE, 0, 0x0000, 5000, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the window
|
||||
/// </summary>
|
||||
internal void CloseThisWindow()
|
||||
{
|
||||
Thread thread = new(new ThreadStart(CloseThisWindowHelper));
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the window name to string along with the process name
|
||||
/// </summary>
|
||||
/// <returns>The title of the window</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
// Using CurrentCulture since this is user facing
|
||||
return Title + " (" + processInfo.Name?.ToUpper(CultureInfo.CurrentCulture) + ")";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns what the window size is
|
||||
/// </summary>
|
||||
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
|
||||
internal WindowSizeState GetWindowSizeState()
|
||||
{
|
||||
NativeMethods.GetWindowPlacement(Hwnd, out WINDOWPLACEMENT placement);
|
||||
|
||||
switch (placement.ShowCmd)
|
||||
{
|
||||
case ShowWindowCommand.Normal:
|
||||
return WindowSizeState.Normal;
|
||||
case ShowWindowCommand.Minimize:
|
||||
case ShowWindowCommand.ShowMinimized:
|
||||
return WindowSizeState.Minimized;
|
||||
case ShowWindowCommand.Maximize: // No need for ShowMaximized here since its also of value 3
|
||||
return WindowSizeState.Maximized;
|
||||
default:
|
||||
// throw new Exception("Don't know how to handle window state = " + placement.ShowCmd);
|
||||
return WindowSizeState.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to simplify the state of the window
|
||||
/// </summary>
|
||||
internal enum WindowSizeState
|
||||
{
|
||||
Normal,
|
||||
Minimized,
|
||||
Maximized,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the window cloak state from DWM
|
||||
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
|
||||
/// </summary>
|
||||
/// <returns>The state (none, app, ...) of the window</returns>
|
||||
internal WindowCloakState GetWindowCloakState()
|
||||
{
|
||||
_ = NativeMethods.DwmGetWindowAttribute(Hwnd, (int)DwmWindowAttributes.Cloaked, out var isCloakedState, sizeof(uint));
|
||||
|
||||
switch (isCloakedState)
|
||||
{
|
||||
case (int)DwmWindowCloakStates.None:
|
||||
return WindowCloakState.None;
|
||||
case (int)DwmWindowCloakStates.CloakedApp:
|
||||
return WindowCloakState.App;
|
||||
case (int)DwmWindowCloakStates.CloakedShell:
|
||||
return WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.IsWindowCloakedByVirtualDesktopManager(hwnd, Desktop.Id) ? WindowCloakState.OtherDesktop : WindowCloakState.Shell;
|
||||
case (int)DwmWindowCloakStates.CloakedInherited:
|
||||
return WindowCloakState.Inherited;
|
||||
default:
|
||||
return WindowCloakState.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to simplify the cloak state of the window
|
||||
/// </summary>
|
||||
internal enum WindowCloakState
|
||||
{
|
||||
None,
|
||||
App,
|
||||
Shell,
|
||||
Inherited,
|
||||
OtherDesktop,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the class name of a window.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">Handle to the window.</param>
|
||||
/// <returns>Class name</returns>
|
||||
private static string GetWindowClassName(IntPtr hwnd)
|
||||
{
|
||||
StringBuilder windowClassName = new StringBuilder(300);
|
||||
var numCharactersWritten = NativeMethods.GetClassName(hwnd, windowClassName, windowClassName.MaxCapacity);
|
||||
|
||||
if (numCharactersWritten == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return windowClassName.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of <see cref="WindowProcess"/> form process cache or creates a new one. A new one will be added to the cache.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">The handle to the window</param>
|
||||
/// <returns>A new Instance of type <see cref="WindowProcess"/></returns>
|
||||
private static WindowProcess CreateWindowProcessInstance(IntPtr hWindow)
|
||||
{
|
||||
lock (_handlesToProcessCache)
|
||||
{
|
||||
if (_handlesToProcessCache.Count > 7000)
|
||||
{
|
||||
Debug.Print("Clearing Process Cache because it's size is " + _handlesToProcessCache.Count);
|
||||
_handlesToProcessCache.Clear();
|
||||
}
|
||||
|
||||
// Add window's process to cache if missing
|
||||
if (!_handlesToProcessCache.ContainsKey(hWindow))
|
||||
{
|
||||
// Get process ID and name
|
||||
var processId = WindowProcess.GetProcessIDFromWindowHandle(hWindow);
|
||||
var threadId = WindowProcess.GetThreadIDFromWindowHandle(hWindow);
|
||||
var processName = WindowProcess.GetProcessNameFromProcessID(processId);
|
||||
|
||||
if (processName.Length != 0)
|
||||
{
|
||||
_handlesToProcessCache.Add(hWindow, new WindowProcess(processId, threadId, processName));
|
||||
}
|
||||
else
|
||||
{
|
||||
// For the dwm process we cannot receive the name. This is no problem because the window isn't part of result list.
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Invalid process {processId} ({processName}) for window handle {hWindow}." });
|
||||
_handlesToProcessCache.Add(hWindow, new WindowProcess(0, 0, string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
// Correct the process data if the window belongs to a uwp app hosted by 'ApplicationFrameHost.exe'
|
||||
// (This only works if the window isn't minimized. For minimized windows the required child window isn't assigned.)
|
||||
if (string.Equals(_handlesToProcessCache[hWindow].Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
new Task(() =>
|
||||
{
|
||||
EnumWindowsProc callbackptr = new EnumWindowsProc((IntPtr hwnd, IntPtr lParam) =>
|
||||
{
|
||||
// Every uwp app main window has at least three child windows. Only the one we are interested in has a class starting with "Windows.UI.Core." and is assigned to the real app process.
|
||||
// (The other ones have a class name that begins with the string "ApplicationFrame".)
|
||||
if (GetWindowClassName(hwnd).StartsWith("Windows.UI.Core.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var childProcessId = WindowProcess.GetProcessIDFromWindowHandle(hwnd);
|
||||
var childThreadId = WindowProcess.GetThreadIDFromWindowHandle(hwnd);
|
||||
var childProcessName = WindowProcess.GetProcessNameFromProcessID(childProcessId);
|
||||
|
||||
// Update process info in cache
|
||||
_handlesToProcessCache[hWindow].UpdateProcessInfo(childProcessId, childThreadId, childProcessName);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
_ = NativeMethods.EnumChildWindows(hWindow, callbackptr, 0);
|
||||
}).Start();
|
||||
}
|
||||
|
||||
return _handlesToProcessCache[hWindow];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the process data of an open window. This class is used in the process cache and for the process object of the open window
|
||||
/// </summary>
|
||||
internal sealed class WindowProcess
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum size of a file name
|
||||
/// </summary>
|
||||
private const int MaximumFileNameLength = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// An indicator if the window belongs to an 'Universal Windows Platform (UWP)' process
|
||||
/// </summary>
|
||||
private readonly bool _isUwpApp;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the id of the process
|
||||
/// </summary>
|
||||
internal uint ProcessID
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the process is responding or not
|
||||
/// </summary>
|
||||
internal bool IsResponding
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return Process.GetProcessById((int)ProcessID).Responding;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Thrown when process not exist.
|
||||
return true;
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// Thrown when process is not running locally.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the id of the thread
|
||||
/// </summary>
|
||||
internal uint ThreadID
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the process
|
||||
/// </summary>
|
||||
internal string? Name
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the window belongs to an 'Universal Windows Platform (UWP)' process
|
||||
/// </summary>
|
||||
internal bool IsUwpApp => _isUwpApp;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this is the shell process or not
|
||||
/// The shell process (like explorer.exe) hosts parts of the user interface (like taskbar, start menu, ...)
|
||||
/// </summary>
|
||||
internal bool IsShellProcess
|
||||
{
|
||||
get
|
||||
{
|
||||
var hShellWindow = NativeMethods.GetShellWindow();
|
||||
return GetProcessIDFromWindowHandle(hShellWindow) == ProcessID;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the process exists on the machine
|
||||
/// </summary>
|
||||
internal bool DoesExist
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = Process.GetProcessById((int)ProcessID);
|
||||
p.Dispose();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Thrown when process not exist.
|
||||
return false;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Thrown when process not exist.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether full access to the process is denied or not
|
||||
/// </summary>
|
||||
internal bool IsFullAccessDenied
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowProcess"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pid">New process id.</param>
|
||||
/// <param name="tid">New thread id.</param>
|
||||
/// <param name="name">New process name.</param>
|
||||
internal WindowProcess(uint pid, uint tid, string name)
|
||||
{
|
||||
UpdateProcessInfo(pid, tid, name);
|
||||
_isUwpApp = string.Equals(Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the process information of the <see cref="WindowProcess"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="pid">New process id.</param>
|
||||
/// <param name="tid">New thread id.</param>
|
||||
/// <param name="name">New process name.</param>
|
||||
internal void UpdateProcessInfo(uint pid, uint tid, string name)
|
||||
{
|
||||
// TODO: Add verification as to whether the process id and thread id is valid
|
||||
ProcessID = pid;
|
||||
ThreadID = tid;
|
||||
Name = name;
|
||||
|
||||
// Process can be elevated only if process id is not 0 (Dummy value on error)
|
||||
IsFullAccessDenied = (pid != 0) ? TestProcessAccessUsingAllAccessFlag(pid) : false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process ID for the window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The process ID</returns>
|
||||
internal static uint GetProcessIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
_ = NativeMethods.GetWindowThreadProcessId(hwnd, out var processId);
|
||||
return processId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the thread ID for the window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The thread ID</returns>
|
||||
internal static uint GetThreadIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
var threadId = NativeMethods.GetWindowThreadProcessId(hwnd, out _);
|
||||
return threadId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process name for the process ID
|
||||
/// </summary>
|
||||
/// <param name="pid">The id of the process/param>
|
||||
/// <returns>A string representing the process name or an empty string if the function fails</returns>
|
||||
internal static string GetProcessNameFromProcessID(uint pid)
|
||||
{
|
||||
var processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryLimitedInformation, true, (int)pid);
|
||||
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
|
||||
|
||||
if (NativeMethods.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0)
|
||||
{
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return processName.ToString().Split('\\').Reverse().ToArray()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the process by it's id. If permissions are required, they will be requested.
|
||||
/// </summary>
|
||||
/// <param name="killProcessTree">Kill process and sub processes.</param>
|
||||
internal void KillThisProcess(bool killProcessTree)
|
||||
{
|
||||
if (IsFullAccessDenied)
|
||||
{
|
||||
var killTree = killProcessTree ? " /t" : string.Empty;
|
||||
ExplorerInfoResultCommand.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, ExplorerInfoResultCommand.ShellRunAsType.Administrator, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.GetProcessById((int)ProcessID).Kill(killProcessTree);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not.
|
||||
/// </summary>
|
||||
/// <param name="pid">The process ID of the process</param>
|
||||
/// <returns>True if denied and false if not.</returns>
|
||||
private static bool TestProcessAccessUsingAllAccessFlag(uint pid)
|
||||
{
|
||||
var processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.AllAccess, true, (int)pid);
|
||||
|
||||
if (Win32Helpers.GetLastError() == 5)
|
||||
{
|
||||
// Error 5 = ERROR_ACCESS_DENIED
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = Win32Helpers.CloseHandleIfNotNull(processHandle);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Virtual Desktop Manager class
|
||||
/// Code used from <see href="https://learn.microsoft.com/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10"./>
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
|
||||
internal class CVirtualDesktopManager
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for accessing Virtual Desktop Manager.
|
||||
/// Code used from <see href="https://learn.microsoft.com/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10"./>
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
internal interface IVirtualDesktopManager
|
||||
{
|
||||
[PreserveSig]
|
||||
int IsWindowOnCurrentVirtualDesktop([In] IntPtr hTopLevelWindow, [Out] out int onCurrentDesktop);
|
||||
|
||||
[PreserveSig]
|
||||
int GetWindowDesktopId([In] IntPtr hTopLevelWindow, [Out] out Guid desktop);
|
||||
|
||||
[PreserveSig]
|
||||
int MoveWindowToDesktop([In] IntPtr hTopLevelWindow, [MarshalAs(UnmanagedType.LPStruct)][In] Guid desktop);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public static class OSVersionHelper
|
||||
{
|
||||
public static bool IsWindows11()
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 22000;
|
||||
}
|
||||
|
||||
public static bool IsGreaterThanWindows11_21H2()
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build > 22000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// 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.IO;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public class SettingsManager : JsonSettingsManager
|
||||
{
|
||||
private static readonly string _namespace = "windowWalker";
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
private static SettingsManager? instance;
|
||||
|
||||
private readonly ToggleSetting _resultsFromVisibleDesktopOnly = new(
|
||||
Namespaced(nameof(ResultsFromVisibleDesktopOnly)),
|
||||
Resources.windowwalker_SettingResultsVisibleDesktop,
|
||||
Resources.windowwalker_SettingResultsVisibleDesktop,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _subtitleShowPid = new(
|
||||
Namespaced(nameof(SubtitleShowPid)),
|
||||
Resources.windowwalker_SettingTagPid,
|
||||
Resources.windowwalker_SettingTagPid,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _subtitleShowDesktopName = new(
|
||||
Namespaced(nameof(SubtitleShowDesktopName)),
|
||||
Resources.windowwalker_SettingTagDesktopName,
|
||||
Resources.windowwalker_SettingSubtitleDesktopName_Description,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _confirmKillProcess = new(
|
||||
Namespaced(nameof(ConfirmKillProcess)),
|
||||
Resources.windowwalker_SettingConfirmKillProcess,
|
||||
Resources.windowwalker_SettingConfirmKillProcess,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _killProcessTree = new(
|
||||
Namespaced(nameof(KillProcessTree)),
|
||||
Resources.windowwalker_SettingKillProcessTree,
|
||||
Resources.windowwalker_SettingKillProcessTree_Description,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _openAfterKillAndClose = new(
|
||||
Namespaced(nameof(OpenAfterKillAndClose)),
|
||||
Resources.windowwalker_SettingOpenAfterKillAndClose,
|
||||
Resources.windowwalker_SettingOpenAfterKillAndClose_Description,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _hideKillProcessOnElevatedProcesses = new(
|
||||
Namespaced(nameof(HideKillProcessOnElevatedProcesses)),
|
||||
Resources.windowwalker_SettingHideKillProcess,
|
||||
Resources.windowwalker_SettingHideKillProcess,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _hideExplorerSettingInfo = new(
|
||||
Namespaced(nameof(HideExplorerSettingInfo)),
|
||||
Resources.windowwalker_SettingExplorerSettingInfo,
|
||||
Resources.windowwalker_SettingExplorerSettingInfo_Description,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _inMruOrder = new(
|
||||
Namespaced(nameof(InMruOrder)),
|
||||
Resources.windowwalker_SettingInMruOrder,
|
||||
Resources.windowwalker_SettingInMruOrder_Description,
|
||||
true);
|
||||
|
||||
public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value;
|
||||
|
||||
public bool SubtitleShowPid => _subtitleShowPid.Value;
|
||||
|
||||
public bool SubtitleShowDesktopName => _subtitleShowDesktopName.Value;
|
||||
|
||||
public bool ConfirmKillProcess => _confirmKillProcess.Value;
|
||||
|
||||
public bool KillProcessTree => _killProcessTree.Value;
|
||||
|
||||
public bool OpenAfterKillAndClose => _openAfterKillAndClose.Value;
|
||||
|
||||
public bool HideKillProcessOnElevatedProcesses => _hideKillProcessOnElevatedProcesses.Value;
|
||||
|
||||
public bool HideExplorerSettingInfo => _hideExplorerSettingInfo.Value;
|
||||
|
||||
public bool InMruOrder => _inMruOrder.Value;
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
Settings.Add(_resultsFromVisibleDesktopOnly);
|
||||
Settings.Add(_subtitleShowPid);
|
||||
Settings.Add(_subtitleShowDesktopName);
|
||||
Settings.Add(_confirmKillProcess);
|
||||
Settings.Add(_killProcessTree);
|
||||
Settings.Add(_openAfterKillAndClose);
|
||||
Settings.Add(_hideKillProcessOnElevatedProcesses);
|
||||
Settings.Add(_hideExplorerSettingInfo);
|
||||
Settings.Add(_inMruOrder);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (s, a) => this.SaveSettings();
|
||||
}
|
||||
|
||||
internal static SettingsManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
instance ??= new SettingsManager();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public static class ShellCommand
|
||||
{
|
||||
public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
|
||||
|
||||
private static bool containsSecurityWindow;
|
||||
|
||||
public static Process? RunAsDifferentUser(ProcessStartInfo processStartInfo)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(processStartInfo);
|
||||
|
||||
processStartInfo.Verb = "RunAsUser";
|
||||
var process = Process.Start(processStartInfo);
|
||||
|
||||
containsSecurityWindow = false;
|
||||
|
||||
// wait for windows to bring up the "Windows Security" dialog
|
||||
while (!containsSecurityWindow)
|
||||
{
|
||||
CheckSecurityWindow();
|
||||
Thread.Sleep(25);
|
||||
}
|
||||
|
||||
// while this process contains a "Windows Security" dialog, stay open
|
||||
while (containsSecurityWindow)
|
||||
{
|
||||
containsSecurityWindow = false;
|
||||
CheckSecurityWindow();
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
private static void CheckSecurityWindow()
|
||||
{
|
||||
ProcessThreadCollection ptc = Process.GetCurrentProcess().Threads;
|
||||
for (var i = 0; i < ptc.Count; i++)
|
||||
{
|
||||
NativeMethods.EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CheckSecurityThread(IntPtr hwnd, IntPtr lParam)
|
||||
{
|
||||
if (GetWindowTitle(hwnd) == "Windows Security")
|
||||
{
|
||||
containsSecurityWindow = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string GetWindowTitle(IntPtr hwnd)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(NativeMethods.GetWindowTextLength(hwnd) + 1);
|
||||
_ = NativeMethods.GetWindowText(hwnd, sb, sb.Capacity);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "")
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
WorkingDirectory = workingDirectory,
|
||||
Arguments = arguments,
|
||||
Verb = verb,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Class that represents a Virtual Desktop
|
||||
/// </summary>
|
||||
/// <remarks>This class is named VDesktop to make clear it isn't an instance of the original Desktop class from Virtual Desktop Manager.
|
||||
/// We can't use the original one, because therefore we must access private com interfaces. We aren't allowed to do this, because this is an official Microsoft project.</remarks>
|
||||
public class VDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the guid of the desktop
|
||||
/// </summary>
|
||||
public Guid Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the desktop
|
||||
/// </summary>
|
||||
public string? Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number (position) of the desktop
|
||||
/// </summary>
|
||||
public int Number
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the desktop is currently visible to the user
|
||||
/// </summary>
|
||||
public bool IsVisible
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the desktop guid belongs to the generic "AllDesktops" view.
|
||||
/// This view hold all windows that are pinned to all desktops.
|
||||
/// </summary>
|
||||
public bool IsAllDesktopsView
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the position of a desktop in the list of all desktops
|
||||
/// </summary>
|
||||
public VirtualDesktopPosition Position
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty instance of <see cref="VDesktop"/>
|
||||
/// </summary>
|
||||
public static VDesktop Empty => new()
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Name = string.Empty,
|
||||
Number = 0,
|
||||
IsVisible = true, // Setting this always to true to simulate a visible desktop
|
||||
IsAllDesktopsView = false,
|
||||
Position = VirtualDesktopPosition.NotApplicable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to work with Virtual Desktops.
|
||||
/// This helper uses only public available and documented COM-Interfaces or information from registry.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this helper you have to create an instance of it and access the method via the helper instance.
|
||||
/// We are only allowed to use public documented com interfaces.
|
||||
/// </remarks>
|
||||
/// <SeeAlso href="https://learn.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ivirtualdesktopmanager">Documentation of IVirtualDesktopManager interface</SeeAlso>
|
||||
/// <SeeAlso href="https://learn.microsoft.com/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10">CSharp example code for IVirtualDesktopManager</SeeAlso>
|
||||
public class VirtualDesktopHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Are we running on Windows 11
|
||||
/// </summary>
|
||||
private readonly bool _isWindowsEleven;
|
||||
|
||||
/// <summary>
|
||||
/// Instance of "Virtual Desktop Manager"
|
||||
/// </summary>
|
||||
private readonly IVirtualDesktopManager? _virtualDesktopManager;
|
||||
|
||||
/// <summary>
|
||||
/// Internal settings to enable automatic update of desktop list.
|
||||
/// This will be off by default to avoid to many registry queries.
|
||||
/// </summary>
|
||||
private readonly bool _desktopListAutoUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// List of all available Virtual Desktop in their real order
|
||||
/// The order and list in the registry is always up to date
|
||||
/// </summary>
|
||||
private readonly List<Guid> _availableDesktops = [];
|
||||
|
||||
/// <summary>
|
||||
/// Id of the current visible Desktop.
|
||||
/// </summary>
|
||||
private Guid _currentDesktop;
|
||||
|
||||
private static readonly CompositeFormat VirtualDesktopHelperDesktop = System.Text.CompositeFormat.Parse(Properties.Resources.VirtualDesktopHelper_Desktop);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VirtualDesktopHelper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="desktopListUpdate">Setting to configure if the list of available desktops should update automatically or only when calling <see cref="UpdateDesktopList"/>. Per default this is set to manual update (false) to have less registry queries.</param>
|
||||
public VirtualDesktopHelper(bool desktopListUpdate = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
_virtualDesktopManager = (IVirtualDesktopManager)new CVirtualDesktopManager();
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Initialization of <VirtualDesktopHelper> failed: An exception was thrown when creating the instance of COM interface <IVirtualDesktopManager>. {ex} " });
|
||||
return;
|
||||
}
|
||||
|
||||
_isWindowsEleven = OSVersionHelper.IsWindows11();
|
||||
_desktopListAutoUpdate = desktopListUpdate;
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Virtual Desktop Manager is initialized successfully
|
||||
/// </summary>
|
||||
public bool VirtualDesktopManagerInitialized => _virtualDesktopManager != null;
|
||||
|
||||
/// <summary>
|
||||
/// Method to update the list of Virtual Desktops from Registry
|
||||
/// The data in the registry are always up to date
|
||||
/// </summary>
|
||||
/// <remarks>If we cannot read from registry, we set the list/guid to empty values.</remarks>
|
||||
public void UpdateDesktopList()
|
||||
{
|
||||
var userSessionId = Process.GetCurrentProcess().SessionId;
|
||||
var registrySessionVirtualDesktops = $"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SessionInfo\\{userSessionId}\\VirtualDesktops"; // Windows 10
|
||||
var registryExplorerVirtualDesktops = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops"; // Windows 11
|
||||
|
||||
// List of all desktops
|
||||
using RegistryKey? virtualDesktopKey = Registry.CurrentUser.OpenSubKey(registryExplorerVirtualDesktops, false);
|
||||
if (virtualDesktopKey != null)
|
||||
{
|
||||
var allDeskValue = (byte[]?)virtualDesktopKey.GetValue("VirtualDesktopIDs", null) ?? Array.Empty<byte>();
|
||||
if (allDeskValue != null)
|
||||
{
|
||||
// We clear only, if we can read from registry. Otherwise we keep the existing values.
|
||||
_availableDesktops.Clear();
|
||||
|
||||
// Each guid has a length of 16 elements
|
||||
var numberOfDesktops = allDeskValue.Length / 16;
|
||||
for (var i = 0; i < numberOfDesktops; i++)
|
||||
{
|
||||
var guidArray = new byte[16];
|
||||
Array.ConstrainedCopy(allDeskValue, i * 16, guidArray, 0, 16);
|
||||
_availableDesktops.Add(new Guid(guidArray));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.UpdateDesktopList() failed to read the list of existing desktops form registry." });
|
||||
}
|
||||
}
|
||||
|
||||
// Guid for current desktop
|
||||
var virtualDesktopsKeyName = _isWindowsEleven ? registryExplorerVirtualDesktops : registrySessionVirtualDesktops;
|
||||
using RegistryKey? virtualDesktopsKey = Registry.CurrentUser.OpenSubKey(virtualDesktopsKeyName, false);
|
||||
if (virtualDesktopsKey != null)
|
||||
{
|
||||
var currentVirtualDesktopValue = virtualDesktopsKey.GetValue("CurrentVirtualDesktop", null);
|
||||
if (currentVirtualDesktopValue != null)
|
||||
{
|
||||
_currentDesktop = new Guid((byte[])currentVirtualDesktopValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The registry value is missing when the user hasn't switched the desktop at least one time before reading the registry. In this case we can set it to desktop one.
|
||||
// We can only set it to desktop one, if we have at least one desktop in the desktops list. Otherwise we keep the existing value.
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.UpdateDesktopList() failed to read the id for the current desktop form registry." });
|
||||
_currentDesktop = _availableDesktops.Count >= 1 ? _availableDesktops[0] : _currentDesktop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ordered list with the ids of all existing desktops. The list is ordered in the same way as the existing desktops.
|
||||
/// </summary>
|
||||
/// <returns>List of desktop ids or an empty list on failure.</returns>
|
||||
public List<Guid> GetDesktopIdList()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return _availableDesktops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ordered list with of all existing desktops and their properties. The list is ordered in the same way as the existing desktops.
|
||||
/// </summary>
|
||||
/// <returns>List of desktops or an empty list on failure.</returns>
|
||||
public List<VDesktop> GetDesktopList()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
List<VDesktop> list = new List<VDesktop>();
|
||||
foreach (Guid d in _availableDesktops)
|
||||
{
|
||||
list.Add(CreateVDesktopInstance(d));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the count of existing desktops
|
||||
/// </summary>
|
||||
/// <returns>Number of existing desktops or zero on failure.</returns>
|
||||
public int GetDesktopCount()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return _availableDesktops.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the id of the desktop that is currently visible to the user.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Guid"/> of the current desktop. Or <see cref="Guid.Empty"/> on failure and if we don't know the current desktop.</returns>
|
||||
public Guid GetCurrentDesktopId()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return _currentDesktop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of <see cref="VDesktop"/> for the desktop that is currently visible to the user.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="VDesktop"/> for the current desktop, or an empty instance of <see cref="VDesktop"/> on failure.</returns>
|
||||
public VDesktop GetCurrentDesktop()
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return CreateVDesktopInstance(_currentDesktop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a desktop is currently visible to the user.
|
||||
/// </summary>
|
||||
/// <param name="desktop">The guid of the desktop to check.</param>
|
||||
/// <returns><see langword="True"/> if the guid belongs to the currently visible desktop. <see langword="False"/> if not or if we don't know the visible desktop.</returns>
|
||||
public bool IsDesktopVisible(Guid desktop)
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
return _currentDesktop == desktop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number (position) of a desktop.
|
||||
/// </summary>
|
||||
/// <param name="desktop">The guid of the desktop.</param>
|
||||
/// <returns>Number of the desktop, if found. Otherwise a value of zero.</returns>
|
||||
public int GetDesktopNumber(Guid desktop)
|
||||
{
|
||||
if (_desktopListAutoUpdate)
|
||||
{
|
||||
UpdateDesktopList();
|
||||
}
|
||||
|
||||
// Adding +1 because index starts with zero and humans start counting with one.
|
||||
return _availableDesktops.IndexOf(desktop) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of a desktop
|
||||
/// </summary>
|
||||
/// <param name="desktop">Guid of the desktop</param>
|
||||
/// <returns>Returns the name of the desktop or <see cref="string.Empty"/> on failure.</returns>
|
||||
public string GetDesktopName(Guid desktop)
|
||||
{
|
||||
if (desktop == Guid.Empty || !GetDesktopIdList().Contains(desktop))
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.GetDesktopName() failed. Parameter contains an invalid desktop guid ({desktop}) that doesn't belongs to an available desktop. Maybe the guid belongs to the generic 'AllDesktops' view." });
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// If the desktop name was not changed by the user, it isn't saved to the registry. Then we need the default name for the desktop.
|
||||
var defaultName = string.Format(System.Globalization.CultureInfo.InvariantCulture, VirtualDesktopHelperDesktop, GetDesktopNumber(desktop));
|
||||
|
||||
var registryPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops\\Desktops\\{" + desktop.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "}";
|
||||
using RegistryKey? deskSubKey = Registry.CurrentUser.OpenSubKey(registryPath, false);
|
||||
var desktopName = deskSubKey?.GetValue("Name");
|
||||
|
||||
return (desktopName != null) ? (string)desktopName : defaultName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the position type for a desktop.
|
||||
/// </summary>
|
||||
/// <param name="desktop">Guid of the desktop.</param>
|
||||
/// <returns>Type of <see cref="VirtualDesktopPosition"/>. On failure we return <see cref="VirtualDesktopPosition.Unknown"/>.</returns>
|
||||
public VirtualDesktopPosition GetDesktopPositionType(Guid desktop)
|
||||
{
|
||||
var desktopNumber = GetDesktopNumber(desktop);
|
||||
var desktopCount = GetDesktopCount();
|
||||
|
||||
if (desktopCount == 0 || desktop == Guid.Empty)
|
||||
{
|
||||
// On failure or empty guid
|
||||
return VirtualDesktopPosition.NotApplicable;
|
||||
}
|
||||
else if (desktopNumber == 1)
|
||||
{
|
||||
return VirtualDesktopPosition.FirstDesktop;
|
||||
}
|
||||
else if (desktopNumber == desktopCount)
|
||||
{
|
||||
return VirtualDesktopPosition.LastDesktop;
|
||||
}
|
||||
else if (desktopNumber > 1 & desktopNumber < desktopCount)
|
||||
{
|
||||
return VirtualDesktopPosition.BetweenOtherDesktops;
|
||||
}
|
||||
else
|
||||
{
|
||||
// All desktops view or a guid that doesn't belong to an existing desktop
|
||||
return VirtualDesktopPosition.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the desktop id for a window.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <param name="desktopId">The guid of the desktop, where the window is shown.</param>
|
||||
/// <returns>HResult of the called method as integer.</returns>
|
||||
public int GetWindowDesktopId(IntPtr hWindow, out Guid desktopId)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktopId() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
desktopId = Guid.Empty;
|
||||
return unchecked((int)HRESULT.E_UNEXPECTED);
|
||||
}
|
||||
|
||||
return _virtualDesktopManager.GetWindowDesktopId(hWindow, out desktopId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of <see cref="VDesktop"/> for the desktop where the window is assigned to.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <returns>An instance of <see cref="VDesktop"/> for the desktop where the window is assigned to, or an empty instance of <see cref="VDesktop"/> on failure.</returns>
|
||||
public VDesktop GetWindowDesktop(IntPtr hWindow)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktop() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
return CreateVDesktopInstance(Guid.Empty);
|
||||
}
|
||||
|
||||
var hr = _virtualDesktopManager.GetWindowDesktopId(hWindow, out Guid desktopId);
|
||||
return (hr != (int)HRESULT.S_OK || desktopId == Guid.Empty) ? VDesktop.Empty : CreateVDesktopInstance(desktopId, hWindow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the desktop assignment type for a window.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <param name="desktop">Optional the desktop id if known</param>
|
||||
/// <returns>Type of <see cref="VirtualDesktopAssignmentType"/>.</returns>
|
||||
public VirtualDesktopAssignmentType GetWindowDesktopAssignmentType(IntPtr hWindow, Guid? desktop = null)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.GetWindowDesktopAssignmentType() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
return VirtualDesktopAssignmentType.Unknown;
|
||||
}
|
||||
|
||||
_ = _virtualDesktopManager.IsWindowOnCurrentVirtualDesktop(hWindow, out var isOnCurrentDesktop);
|
||||
Guid windowDesktopId = desktop ?? Guid.Empty; // Prepare variable in case we have no input parameter for desktop
|
||||
var hResult = desktop is null ? GetWindowDesktopId(hWindow, out windowDesktopId) : 0;
|
||||
|
||||
if (hResult != (int)HRESULT.S_OK)
|
||||
{
|
||||
return VirtualDesktopAssignmentType.Unknown;
|
||||
}
|
||||
else if (windowDesktopId == Guid.Empty)
|
||||
{
|
||||
return VirtualDesktopAssignmentType.NotAssigned;
|
||||
}
|
||||
else if (isOnCurrentDesktop == 1 && !GetDesktopIdList().Contains(windowDesktopId))
|
||||
{
|
||||
// These windows are marked as visible on the current desktop, but the desktop id doesn't belongs to an existing desktop.
|
||||
// In this case the desktop id belongs to the generic view 'AllDesktops'.
|
||||
return VirtualDesktopAssignmentType.AllDesktops;
|
||||
}
|
||||
else if (isOnCurrentDesktop == 1)
|
||||
{
|
||||
return VirtualDesktopAssignmentType.CurrentDesktop;
|
||||
}
|
||||
else
|
||||
{
|
||||
return VirtualDesktopAssignmentType.OtherDesktop;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the window is assigned to a currently visible desktop.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle to the top level window.</param>
|
||||
/// <param name="desktop">Optional the desktop id if known</param>
|
||||
/// <returns><see langword="True"/> if the desktop with the window is visible or if the window is assigned to all desktops. <see langword="False"/> if the desktop is not visible and on failure,</returns>
|
||||
public bool IsWindowOnVisibleDesktop(IntPtr hWindow, Guid? desktop = null)
|
||||
{
|
||||
return GetWindowDesktopAssignmentType(hWindow, desktop) == VirtualDesktopAssignmentType.CurrentDesktop || GetWindowDesktopAssignmentType(hWindow, desktop) == VirtualDesktopAssignmentType.AllDesktops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the window is cloaked by VirtualDesktopManager.
|
||||
/// (A cloaked window is not visible to the user. But the window is still composed by DWM.)
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the window.</param>
|
||||
/// <param name="desktop">Optional the desktop id if known</param>
|
||||
/// <returns>A value indicating if the window is cloaked by Virtual Desktop Manager, because it is moved to another desktop.</returns>
|
||||
public bool IsWindowCloakedByVirtualDesktopManager(IntPtr hWindow, Guid? desktop = null)
|
||||
{
|
||||
// If a window is hidden because it is moved to another desktop, then DWM returns type "CloakedShell". If DWM returns another type the window is not cloaked by shell or VirtualDesktopManager.
|
||||
_ = NativeMethods.DwmGetWindowAttribute(hWindow, (int)DwmWindowAttributes.Cloaked, out var dwmCloakedState, sizeof(uint));
|
||||
return GetWindowDesktopAssignmentType(hWindow, desktop) == VirtualDesktopAssignmentType.OtherDesktop && dwmCloakedState == (int)DwmWindowCloakStates.CloakedShell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the window to a specific desktop.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the top level window.</param>
|
||||
/// <param name="desktopId">Guid of the target desktop.</param>
|
||||
/// <returns><see langword="True"/> on success and <see langword="false"/> on failure.</returns>
|
||||
public bool MoveWindowToDesktop(IntPtr hWindow, in Guid desktopId)
|
||||
{
|
||||
if (_virtualDesktopManager == null)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.MoveWindowToDesktop() failed: The instance of <IVirtualDesktopHelper> isn't available." });
|
||||
return false;
|
||||
}
|
||||
|
||||
var hr = _virtualDesktopManager.MoveWindowToDesktop(hWindow, desktopId);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.MoveWindowToDesktop() failed: An exception was thrown when moving the window ({hWindow}) to another desktop ({desktopId})." });
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a window one desktop left.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the top level window.</param>
|
||||
/// <returns><see langword="True"/> on success and <see langword="false"/> on failure.</returns>
|
||||
public bool MoveWindowOneDesktopLeft(IntPtr hWindow)
|
||||
{
|
||||
var hr = GetWindowDesktopId(hWindow, out Guid windowDesktop);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopLeft() failed when moving the window ({hWindow}) one desktop left: Can't get current desktop of the window." });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetDesktopIdList().Count == 0 || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.Unknown || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.NotAssigned)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopLeft() failed when moving the window ({hWindow}) one desktop left: We can't find the target desktop. This can happen if the desktop list is empty or if the window isn't assigned to a specific desktop." });
|
||||
return false;
|
||||
}
|
||||
|
||||
var windowDesktopNumber = GetDesktopIdList().IndexOf(windowDesktop);
|
||||
if (windowDesktopNumber == 1)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopLeft() failed when moving the window ({hWindow}) one desktop left: The window is on the first desktop." });
|
||||
return false;
|
||||
}
|
||||
|
||||
Guid newDesktop = _availableDesktops[windowDesktopNumber - 1];
|
||||
return MoveWindowToDesktop(hWindow, newDesktop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a window one desktop right.
|
||||
/// </summary>
|
||||
/// <param name="hWindow">Handle of the top level window.</param>
|
||||
/// <returns><see langword="True"/> on success and <see langword="false"/> on failure.</returns>
|
||||
public bool MoveWindowOneDesktopRight(IntPtr hWindow)
|
||||
{
|
||||
var hr = GetWindowDesktopId(hWindow, out Guid windowDesktop);
|
||||
if (hr != (int)HRESULT.S_OK)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopRight() failed when moving the window ({hWindow}) one desktop right: Can't get current desktop of the window." });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetDesktopIdList().Count == 0 || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.Unknown || GetWindowDesktopAssignmentType(hWindow, windowDesktop) == VirtualDesktopAssignmentType.NotAssigned)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopRight() failed when moving the window ({hWindow}) one desktop right: We can't find the target desktop. This can happen if the desktop list is empty or if the window isn't assigned to a specific desktop." });
|
||||
return false;
|
||||
}
|
||||
|
||||
var windowDesktopNumber = GetDesktopIdList().IndexOf(windowDesktop);
|
||||
if (windowDesktopNumber == GetDesktopCount())
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"VirtualDesktopHelper.MoveWindowOneDesktopRight() failed when moving the window ({hWindow}) one desktop right: The window is on the last desktop." });
|
||||
return false;
|
||||
}
|
||||
|
||||
Guid newDesktop = _availableDesktops[windowDesktopNumber + 1];
|
||||
return MoveWindowToDesktop(hWindow, newDesktop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of <see cref="VDesktop"/> for a Guid.
|
||||
/// </summary>
|
||||
/// <param name="desktop">Guid of the desktop.</param>
|
||||
/// <param name="hWindow">Handle of the window shown on the desktop. If this parameter is set we can detect if it is the AllDesktops view.</param>
|
||||
/// <returns>A <see cref="VDesktop"/> instance. If the parameter desktop is <see cref="Guid.Empty"/>, we return an empty <see cref="VDesktop"/> instance.</returns>
|
||||
private VDesktop CreateVDesktopInstance(Guid desktop, IntPtr hWindow = default)
|
||||
{
|
||||
if (desktop == Guid.Empty)
|
||||
{
|
||||
return VDesktop.Empty;
|
||||
}
|
||||
|
||||
// Can be only detected if method is invoked with window handle parameter.
|
||||
VirtualDesktopAssignmentType desktopType = (hWindow != default) ? GetWindowDesktopAssignmentType(hWindow, desktop) : VirtualDesktopAssignmentType.Unknown;
|
||||
var isAllDesktops = (hWindow != default) && desktopType == VirtualDesktopAssignmentType.AllDesktops;
|
||||
var isDesktopVisible = (hWindow != default) ? (isAllDesktops || desktopType == VirtualDesktopAssignmentType.CurrentDesktop) : IsDesktopVisible(desktop);
|
||||
|
||||
return new VDesktop()
|
||||
{
|
||||
Id = desktop,
|
||||
Name = isAllDesktops ? Resources.VirtualDesktopHelper_AllDesktops : GetDesktopName(desktop),
|
||||
Number = GetDesktopNumber(desktop),
|
||||
IsVisible = isDesktopVisible || isAllDesktops,
|
||||
IsAllDesktopsView = isAllDesktops,
|
||||
Position = GetDesktopPositionType(desktop),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to show in which way a window is assigned to a desktop
|
||||
/// </summary>
|
||||
public enum VirtualDesktopAssignmentType
|
||||
{
|
||||
Unknown = -1,
|
||||
NotAssigned = 0,
|
||||
AllDesktops = 1,
|
||||
CurrentDesktop = 2,
|
||||
OtherDesktop = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to show the position of a desktop in the list of all desktops
|
||||
/// </summary>
|
||||
public enum VirtualDesktopPosition
|
||||
{
|
||||
FirstDesktop,
|
||||
BetweenOtherDesktops,
|
||||
LastDesktop,
|
||||
NotApplicable, // If not applicable or unknown
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||
|
||||
public static class Win32Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects the type of system firmware which is equal to the boot type by calling the method <see cref="NativeMethods.GetFirmwareType"/>.
|
||||
/// </summary>
|
||||
/// <returns>Firmware type like Uefi or Bios.</returns>
|
||||
public static FirmwareType GetSystemFirmwareType()
|
||||
{
|
||||
FirmwareType firmwareType = default;
|
||||
_ = NativeMethods.GetFirmwareType(ref firmwareType);
|
||||
return firmwareType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last Win32 Error code thrown by a native method if enabled for this method.
|
||||
/// </summary>
|
||||
/// <returns>The error code as int value.</returns>
|
||||
public static int GetLastError()
|
||||
{
|
||||
return Marshal.GetLastPInvokeError();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the handle is not null and close it.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle to close.</param>
|
||||
/// <returns>Zero if native method fails and nonzero if the native method succeeds.</returns>
|
||||
public static bool CloseHandleIfNotNull(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
// Return true if there is nothing to close.
|
||||
return true;
|
||||
}
|
||||
|
||||
return NativeMethods.CloseHandle(handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description for an HRESULT error code.
|
||||
/// </summary>
|
||||
/// <param name="hr">The HRESULT number</param>
|
||||
/// <returns>A string containing the description.</returns>
|
||||
public static string MessageFromHResult(int hr)
|
||||
{
|
||||
return Marshal.GetExceptionForHR(hr)?.Message ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.WindowWalker</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\WindowWalker.svg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\WindowWalker.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\WindowWalker.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Images\windowwalker.dark.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\windowwalker.light.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Pages;
|
||||
|
||||
internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposable
|
||||
{
|
||||
private System.Threading.CancellationTokenSource _cancellationTokenSource = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public WindowWalkerListPage()
|
||||
{
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\WindowWalker.svg");
|
||||
Name = Resources.windowwalker_name;
|
||||
Id = "com.microsoft.cmdpal.windowwalker";
|
||||
PlaceholderText = Resources.windowwalker_PlaceholderText;
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch) =>
|
||||
RaiseItemsChanged(0);
|
||||
|
||||
public List<WindowWalkerListItem> Query(string query)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new System.Threading.CancellationTokenSource();
|
||||
|
||||
WindowWalkerCommandsProvider.VirtualDesktopHelperInstance.UpdateDesktopList();
|
||||
OpenWindows.Instance.UpdateOpenWindowsList(_cancellationTokenSource.Token);
|
||||
SearchController.Instance.UpdateSearchText(query);
|
||||
var searchControllerResults = SearchController.Instance.SearchMatches;
|
||||
|
||||
return ResultHelper.GetResultList(searchControllerResults, !string.IsNullOrEmpty(query));
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => Query(SearchText).ToArray();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
396
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
generated
Normal file
396
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,396 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.WindowWalker.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Switch to.
|
||||
/// </summary>
|
||||
public static string switch_to_command_title {
|
||||
get {
|
||||
return ResourceManager.GetString("switch_to_command_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to On all Desktops.
|
||||
/// </summary>
|
||||
public static string VirtualDesktopHelper_AllDesktops {
|
||||
get {
|
||||
return ResourceManager.GetString("VirtualDesktopHelper_AllDesktops", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Desktop {0}.
|
||||
/// </summary>
|
||||
public static string VirtualDesktopHelper_Desktop {
|
||||
get {
|
||||
return ResourceManager.GetString("VirtualDesktopHelper_Desktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Switch between open windows.
|
||||
/// </summary>
|
||||
public static string window_walker_top_level_command_title {
|
||||
get {
|
||||
return ResourceManager.GetString("window_walker_top_level_command_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Close window.
|
||||
/// </summary>
|
||||
public static string windowwalker_Close {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Close", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Desktop.
|
||||
/// </summary>
|
||||
public static string windowwalker_Desktop {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Desktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Folder windows do not run in separate processes. (Click to open Explorer properties.).
|
||||
/// </summary>
|
||||
public static string windowwalker_ExplorerInfoSubTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_ExplorerInfoSubTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Info: Killing the Explorer process isn't possible..
|
||||
/// </summary>
|
||||
public static string windowwalker_ExplorerInfoTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_ExplorerInfoTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process.
|
||||
/// </summary>
|
||||
public static string windowwalker_Kill {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Kill", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your are going to kill the following process:.
|
||||
/// </summary>
|
||||
public static string windowwalker_KillMessage {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_KillMessage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Continue?.
|
||||
/// </summary>
|
||||
public static string windowwalker_KillMessageQuestion {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_KillMessageQuestion", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process confirmation.
|
||||
/// </summary>
|
||||
public static string windowwalker_KillMessageTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_KillMessageTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Because this is an app process, all instances of the app will be killed. Continue?.
|
||||
/// </summary>
|
||||
public static string windowwalker_KillMessageUwp {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_KillMessageUwp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Window Walker.
|
||||
/// </summary>
|
||||
public static string windowwalker_name {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Not Responding.
|
||||
/// </summary>
|
||||
public static string windowwalker_NotResponding {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_NotResponding", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No..
|
||||
/// </summary>
|
||||
public static string windowwalker_Number {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Number", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to pid.
|
||||
/// </summary>
|
||||
public static string windowwalker_pid {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_pid", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search open windows....
|
||||
/// </summary>
|
||||
public static string windowwalker_PlaceholderText {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_PlaceholderText", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Switches between open windows.
|
||||
/// </summary>
|
||||
public static string windowwalker_plugin_description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_plugin_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Process name.
|
||||
/// </summary>
|
||||
public static string windowwalker_Process {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Process", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Process id.
|
||||
/// </summary>
|
||||
public static string windowwalker_ProcessId {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_ProcessId", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Running.
|
||||
/// </summary>
|
||||
public static string windowwalker_Running {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_Running", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Request confirmation when killing a process.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingConfirmKillProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingConfirmKillProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide Explorer process information.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingExplorerSettingInfo {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingExplorerSettingInfo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This is an optional message that informs users about explorer behavior.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingExplorerSettingInfo_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingExplorerSettingInfo_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide "kill process" button if additional permissions required.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingHideKillProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingHideKillProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show windows in most-recently-used order.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingInMruOrder {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingInMruOrder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to When disabled, windows will be sorted by title.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingInMruOrder_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingInMruOrder_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Kill process and its child processes.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingKillProcessTree {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingKillProcessTree", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Be careful when activating this. Killing the whole process tree can lead to problematic application crashes..
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingKillProcessTree_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingKillProcessTree_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Stay open after closing windows and killing processes.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingOpenAfterKillAndClose {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingOpenAfterKillAndClose", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This feature won't work if the kill process confirmation is enabled..
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingOpenAfterKillAndClose_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingOpenAfterKillAndClose_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show only results from visible desktop.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingResultsVisibleDesktop {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingResultsVisibleDesktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Settings.
|
||||
/// </summary>
|
||||
public static string windowwalker_settings_name {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_settings_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This information is only shown in subtitle and tool tip, if you have at least two desktops..
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingSubtitleDesktopName_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingSubtitleDesktopName_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show desktop as a tag on the list item.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingTagDesktopName {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingTagDesktopName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show process id as a tag on the list item.
|
||||
/// </summary>
|
||||
public static string windowwalker_SettingTagPid {
|
||||
get {
|
||||
return ResourceManager.GetString("windowwalker_SettingTagPid", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="windowwalker_name" xml:space="preserve">
|
||||
<value>Window Walker</value>
|
||||
</data>
|
||||
<data name="windowwalker_plugin_description" xml:space="preserve">
|
||||
<value>Switches between open windows</value>
|
||||
</data>
|
||||
<data name="windowwalker_Running" xml:space="preserve">
|
||||
<value>Running</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingConfirmKillProcess" xml:space="preserve">
|
||||
<value>Request confirmation when killing a process</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingExplorerSettingInfo" xml:space="preserve">
|
||||
<value>Hide Explorer process information</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="windowwalker_SettingHideKillProcess" xml:space="preserve">
|
||||
<value>Hide "kill process" button if additional permissions required</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingResultsVisibleDesktop" xml:space="preserve">
|
||||
<value>Show only results from visible desktop</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingTagDesktopName" xml:space="preserve">
|
||||
<value>Show desktop as a tag on the list item</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingTagPid" xml:space="preserve">
|
||||
<value>Show process id as a tag on the list item</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingOpenAfterKillAndClose" xml:space="preserve">
|
||||
<value>Stay open after closing windows and killing processes</value>
|
||||
</data>
|
||||
<data name="windowwalker_Desktop" xml:space="preserve">
|
||||
<value>Desktop</value>
|
||||
</data>
|
||||
<data name="windowwalker_Number" xml:space="preserve">
|
||||
<value>No.</value>
|
||||
<comment>Short version of "Number"</comment>
|
||||
</data>
|
||||
<data name="windowwalker_Process" xml:space="preserve">
|
||||
<value>Process name</value>
|
||||
</data>
|
||||
<data name="windowwalker_ProcessId" xml:space="preserve">
|
||||
<value>Process id</value>
|
||||
</data>
|
||||
<data name="windowwalker_ExplorerInfoSubTitle" xml:space="preserve">
|
||||
<value>Folder windows do not run in separate processes. (Click to open Explorer properties.)</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="windowwalker_ExplorerInfoTitle" xml:space="preserve">
|
||||
<value>Info: Killing the Explorer process isn't possible.</value>
|
||||
<comment>Explorer is here the program File Explorer</comment>
|
||||
</data>
|
||||
<data name="windowwalker_Close" xml:space="preserve">
|
||||
<value>Close window</value>
|
||||
</data>
|
||||
<data name="windowwalker_Kill" xml:space="preserve">
|
||||
<value>Kill process</value>
|
||||
</data>
|
||||
<data name="windowwalker_KillMessage" xml:space="preserve">
|
||||
<value>Your are going to kill the following process:</value>
|
||||
</data>
|
||||
<data name="windowwalker_KillMessageQuestion" xml:space="preserve">
|
||||
<value>Continue?</value>
|
||||
</data>
|
||||
<data name="windowwalker_KillMessageTitle" xml:space="preserve">
|
||||
<value>Kill process confirmation</value>
|
||||
</data>
|
||||
<data name="windowwalker_KillMessageUwp" xml:space="preserve">
|
||||
<value>Because this is an app process, all instances of the app will be killed. Continue?</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingKillProcessTree" xml:space="preserve">
|
||||
<value>Kill process and its child processes</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingExplorerSettingInfo_Description" xml:space="preserve">
|
||||
<value>This is an optional message that informs users about explorer behavior</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingKillProcessTree_Description" xml:space="preserve">
|
||||
<value>Be careful when activating this. Killing the whole process tree can lead to problematic application crashes.</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingOpenAfterKillAndClose_Description" xml:space="preserve">
|
||||
<value>This feature won't work if the kill process confirmation is enabled.</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingSubtitleDesktopName_Description" xml:space="preserve">
|
||||
<value>This information is only shown in subtitle and tool tip, if you have at least two desktops.</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingInMruOrder" xml:space="preserve">
|
||||
<value>Show windows in most-recently-used order</value>
|
||||
</data>
|
||||
<data name="windowwalker_SettingInMruOrder_Description" xml:space="preserve">
|
||||
<value>When disabled, windows will be sorted by title</value>
|
||||
</data>
|
||||
<data name="windowwalker_NotResponding" xml:space="preserve">
|
||||
<value>Not Responding</value>
|
||||
</data>
|
||||
<data name="window_walker_top_level_command_title" xml:space="preserve">
|
||||
<value>Switch between open windows</value>
|
||||
</data>
|
||||
<data name="switch_to_command_title" xml:space="preserve">
|
||||
<value>Switch to</value>
|
||||
</data>
|
||||
<data name="VirtualDesktopHelper_AllDesktops" xml:space="preserve">
|
||||
<value>On all Desktops</value>
|
||||
</data>
|
||||
<data name="VirtualDesktopHelper_Desktop" xml:space="preserve">
|
||||
<value>Desktop {0}</value>
|
||||
</data>
|
||||
<data name="windowwalker_settings_name" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="windowwalker_pid" xml:space="preserve">
|
||||
<value>pid</value>
|
||||
</data>
|
||||
<data name="windowwalker_PlaceholderText" xml:space="preserve">
|
||||
<value>Search open windows...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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.Pages;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker;
|
||||
|
||||
public partial class WindowWalkerCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly CommandItem _windowWalkerPageItem;
|
||||
|
||||
internal static readonly VirtualDesktopHelper VirtualDesktopHelperInstance = new();
|
||||
|
||||
public WindowWalkerCommandsProvider()
|
||||
{
|
||||
Id = "WindowWalker";
|
||||
DisplayName = Resources.windowwalker_name;
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\WindowWalker.svg");
|
||||
Settings = SettingsManager.Instance.Settings;
|
||||
|
||||
_windowWalkerPageItem = new CommandItem(new WindowWalkerListPage())
|
||||
{
|
||||
Title = Resources.window_walker_top_level_command_title,
|
||||
Subtitle = Resources.windowwalker_name,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(Settings.SettingsPage),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_windowWalkerPageItem];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowWalker;
|
||||
|
||||
internal sealed partial class WindowWalkerListItem : ListItem
|
||||
{
|
||||
private readonly Window? _window;
|
||||
|
||||
public Window? Window => _window;
|
||||
|
||||
public WindowWalkerListItem(Window? window)
|
||||
: base(new SwitchToWindowCommand(window))
|
||||
{
|
||||
_window = window;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user