mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
[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>
This commit is contained in:
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -639,6 +639,7 @@ Hiber
|
|||||||
Hiberboot
|
Hiberboot
|
||||||
HIBYTE
|
HIBYTE
|
||||||
hicon
|
hicon
|
||||||
|
HICONSM
|
||||||
HIDEREADONLY
|
HIDEREADONLY
|
||||||
HIDEWINDOW
|
HIDEWINDOW
|
||||||
Hif
|
Hif
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public class Settings : ISettingsInterface
|
|||||||
private readonly bool hideKillProcessOnElevatedProcesses;
|
private readonly bool hideKillProcessOnElevatedProcesses;
|
||||||
private readonly bool hideExplorerSettingInfo;
|
private readonly bool hideExplorerSettingInfo;
|
||||||
private readonly bool inMruOrder;
|
private readonly bool inMruOrder;
|
||||||
|
private readonly bool useWindowIcon;
|
||||||
|
|
||||||
public Settings(
|
public Settings(
|
||||||
bool resultsFromVisibleDesktopOnly = false,
|
bool resultsFromVisibleDesktopOnly = false,
|
||||||
@@ -27,7 +28,8 @@ public class Settings : ISettingsInterface
|
|||||||
bool openAfterKillAndClose = false,
|
bool openAfterKillAndClose = false,
|
||||||
bool hideKillProcessOnElevatedProcesses = false,
|
bool hideKillProcessOnElevatedProcesses = false,
|
||||||
bool hideExplorerSettingInfo = true,
|
bool hideExplorerSettingInfo = true,
|
||||||
bool inMruOrder = true)
|
bool inMruOrder = true,
|
||||||
|
bool useWindowIcon = true)
|
||||||
{
|
{
|
||||||
this.resultsFromVisibleDesktopOnly = resultsFromVisibleDesktopOnly;
|
this.resultsFromVisibleDesktopOnly = resultsFromVisibleDesktopOnly;
|
||||||
this.subtitleShowPid = subtitleShowPid;
|
this.subtitleShowPid = subtitleShowPid;
|
||||||
@@ -38,6 +40,7 @@ public class Settings : ISettingsInterface
|
|||||||
this.hideKillProcessOnElevatedProcesses = hideKillProcessOnElevatedProcesses;
|
this.hideKillProcessOnElevatedProcesses = hideKillProcessOnElevatedProcesses;
|
||||||
this.hideExplorerSettingInfo = hideExplorerSettingInfo;
|
this.hideExplorerSettingInfo = hideExplorerSettingInfo;
|
||||||
this.inMruOrder = inMruOrder;
|
this.inMruOrder = inMruOrder;
|
||||||
|
this.useWindowIcon = useWindowIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ResultsFromVisibleDesktopOnly => resultsFromVisibleDesktopOnly;
|
public bool ResultsFromVisibleDesktopOnly => resultsFromVisibleDesktopOnly;
|
||||||
@@ -57,4 +60,6 @@ public class Settings : ISettingsInterface
|
|||||||
public bool HideExplorerSettingInfo => hideExplorerSettingInfo;
|
public bool HideExplorerSettingInfo => hideExplorerSettingInfo;
|
||||||
|
|
||||||
public bool InMruOrder => inMruOrder;
|
public bool InMruOrder => inMruOrder;
|
||||||
|
|
||||||
|
public bool UseWindowIcon => useWindowIcon;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,16 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
using Microsoft.CmdPal.Ext.WindowWalker.Components;
|
||||||
|
using Microsoft.CmdPal.Ext.WindowWalker.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
namespace Microsoft.CmdPal.Ext.WindowWalker.Commands;
|
||||||
|
|
||||||
@@ -16,20 +21,53 @@ internal sealed partial class SwitchToWindowCommand : InvokableCommand
|
|||||||
|
|
||||||
public SwitchToWindowCommand(Window? window)
|
public SwitchToWindowCommand(Window? window)
|
||||||
{
|
{
|
||||||
|
Icon = Icons.GenericAppIcon; // Fallback to default icon
|
||||||
Name = Resources.switch_to_command_title;
|
Name = Resources.switch_to_command_title;
|
||||||
_window = window;
|
_window = window;
|
||||||
if (_window is not null)
|
if (_window is not null)
|
||||||
{
|
{
|
||||||
var p = Process.GetProcessById((int)_window.Process.ProcessID);
|
// Use window icon
|
||||||
if (p is not null)
|
if (SettingsManager.Instance.UseWindowIcon)
|
||||||
{
|
{
|
||||||
try
|
if (_window.TryGetWindowIcon(out var icon) && icon is not null)
|
||||||
{
|
{
|
||||||
var processFileName = p.MainModule?.FileName;
|
try
|
||||||
Icon = new IconInfo(processFileName);
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,62 @@ internal sealed class Window
|
|||||||
thread.Start();
|
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>
|
/// <summary>
|
||||||
/// Converts the window name to string along with the process name
|
/// Converts the window name to string along with the process name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -23,4 +23,6 @@ public interface ISettingsInterface
|
|||||||
public bool HideExplorerSettingInfo { get; }
|
public bool HideExplorerSettingInfo { get; }
|
||||||
|
|
||||||
public bool InMruOrder { get; }
|
public bool InMruOrder { get; }
|
||||||
|
|
||||||
|
public bool UseWindowIcon { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ public static partial class NativeMethods
|
|||||||
[DllImport("user32.dll")]
|
[DllImport("user32.dll")]
|
||||||
public static extern int SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam, int fuFlags, int uTimeout, out int lpdwResult);
|
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")]
|
[DllImport("kernel32.dll")]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
public static extern bool CloseHandle(IntPtr hObject);
|
public static extern bool CloseHandle(IntPtr hObject);
|
||||||
@@ -143,6 +149,41 @@ public static class Win32Constants
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const int SC_CLOSE = 0xF060;
|
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>
|
/// <summary>
|
||||||
/// RPC call succeeded
|
/// RPC call succeeded
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
|||||||
Resources.windowwalker_SettingInMruOrder_Description,
|
Resources.windowwalker_SettingInMruOrder_Description,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
private readonly ToggleSetting _useWindowIcon = new(
|
||||||
|
Namespaced(nameof(UseWindowIcon)),
|
||||||
|
Resources.windowwalker_SettingUseWindowIcon,
|
||||||
|
Resources.windowwalker_SettingUseWindowIcon_Description,
|
||||||
|
true);
|
||||||
|
|
||||||
public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value;
|
public bool ResultsFromVisibleDesktopOnly => _resultsFromVisibleDesktopOnly.Value;
|
||||||
|
|
||||||
public bool SubtitleShowPid => _subtitleShowPid.Value;
|
public bool SubtitleShowPid => _subtitleShowPid.Value;
|
||||||
@@ -88,6 +94,8 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
|||||||
|
|
||||||
public bool InMruOrder => _inMruOrder.Value;
|
public bool InMruOrder => _inMruOrder.Value;
|
||||||
|
|
||||||
|
public bool UseWindowIcon => _useWindowIcon.Value;
|
||||||
|
|
||||||
internal static string SettingsJsonPath()
|
internal static string SettingsJsonPath()
|
||||||
{
|
{
|
||||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||||
@@ -110,6 +118,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
|||||||
Settings.Add(_hideKillProcessOnElevatedProcesses);
|
Settings.Add(_hideKillProcessOnElevatedProcesses);
|
||||||
Settings.Add(_hideExplorerSettingInfo);
|
Settings.Add(_hideExplorerSettingInfo);
|
||||||
Settings.Add(_inMruOrder);
|
Settings.Add(_inMruOrder);
|
||||||
|
Settings.Add(_useWindowIcon);
|
||||||
|
|
||||||
// Load settings from file upon initialization
|
// Load settings from file upon initialization
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
|
|||||||
@@ -15,4 +15,6 @@ internal sealed class Icons
|
|||||||
internal static IconInfo CloseWindow { get; } = new IconInfo("\uE894"); // Clear
|
internal static IconInfo CloseWindow { get; } = new IconInfo("\uE894"); // Clear
|
||||||
|
|
||||||
internal static IconInfo Info { get; } = new IconInfo("\uE946"); // Info
|
internal static IconInfo Info { get; } = new IconInfo("\uE946"); // Info
|
||||||
|
|
||||||
|
internal static IconInfo GenericAppIcon { get; } = new("\uE737"); // Favicon
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,5 +401,23 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Properties {
|
|||||||
return ResourceManager.GetString("windowwalker_SettingTagPid", resourceCulture);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,4 +235,10 @@
|
|||||||
<data name="windowwalker_NoResultsMessage" xml:space="preserve">
|
<data name="windowwalker_NoResultsMessage" xml:space="preserve">
|
||||||
<value>No open windows found</value>
|
<value>No open windows found</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
Reference in New Issue
Block a user