diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 9115249393..4d7cc47d3c 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -639,6 +639,7 @@ Hiber
Hiberboot
HIBYTE
hicon
+HICONSM
HIDEREADONLY
HIDEWINDOW
Hif
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs
index e8271da371..cbbe365a1b 100644
--- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs
@@ -17,6 +17,7 @@ public class Settings : ISettingsInterface
private readonly bool hideKillProcessOnElevatedProcesses;
private readonly bool hideExplorerSettingInfo;
private readonly bool inMruOrder;
+ private readonly bool useWindowIcon;
public Settings(
bool resultsFromVisibleDesktopOnly = false,
@@ -27,7 +28,8 @@ public class Settings : ISettingsInterface
bool openAfterKillAndClose = false,
bool hideKillProcessOnElevatedProcesses = false,
bool hideExplorerSettingInfo = true,
- bool inMruOrder = true)
+ bool inMruOrder = true,
+ bool useWindowIcon = true)
{
this.resultsFromVisibleDesktopOnly = resultsFromVisibleDesktopOnly;
this.subtitleShowPid = subtitleShowPid;
@@ -38,6 +40,7 @@ public class Settings : ISettingsInterface
this.hideKillProcessOnElevatedProcesses = hideKillProcessOnElevatedProcesses;
this.hideExplorerSettingInfo = hideExplorerSettingInfo;
this.inMruOrder = inMruOrder;
+ this.useWindowIcon = useWindowIcon;
}
public bool ResultsFromVisibleDesktopOnly => resultsFromVisibleDesktopOnly;
@@ -57,4 +60,6 @@ public class Settings : ISettingsInterface
public bool HideExplorerSettingInfo => hideExplorerSettingInfo;
public bool InMruOrder => inMruOrder;
+
+ public bool UseWindowIcon => useWindowIcon;
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs
index 695eaa2c83..c215d0f300 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/SwitchToWindowCommand.cs
@@ -2,11 +2,16 @@
// 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.Drawing;
+using System.IO;
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;
+using Windows.Storage.Streams;
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
@@ -16,20 +21,53 @@ internal sealed partial class SwitchToWindowCommand : InvokableCommand
public SwitchToWindowCommand(Window? window)
{
+ Icon = Icons.GenericAppIcon; // Fallback to default icon
Name = Resources.switch_to_command_title;
_window = window;
if (_window is not null)
{
- var p = Process.GetProcessById((int)_window.Process.ProcessID);
- if (p is not null)
+ // Use window icon
+ if (SettingsManager.Instance.UseWindowIcon)
{
- try
+ if (_window.TryGetWindowIcon(out var icon) && icon is not null)
{
- var processFileName = p.MainModule?.FileName;
- Icon = new IconInfo(processFileName);
+ try
+ {
+ using var bitmap = icon.ToBitmap();
+ using var memoryStream = new MemoryStream();
+ bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
+ var raStream = new InMemoryRandomAccessStream();
+ using var outputStream = raStream.GetOutputStreamAt(0);
+ using var dataWriter = new DataWriter(outputStream);
+ dataWriter.WriteBytes(memoryStream.ToArray());
+ dataWriter.StoreAsync().AsTask().Wait();
+ dataWriter.FlushAsync().AsTask().Wait();
+ Icon = IconInfo.FromStream(raStream);
+ }
+ catch
+ {
+ }
+ finally
+ {
+ icon.Dispose();
+ }
}
- catch
+ }
+
+ // Use process icon
+ else
+ {
+ var p = Process.GetProcessById((int)_window.Process.ProcessID);
+ if (p is not null)
{
+ try
+ {
+ var processFileName = p.MainModule?.FileName;
+ Icon = new IconInfo(processFileName);
+ }
+ catch
+ {
+ }
}
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs
index 1dc43600c2..c071d55e80 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/Window.cs
@@ -188,6 +188,62 @@ internal sealed class Window
thread.Start();
}
+ ///
+ /// Tries to get the window icon.
+ ///
+ /// The window icon if found; otherwise, null.
+ /// True if an icon was found; otherwise, false.
+ internal bool TryGetWindowIcon(out System.Drawing.Icon? icon)
+ {
+ icon = null;
+
+ if (hwnd == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ // Try WM_GETICON with SendMessageTimeout
+ if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_BIG, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out var result) != 0 && result != 0)
+ {
+ icon = System.Drawing.Icon.FromHandle((IntPtr)result);
+ NativeMethods.DestroyIcon((IntPtr)result);
+ return true;
+ }
+
+ if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_SMALL, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out result) != 0 && result != 0)
+ {
+ icon = System.Drawing.Icon.FromHandle((IntPtr)result);
+ NativeMethods.DestroyIcon((IntPtr)result);
+ return true;
+ }
+
+ if (NativeMethods.SendMessageTimeout(hwnd, Win32Constants.WM_GETICON, (UIntPtr)Win32Constants.ICON_SMALL2, IntPtr.Zero, Win32Constants.SMTO_ABORTIFHUNG, 100, out result) != 0 && result != 0)
+ {
+ icon = System.Drawing.Icon.FromHandle((IntPtr)result);
+ NativeMethods.DestroyIcon((IntPtr)result);
+ return true;
+ }
+
+ // Fallback to GetClassLongPtr
+ var iconHandle = NativeMethods.GetClassLongPtr(hwnd, Win32Constants.GCLP_HICON);
+ if (iconHandle != IntPtr.Zero)
+ {
+ icon = System.Drawing.Icon.FromHandle(iconHandle);
+ NativeMethods.DestroyIcon((IntPtr)iconHandle);
+ return true;
+ }
+
+ iconHandle = NativeMethods.GetClassLongPtr(hwnd, Win32Constants.GCLP_HICONSM);
+ if (iconHandle != IntPtr.Zero)
+ {
+ icon = System.Drawing.Icon.FromHandle(iconHandle);
+ NativeMethods.DestroyIcon((IntPtr)iconHandle);
+ return true;
+ }
+
+ return false;
+ }
+
///
/// Converts the window name to string along with the process name
///
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs
index e77acb56cf..de3827c76a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs
@@ -23,4 +23,6 @@ public interface ISettingsInterface
public bool HideExplorerSettingInfo { get; }
public bool InMruOrder { get; }
+
+ public bool UseWindowIcon { get; }
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs
index 382d1a56d1..57d65a305e 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs
@@ -84,6 +84,12 @@ public static partial class NativeMethods
[DllImport("user32.dll")]
public static extern int SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam, int fuFlags, int uTimeout, out int lpdwResult);
+ [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
+ public static extern IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex);
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)]
+ internal static extern bool DestroyIcon(IntPtr hIcon);
+
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
@@ -143,6 +149,41 @@ public static class Win32Constants
///
public const int SC_CLOSE = 0xF060;
+ ///
+ /// Sent to a window to retrieve a handle to the large or small icon associated with a window.
+ ///
+ public const uint WM_GETICON = 0x007F;
+
+ ///
+ /// Retrieve the large icon for the window.
+ ///
+ public const int ICON_BIG = 1;
+
+ ///
+ /// Retrieve the small icon for the window.
+ ///
+ public const int ICON_SMALL = 0;
+
+ ///
+ /// Retrieve the small icon provided by the application.
+ ///
+ public const int ICON_SMALL2 = 2;
+
+ ///
+ /// The function returns if the receiving thread does not respond within the timeout period.
+ ///
+ public const int SMTO_ABORTIFHUNG = 0x0002;
+
+ ///
+ /// Retrieves a handle to the icon associated with the class.
+ ///
+ public const int GCLP_HICON = -14;
+
+ ///
+ /// Retrieves a handle to the small icon associated with the class.
+ ///
+ public const int GCLP_HICONSM = -34;
+
///
/// RPC call succeeded
///
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs
index b2a248beca..1b223fea9b 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs
@@ -70,6 +70,12 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
Resources.windowwalker_SettingInMruOrder_Description,
true);
+ private readonly ToggleSetting _useWindowIcon = new(
+ Namespaced(nameof(UseWindowIcon)),
+ Resources.windowwalker_SettingUseWindowIcon,
+ Resources.windowwalker_SettingUseWindowIcon_Description,
+ true);
+
public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value;
public bool SubtitleShowPid => _subtitleShowPid.Value;
@@ -88,6 +94,8 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
public bool InMruOrder => _inMruOrder.Value;
+ public bool UseWindowIcon => _useWindowIcon.Value;
+
internal static string SettingsJsonPath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
@@ -110,6 +118,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
Settings.Add(_hideKillProcessOnElevatedProcesses);
Settings.Add(_hideExplorerSettingInfo);
Settings.Add(_inMruOrder);
+ Settings.Add(_useWindowIcon);
// Load settings from file upon initialization
LoadSettings();
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs
index bfcb47f428..dd2ae9b1cb 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Icons.cs
@@ -15,4 +15,6 @@ internal sealed class Icons
internal static IconInfo CloseWindow { get; } = new IconInfo("\uE894"); // Clear
internal static IconInfo Info { get; } = new IconInfo("\uE946"); // Info
+
+ internal static IconInfo GenericAppIcon { get; } = new("\uE737"); // Favicon
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
index ecb09c8c38..1a8c5106d4 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.Designer.cs
@@ -401,5 +401,23 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
return ResourceManager.GetString("windowwalker_SettingTagPid", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Use window icons.
+ ///
+ public static string windowwalker_SettingUseWindowIcon {
+ get {
+ return ResourceManager.GetString("windowwalker_SettingUseWindowIcon", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Show the actual window icon instead of the process icon.
+ ///
+ public static string windowwalker_SettingUseWindowIcon_Description {
+ get {
+ return ResourceManager.GetString("windowwalker_SettingUseWindowIcon_Description", resourceCulture);
+ }
+ }
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx
index c610b7b09c..3d61936a1d 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Properties/Resources.resx
@@ -235,4 +235,10 @@
No open windows found
+
+ Use window icons
+
+
+ Show the actual window icon instead of the process icon
+
\ No newline at end of file