Compare commits

...

6 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
fc6daf4725 Restore Quick access menu item in tray menu
Co-authored-by: davidegiacometti <25966642+davidegiacometti@users.noreply.github.com>
2025-10-20 18:25:57 +00:00
copilot-swe-agent[bot]
76a3fd5c04 Initial plan 2025-10-20 18:18:21 +00:00
Lee Won Jun
f28d009131 [CmdPal] WindowWalker Show the actual window icon instead of the process icon (#42316)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->

<img width="629" height="767" alt="image"
src="https://github.com/user-attachments/assets/bc093640-db9d-4bc8-bc33-53729e692850"
/>


## Summary of the Pull Request

This is a PR for issue **#42260**.
It targets **CmdPal’s WindowWalker** and changes the icon retrieval to
use **SendMessage** to obtain the window’s actual icon, instead of using
the **process icon**.

To support this, I added a new configuration option.

<img width="400" height="401" alt="image"
src="https://github.com/user-attachments/assets/1a2d97a8-ff95-40b0-be42-746c2b1409d4"
/>


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #42260
- [ ] **Communication:** @jiripolasek 
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments


Actully, The `ThumbnailHelper` already contains code that converts an
`IntPtr` `hIcon` into an `IRandomAccessStream`, as shown below:

```
 private static MemoryStream GetMemoryStreamFromIcon(IntPtr hIcon)
 {
     var memoryStream = new MemoryStream();

     // Ensure disposing the icon before freeing the handle
     using (var icon = Icon.FromHandle(hIcon))
     {
         icon.ToBitmap().Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
     }

     // Clean up the unmanaged handle without risking a use-after-free.
     NativeMethods.DestroyIcon(hIcon);

     memoryStream.Position = 0;
     return memoryStream;
 }

 private static async Task<IRandomAccessStream?> FromHIconToStream(IntPtr hIcon)
 {
     var stream = new InMemoryRandomAccessStream();

     using var memoryStream = GetMemoryStreamFromIcon(hIcon); // this will DestroyIcon hIcon
     using var outputStream = stream.GetOutputStreamAt(0);
     using var dataWriter = new DataWriter(outputStream);

     dataWriter.WriteBytes(memoryStream.ToArray());
     await dataWriter.StoreAsync();
     await dataWriter.FlushAsync();

     return stream;
 }
```

Without modifying (or using) this code, I implemented the almost same
logic directly in `SwitchToWindowCommand` (calling the async code with
`Wait` to block synchronously). The reasons are:

1. I wanted to limit changes to the **WindowWalker** project area. I
don’t expect other extensions to need this behavior.
2. Because this is resource-related work, exposing a public helper that
pulls memory from an `hIcon` pointer seems risky—especially in a class
like `ThumbnailHelper`.

Therefore, I implemented behavior that is nearly identical to the
snippet above.

I did use `using`/`Dispose` where appropriate, but the
`InMemoryRandomAccessStream` created for `IconInfo.FromStream` appears
to use internal referencing; disposing it would be incorrect. For that
reason I didn’t wrap it in a `using`. I’m not entirely sure whether GC
will handle this cleanly.

However, based on the implementation of `FromStream` itself and its
usage elsewhere (e.g., in `ThumbnailHelper`), this seems to be the
correct usage pattern, though I’m not entirely sure.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Jiří Polášek <me@jiripolasek.com>
2025-10-20 12:09:23 -05:00
Jiří Polášek
bc8adb3189 BugReport: Fix incorrect XML closing tag syntax generated by XmlDocumentEx (#42399)
## Summary of the Pull Request

The `XmlDocumentEx::Print` method previously used `<\\` to close XML
tags, which is invalid. This commit replaces `<\\` with `</` to ensure
proper XML closing tag syntax.

Changes include:
- Replacing `<\\` with `</` in three instances where closing tags are
generated.
- Ensuring the XML output conforms to standard XML syntax.

These changes improve the correctness of the XML output generated by the
method.

<img width="1014" height="499" alt="image"
src="https://github.com/user-attachments/assets/a9ff6e47-6976-4290-a4f0-c23b0c773d61"
/>


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #42390
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **New binaries:** Added on the required places
- [x] **Documentation updated:** 

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-10-20 12:00:04 -05:00
Sam Rueby
b67d3b4418 Replaced "🔴" with actual red circle emoji within cmdpal ne… (#42666)
…twork connection properies.

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Resolves hardcoded "🔴" with red circle emoji, seen when
exploring Windows System Commands with Command Palette.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ X ] Closes: #42647
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Ran locally, looks good.
2025-10-20 11:58:36 -05:00
Jiří Polášek
ae6da3235b CmdPal: Cleanup warnings, part 1 (#42584)
## Summary of the Pull Request

This PR fixes several warnings in the Command Palette projects.


- _MSB4011_: `"Sdk.props" cannot be imported again. It was already
imported ...`
  - Removed the `Sdk` attribute from `CoreCommonProps.props`.

- _CsWinRT1028_ – “Class should be marked partial” on
*CsWin32*-generated classes.
- Since these classes cannot be made partial, a suppression attribute
has been added.
  - The `LocalKeyboardListener` type has been marked as partial.

- _Resource.resx_ – some strings had empty values.  
  - Updated the missing content.

- _WMC1506_ – *OneWay* bindings require at least one step to support
change notifications.
- In `SettingsWindow.xaml`, the breadcrumb binding was changed from
*OneWay* to *OneTime*, as `Crumb` does not support change notifications.

- _WMC0001_ – Unknown type in XML namespace
- In `SettingsWindow.xaml`, `FontWeight` was qualified with the CLR
namespace `Windows.UI.Text`.


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Related to: #42574
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-10-20 11:56:55 -05:00
21 changed files with 234 additions and 26 deletions

View File

@@ -639,6 +639,7 @@ Hiber
Hiberboot
HIBYTE
hicon
HICONSM
HIDEREADONLY
HIDEWINDOW
Hif

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project>
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>

View File

@@ -14,7 +14,7 @@ namespace Microsoft.CmdPal.UI.Helpers;
/// <summary>
/// A class that listens for local keyboard events using a Windows hook.
/// </summary>
internal sealed class LocalKeyboardListener : IDisposable
internal sealed partial class LocalKeyboardListener : IDisposable
{
/// <summary>
/// Event that is raised when a key is pressed down.

View File

@@ -0,0 +1,9 @@
// 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.CodeAnalysis;
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.DestroyIconSafeHandle")]
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.DestroyMenuSafeHandle")]
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.FreeLibrarySafeHandle")]
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.UnhookWindowsHookExSafeHandle")]

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.CmdPal.UI.Settings"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:text="using:Windows.UI.Text"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:winuiex="using:WinUIEx"
Title="SettingsWindow"
@@ -73,14 +74,14 @@
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
<BreadcrumbBar.ItemTemplate>
<DataTemplate x:DataType="local:Crumb">
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
<TextBlock Text="{x:Bind Label}" />
</DataTemplate>
</BreadcrumbBar.ItemTemplate>
<BreadcrumbBar.Resources>
<ResourceDictionary>
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">28</x:Double>
<Thickness x:Key="BreadcrumbBarChevronPadding">7,4,8,0</Thickness>
<FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</FontWeight>
<text:FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</text:FontWeight>
<x:Double x:Key="BreadcrumbBarChevronFontSize">16</x:Double>
</ResourceDictionary>
</BreadcrumbBar.Resources>

View File

@@ -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;
}

View File

@@ -0,0 +1,6 @@
// 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.CodeAnalysis;
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.SysFreeStringSafeHandle")]

View File

@@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
// 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.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
@@ -187,7 +187,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
}
/// <summary>
/// Looks up a localized string similar to .
/// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item.
/// </summary>
public static string settings_confirm_delete_description {
get {
@@ -196,7 +196,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
}
/// <summary>
/// Looks up a localized string similar to Show a confirmation dialog when manually deleting an item.
/// Looks up a localized string similar to Ask for confirmation before deleting items.
/// </summary>
public static string settings_confirm_delete_title {
get {
@@ -205,7 +205,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
}
/// <summary>
/// Looks up a localized string similar to .
/// Looks up a localized string similar to Keep items in clipboard history after pasting.
/// </summary>
public static string settings_keep_after_paste_description {
get {
@@ -214,7 +214,7 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
}
/// <summary>
/// Looks up a localized string similar to Keep items in clipboard history after pasting.
/// Looks up a localized string similar to Keep items after pasting.
/// </summary>
public static string settings_keep_after_paste_title {
get {

View File

@@ -151,16 +151,16 @@
<value>Deleted from clipboard history</value>
</data>
<data name="settings_keep_after_paste_description" xml:space="preserve">
<value />
</data>
<data name="settings_keep_after_paste_title" xml:space="preserve">
<value>Keep items in clipboard history after pasting</value>
</data>
<data name="settings_keep_after_paste_title" xml:space="preserve">
<value>Keep items after pasting</value>
</data>
<data name="settings_confirm_delete_title" xml:space="preserve">
<value>Show a confirmation dialog when manually deleting an item</value>
<value>Ask for confirmation before deleting items</value>
</data>
<data name="settings_confirm_delete_description" xml:space="preserve">
<value />
<value>Show a confirmation dialog when manually deleting an item</value>
</data>
<data name="delete_confirmation_title" xml:space="preserve">
<value>Delete item?</value>

View File

@@ -0,0 +1,6 @@
// 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.CodeAnalysis;
[assembly: SuppressMessage("Interoperability", "CsWinRT1028: Class should be marked partial", Justification = "CsWin32 generated code; not used across WinRT boundary", Scope = "type", Target = "~T:Windows.Win32.LocalFreeSafeHandle")]

View File

@@ -27,6 +27,14 @@ internal sealed class NetworkConnectionProperties
/// <seealso cref="https://github.com/xoofx/markdig/blob/master/src/Markdig/Extensions/Emoji/EmojiMapping.cs"/>
private const int GreenCircleCharacter = 128994;
/// <summary>
/// Decimal unicode value for green circle emoji.
/// We need to generate it in the code because it does not render using Markdown emoji syntax or Unicode character syntax.
/// </summary>
/// <seealso cref="https://github.com/CommunityToolkit/Labs-Windows/blob/main/components/MarkdownTextBlock/samples/MarkdownTextBlock.md"/>
/// <seealso cref="https://github.com/xoofx/markdig/blob/master/src/Markdig/Extensions/Emoji/EmojiMapping.cs"/>
private const int RedCircleCharacter = 128308;
/// <summary>
/// Gets the name of the adapter
/// </summary>
@@ -170,7 +178,7 @@ internal sealed class NetworkConnectionProperties
internal string GetAdapterDetails()
{
return $"**{Resources.Microsoft_plugin_sys_AdapterName}:** {Adapter}" +
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : ":red_circle: " + Resources.Microsoft_plugin_sys_Disconnected) +
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : char.ConvertFromUtf32(RedCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Disconnected) +
$"\n\n**{Resources.Microsoft_plugin_sys_PhysicalAddress}:** {PhysicalAddress}" +
$"\n\n**{Resources.Microsoft_plugin_sys_Speed}:** {GetFormattedSpeedValue(Speed)}" +
$"\n\n**{Resources.Microsoft_plugin_sys_Type}:** {GetAdapterTypeAsString(Type)}" +
@@ -184,7 +192,7 @@ internal sealed class NetworkConnectionProperties
internal string GetConnectionDetails()
{
return $"**{Resources.Microsoft_plugin_sys_ConnectionName}:** {ConnectionName}" +
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : ":red_circle: " + Resources.Microsoft_plugin_sys_Disconnected) +
$"\n\n**{Resources.Microsoft_plugin_sys_State}:** " + (State == OperationalStatus.Up ? char.ConvertFromUtf32(GreenCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Connected : char.ConvertFromUtf32(RedCircleCharacter) + " " + Resources.Microsoft_plugin_sys_Disconnected) +
$"\n\n**{Resources.Microsoft_plugin_sys_Type}:** {GetAdapterTypeAsString(Type)}" +
$"\n\n**{Resources.Microsoft_plugin_sys_Suffix}:** {Suffix}" +
CreateIpInfoForDetailsText($"**{Resources.Microsoft_plugin_sys_Ip4Address}:** ", IPv4) +

View File

@@ -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
{
}
}
}
}

View File

@@ -188,6 +188,62 @@ internal sealed class Window
thread.Start();
}
/// <summary>
/// Tries to get the window icon.
/// </summary>
/// <param name="icon">The window icon if found; otherwise, null.</param>
/// <returns>True if an icon was found; otherwise, false.</returns>
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;
}
/// <summary>
/// Converts the window name to string along with the process name
/// </summary>

View File

@@ -23,4 +23,6 @@ public interface ISettingsInterface
public bool HideExplorerSettingInfo { get; }
public bool InMruOrder { get; }
public bool UseWindowIcon { get; }
}

View File

@@ -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
/// </summary>
public const int SC_CLOSE = 0xF060;
/// <summary>
/// Sent to a window to retrieve a handle to the large or small icon associated with a window.
/// </summary>
public const uint WM_GETICON = 0x007F;
/// <summary>
/// Retrieve the large icon for the window.
/// </summary>
public const int ICON_BIG = 1;
/// <summary>
/// Retrieve the small icon for the window.
/// </summary>
public const int ICON_SMALL = 0;
/// <summary>
/// Retrieve the small icon provided by the application.
/// </summary>
public const int ICON_SMALL2 = 2;
/// <summary>
/// The function returns if the receiving thread does not respond within the timeout period.
/// </summary>
public const int SMTO_ABORTIFHUNG = 0x0002;
/// <summary>
/// Retrieves a handle to the icon associated with the class.
/// </summary>
public const int GCLP_HICON = -14;
/// <summary>
/// Retrieves a handle to the small icon associated with the class.
/// </summary>
public const int GCLP_HICONSM = -34;
/// <summary>
/// RPC call succeeded
/// </summary>

View File

@@ -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();

View File

@@ -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
}

View File

@@ -401,5 +401,23 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
return ResourceManager.GetString("windowwalker_SettingTagPid", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use window icons.
/// </summary>
public static string windowwalker_SettingUseWindowIcon {
get {
return ResourceManager.GetString("windowwalker_SettingUseWindowIcon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show the actual window icon instead of the process icon.
/// </summary>
public static string windowwalker_SettingUseWindowIcon_Description {
get {
return ResourceManager.GetString("windowwalker_SettingUseWindowIcon_Description", resourceCulture);
}
}
}
}

View File

@@ -235,4 +235,10 @@
<data name="windowwalker_NoResultsMessage" xml:space="preserve">
<value>No open windows found</value>
</data>
<data name="windowwalker_SettingUseWindowIcon" xml:space="preserve">
<value>Use window icons</value>
</data>
<data name="windowwalker_SettingUseWindowIcon_Description" xml:space="preserve">
<value>Show the actual window icon instead of the process icon</value>
</data>
</root>

Binary file not shown.

View File

@@ -19,13 +19,13 @@ void XmlDocumentEx::Print(winrt::Windows::Data::Xml::Dom::IXmlNode node, int ind
PrintTagWithAttributes(node);
if (!node.HasChildNodes())
{
stream << L"<\\" << node.NodeName().c_str() << ">" << std::endl;
stream << L"</" << node.NodeName().c_str() << ">" << std::endl;
return;
}
if (node.ChildNodes().Size() == 1 && !node.FirstChild().HasChildNodes())
{
stream << node.InnerText().c_str() << L"<\\" << node.NodeName().c_str() << ">" << std::endl;
stream << node.InnerText().c_str() << L"</" << node.NodeName().c_str() << ">" << std::endl;
return;
}
@@ -40,7 +40,7 @@ void XmlDocumentEx::Print(winrt::Windows::Data::Xml::Dom::IXmlNode node, int ind
{
stream << " ";
}
stream << L"<\\" << node.NodeName().c_str() << ">" << std::endl;
stream << L"</" << node.NodeName().c_str() << ">" << std::endl;
}
void XmlDocumentEx::PrintTagWithAttributes(winrt::Windows::Data::Xml::Dom::IXmlNode node)