mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-22 20:17:21 +01:00
Compare commits
4 Commits
dev/migrie
...
dev/vanzue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2f0008473 | ||
|
|
8586dc88bb | ||
|
|
3322b8ca2f | ||
|
|
50038cbf3e |
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@@ -1440,6 +1440,7 @@ secpol
|
||||
securestring
|
||||
SEEMASKINVOKEIDLIST
|
||||
SELCHANGE
|
||||
selfhost
|
||||
SENDCHANGE
|
||||
sendvirtualinput
|
||||
serverside
|
||||
@@ -1879,6 +1880,7 @@ winexe
|
||||
winforms
|
||||
winget
|
||||
wingetcreate
|
||||
wingetpkgs
|
||||
Winhook
|
||||
WINL
|
||||
winlogon
|
||||
|
||||
@@ -760,7 +760,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste.FuzzTests", "
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste.UnitTests", "src\modules\AdvancedPaste\AdvancedPaste.UnitTests\AdvancedPaste.UnitTests.csproj", "{988C9FAF-5AEC-EB15-578D-FED0DF52BF55}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UITests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.UITests\Microsoft.CmdPal.UITests.csproj", "{6748A29D-DA6A-033A-825B-752295FF6AA0}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UITests", "src\modules\cmdpal\Microsoft.CmdPal.UITests\Microsoft.CmdPal.UITests.csproj", "{6748A29D-DA6A-033A-825B-752295FF6AA0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZones.FuzzTests", "src\modules\fancyzones\FancyZones.FuzzTests\FancyZones.FuzzTests.csproj", "{6EABCF9A-6526-441F-932F-658B1DC3E403}"
|
||||
EndProject
|
||||
@@ -776,14 +776,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{8131151D-B
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Calc.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Calc.UnitTests\Microsoft.CmdPal.Ext.Calc.UnitTests.csproj", "{E816D7AC-4688-4ECB-97CC-3D8E798F3825}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Registry.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Registry.UnitTests\Microsoft.CmdPal.Ext.Registry.UnitTests.csproj", "{E816D7AD-4688-4ECB-97CC-3D8E798F3826}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.System.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.System.UnitTests\Microsoft.CmdPal.Ext.System.UnitTests.csproj", "{E816D7AE-4688-4ECB-97CC-3D8E798F3827}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDate.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.TimeDate.UnitTests\Microsoft.CmdPal.Ext.TimeDate.UnitTests.csproj", "{E816D7AF-4688-4ECB-97CC-3D8E798F3828}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WindowWalker.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj", "{E816D7B0-4688-4ECB-97CC-3D8E798F3829}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -2798,38 +2790,6 @@ Global
|
||||
{E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|x64.Build.0 = Release|x64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|x64.Build.0 = Debug|x64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|x64.Build.0 = Release|x64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|x64.Build.0 = Debug|x64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|x64.Build.0 = Release|x64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|x64.Build.0 = Debug|x64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|x64.Build.0 = Release|x64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|x64.Build.0 = Debug|x64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3117,11 +3077,6 @@ Global
|
||||
{D9BD324E-1D80-44AA-8E7B-73EB00944434} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
|
||||
{8EF25507-2575-4ADE-BF7E-D23376903AB8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
|
||||
{070AC093-C9F2-20AD-0BCD-F318FC2761EA} = {B1234567-1234-1234-1234-123456789ABC}
|
||||
{E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{2C318EC3-BA86-4372-B1BC-DB0F33C208B2} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{BFFB607F-7C78-434B-86B9-DA4C8196A1B5} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
||||
{66E1534A-1587-42B2-912F-45C994D32904} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
|
||||
|
||||
@@ -87,6 +87,13 @@
|
||||
|
||||
### Building PowerToys Locally
|
||||
|
||||
#### One stop script for building installer
|
||||
1. Open developer powershell for vs 2022
|
||||
2. Run tools\build\build-installer.ps1
|
||||
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
|
||||
|
||||
The following manual steps will not install the MSIX apps (such as Command Palette) on your local installer.
|
||||
|
||||
#### Prerequisites for building the MSI installer
|
||||
|
||||
1. Install the [WiX Toolset Visual Studio 2022 Extension](https://marketplace.visualstudio.com/items?itemName=WixToolset.WixToolsetVisualStudio2022Extension).
|
||||
|
||||
33
doc/devdocs/development/test-winget-install-locally.md
Normal file
33
doc/devdocs/development/test-winget-install-locally.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## If for any reason, you'd like to test winget install scenario, you can follow this doc:
|
||||
|
||||
### Powertoys winget manifest definition:
|
||||
[winget repository](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys)
|
||||
|
||||
### How to test a winget installation locally:
|
||||
1. Get artifacts from release CI pipeline Pipelines - Runs for PowerToys Signed YAML Release Build, or you can build one yourself by execute the
|
||||
'tools\build\build-installer.ps1' script
|
||||
|
||||
2. Get the artifact hash, this is required to define winget manifest
|
||||
```powershell
|
||||
cd /path/to/your/directory/contains/installer
|
||||
Get-FileHash -Path ".\<Installer-name>.exe" -Algorithm SHA256
|
||||
```
|
||||
3. Host your installer.exe - Attention: staged github release artifacts or artifacts in release pipeline is not OK in this step
|
||||
You can self-host it or you can upload to a publicly available endpoint
|
||||
**How to selfhost it** (A extremely simple way):
|
||||
```powershell
|
||||
python -m http.server 8000
|
||||
```
|
||||
|
||||
4. Download a version folder from wingetpkgs like: [version 0.92.1](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys/0.92.1)
|
||||
and you get **a folder contains 3 yml files**
|
||||
>note: Do not put any files other than these three in this folder
|
||||
|
||||
5. Modify the yml files based on your version and the self hosted artifact link, and modify the sha256 hash for the installer you'd like to use
|
||||
|
||||
6. Start winget install:
|
||||
```powershell
|
||||
#execute as admin
|
||||
winget settings --enable LocalManifestFiles
|
||||
winget install --manifest "<folder_path_of_manifest_files>" --architecture x64 --scope user
|
||||
```
|
||||
@@ -1,429 +0,0 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Gdi;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
// This is an unfortunate interaction between CsWin32 and CsWinRT.
|
||||
// The warning is technically correct, but because you can't edit generated
|
||||
// files, you can't really fix it. Ideally CsWin32 would generate all types as
|
||||
// partial, but it doesn't do that today
|
||||
[assembly: SuppressMessage("Usage", "CsWinRT1028:Class is not marked partial", Justification = "Type is not passed across the WinRT ABI", Scope = "type", Target = "Windows.Win32.DeleteObjectSafeHandle")]
|
||||
[assembly: SuppressMessage("Usage", "CsWinRT1028:Class is not marked partial", Justification = "Type is not passed across the WinRT ABI", Scope = "type", Target = "Windows.Win32.DestroyIconSafeHandle")]
|
||||
// Relatedly, we added a NativeMethods.json to be able to fix some
|
||||
// error CA1420: Setting SetLastError to 'true' requires runtime marshalling to be enabled
|
||||
// errors.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Converts icon paths to IconSource and IconElement objects for WinUI controls.
|
||||
/// This is a C# port of the C++ IconPathConverter from Microsoft.Terminal.UI.
|
||||
/// </summary>
|
||||
public static class IconPathConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an IconSource for the given path with default settings.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The path to the icon.</param>
|
||||
/// <returns>An IconSource with its source set, if possible.</returns>
|
||||
public static IconSource? IconSource(string iconPath, string? fontFamily = null)
|
||||
{
|
||||
return IconSource(iconPath, false, fontFamily, 24);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IconSource for the given path.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The path to the icon.</param>
|
||||
/// <param name="monochrome">Whether to show the icon as monochrome.</param>
|
||||
/// <param name="targetSize">The target size for the icon.</param>
|
||||
/// <returns>An IconSource with its source set, if possible.</returns>
|
||||
public static IconSource? IconSource(string iconPath, bool monochrome, string? fontFamily = null, int targetSize = 24)
|
||||
{
|
||||
if (TryGetIconIndex(iconPath, out var iconPathWithoutIndex, out var index))
|
||||
{
|
||||
var bitmapSource = GetImageIconSourceForBinary(iconPathWithoutIndex, index, targetSize);
|
||||
if (bitmapSource != null)
|
||||
{
|
||||
var imageIconSource = new ImageIconSource
|
||||
{
|
||||
ImageSource = bitmapSource
|
||||
};
|
||||
return imageIconSource;
|
||||
}
|
||||
}
|
||||
|
||||
return GetIconSource(iconPath, monochrome, fontFamily, targetSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IconElement for the given path with default size.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The path to the icon.</param>
|
||||
/// <returns>An IconElement with its IconSource set, if possible.</returns>
|
||||
public static IconElement Icon(string iconPath)
|
||||
{
|
||||
return Icon(iconPath, 24);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IconElement for the given path.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The path to the icon.</param>
|
||||
/// <param name="targetSize">The target size for the icon.</param>
|
||||
/// <returns>An IconElement with its IconSource set, if possible.</returns>
|
||||
public static IconElement Icon(string iconPath, int targetSize)
|
||||
{
|
||||
if (TryGetIconIndex(iconPath, out var iconPathWithoutIndex, out var index))
|
||||
{
|
||||
var bitmapSource = GetImageIconSourceForBinary(iconPathWithoutIndex, index, targetSize);
|
||||
if (bitmapSource != null)
|
||||
{
|
||||
var icon = new ImageIcon
|
||||
{
|
||||
Source = bitmapSource,
|
||||
Width = targetSize,
|
||||
Height = targetSize
|
||||
};
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
var source = IconSource(iconPath, false, targetSize);
|
||||
var iconSourceElement = new IconSourceElement
|
||||
{
|
||||
IconSource = source
|
||||
};
|
||||
return iconSourceElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IconSource for the given path.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The path to the icon.</param>
|
||||
/// <param name="monochrome">Whether to show the icon as monochrome.</param>
|
||||
/// <param name="targetSize">The target size for the icon.</param>
|
||||
/// <returns>An IconSource with its source set, if possible.</returns>
|
||||
private static IconSource? GetIconSource(string iconPath, bool monochrome, string? fontFamily, int targetSize)
|
||||
{
|
||||
IconSource? iconSource = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
var expandedIconPath = ExpandIconPath(iconPath);
|
||||
iconSource = GetColoredBitmapIcon(expandedIconPath, monochrome);
|
||||
|
||||
// If we fail to set the icon source using the "icon" as a path,
|
||||
// let's try it as a symbol/emoji.
|
||||
//
|
||||
// Anything longer than 2 characters isn't an emoji or symbol, so
|
||||
// don't do this if it's just an invalid path.
|
||||
if (iconSource == null && iconPath.Length <= 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
var icon = new FontIconSource();
|
||||
var ch = iconPath[0];
|
||||
|
||||
// The range of MDL2 Icons isn't explicitly defined, but
|
||||
// we're using this based off the table on:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
|
||||
var isMDL2Icon = ch >= '\uE700' && ch <= '\uF8FF';
|
||||
if (isMDL2Icon)
|
||||
{
|
||||
icon.FontFamily = new FontFamily("Segoe Fluent Icons, Segoe MDL2 Assets");
|
||||
}
|
||||
else if (fontFamily != null)
|
||||
{
|
||||
icon.FontFamily = new FontFamily(fontFamily);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: you do need to manually set the font here.
|
||||
icon.FontFamily = new FontFamily("Segoe UI");
|
||||
}
|
||||
icon.FontSize = targetSize;
|
||||
icon.Glyph = iconPath;
|
||||
iconSource = icon;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore exceptions when creating font icons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iconSource == null)
|
||||
{
|
||||
// Set the default IconSource to a BitmapIconSource with a null source
|
||||
// (instead of just null) because there's a really weird crash when swapping
|
||||
// data bound IconSourceElements in a ListViewTemplate (i.e. CommandPalette).
|
||||
// Swapping between null IconSources and non-null IconSources causes a crash
|
||||
// to occur, but swapping between IconSources with a null source and non-null IconSources
|
||||
// work perfectly fine.
|
||||
var icon = new BitmapIconSource
|
||||
{
|
||||
UriSource = null
|
||||
};
|
||||
iconSource = icon;
|
||||
}
|
||||
|
||||
return iconSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a colored bitmap icon source for the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The full, expanded path to the icon.</param>
|
||||
/// <param name="monochrome">Whether to show the icon as monochrome.</param>
|
||||
/// <returns>An IconSource with its source set, if possible.</returns>
|
||||
private static IconSource? GetColoredBitmapIcon(string path, bool monochrome)
|
||||
{
|
||||
// FontIcon uses glyphs in the private use area, whereas valid URIs only contain ASCII characters.
|
||||
// To skip throwing on Uri construction, we can quickly check if the first character is ASCII.
|
||||
if (!string.IsNullOrEmpty(path) && path[0] < 128)
|
||||
{
|
||||
try
|
||||
{
|
||||
var iconUri = new Uri(path);
|
||||
|
||||
if (Path.GetExtension(path).Equals(".svg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var iconSource = new ImageIconSource();
|
||||
var source = new SvgImageSource(iconUri);
|
||||
iconSource.ImageSource = source;
|
||||
return iconSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
var iconSource = new BitmapIconSource
|
||||
{
|
||||
// Make sure to set this to false, so we keep the RGB data of the
|
||||
// image. Otherwise, the icon will be white for all the
|
||||
// non-transparent pixels in the image.
|
||||
ShowAsMonochrome = monochrome,
|
||||
UriSource = iconUri
|
||||
};
|
||||
return iconSource;
|
||||
}
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
// Ignore exceptions when creating URI-based icons
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands environment variables in the icon path.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The path that may contain environment variables.</param>
|
||||
/// <returns>The expanded path.</returns>
|
||||
private static string ExpandIconPath(string iconPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
// Use Environment.ExpandEnvironmentVariables as the C# equivalent of wil::ExpandEnvironmentStringsW
|
||||
return Environment.ExpandEnvironmentVariables(iconPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the icon index from the icon path provided.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The full icon path, including the index if present.</param>
|
||||
/// <param name="iconPathWithoutIndex">The icon path without the index.</param>
|
||||
/// <param name="iconIndex">The icon index if present.</param>
|
||||
/// <returns>True if the iconPath is an exe/dll/lnk file, false otherwise.</returns>
|
||||
private static bool TryGetIconIndex(string iconPath, out string iconPathWithoutIndex, out int iconIndex)
|
||||
{
|
||||
iconPathWithoutIndex = iconPath;
|
||||
iconIndex = 0;
|
||||
|
||||
if (string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does iconPath have a comma in it? If so, split the string on the
|
||||
// comma and look for the index and extension.
|
||||
var commaIndex = iconPath.IndexOf(',');
|
||||
|
||||
// Split the path on the comma
|
||||
iconPathWithoutIndex = commaIndex >= 0 ? iconPath.Substring(0, commaIndex) : iconPath;
|
||||
|
||||
// It's an exe, dll, or lnk, so we need to extract the icon from the file.
|
||||
var extension = Path.GetExtension(iconPathWithoutIndex).ToLowerInvariant();
|
||||
if (extension != ".exe" && extension != ".dll" && extension != ".lnk")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (commaIndex >= 0)
|
||||
{
|
||||
// Convert the string iconIndex to a signed int to support negative numbers which represent an Icon's ID.
|
||||
var indexString = iconPath.Substring(commaIndex + 1);
|
||||
if (int.TryParse(indexString, out iconIndex))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Failed to parse, return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// We had a binary path, but no index. Default to 0.
|
||||
iconIndex = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an image icon source for a binary file (exe, dll, lnk).
|
||||
/// </summary>
|
||||
/// <param name="iconPathWithoutIndex">The path to the binary file.</param>
|
||||
/// <param name="index">The icon index within the file.</param>
|
||||
/// <param name="targetSize">The target size for the icon.</param>
|
||||
/// <returns>A SoftwareBitmapSource if successful, null otherwise.</returns>
|
||||
private static SoftwareBitmapSource? GetImageIconSourceForBinary(string iconPathWithoutIndex, int index, int targetSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var swBitmap = GetBitmapFromIconFile(iconPathWithoutIndex, index, (uint)targetSize);
|
||||
if (swBitmap == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bitmapSource = new SoftwareBitmapSource();
|
||||
_ = bitmapSource.SetBitmapAsync(swBitmap);
|
||||
return bitmapSource;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a bitmap from an icon file using Win32 APIs.
|
||||
/// This implementation uses SHDefExtractIcon to extract icons from executables, DLLs, and shortcut files.
|
||||
/// </summary>
|
||||
/// <param name="iconPath">The path to the icon file.</param>
|
||||
/// <param name="iconIndex">The index of the icon within the file.</param>
|
||||
/// <param name="iconSize">The desired icon size.</param>
|
||||
/// <returns>A SoftwareBitmap if successful, null otherwise.</returns>
|
||||
private static SoftwareBitmap? GetBitmapFromIconFile(string iconPath, int iconIndex, uint iconSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
DestroyIconSafeHandle? hIconLarge;
|
||||
DestroyIconSafeHandle? hIconSmall;
|
||||
// Extract the icon using SHDefExtractIcon
|
||||
var result = PInvoke.SHDefExtractIcon(
|
||||
iconPath,
|
||||
iconIndex,
|
||||
0,
|
||||
out hIconLarge,
|
||||
out hIconSmall,
|
||||
iconSize);
|
||||
using (hIconLarge)
|
||||
using (hIconSmall)
|
||||
{
|
||||
if (result != 0 || hIconLarge.IsInvalid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// For simplicity, convert HICON to bitmap using a more straightforward approach
|
||||
// This could be enhanced to use WIC directly for better performance
|
||||
return ConvertHIconToSoftwareBitmap(hIconLarge);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an HICON to a SoftwareBitmap using Win32 APIs.
|
||||
/// </summary>
|
||||
/// <param name="hIcon">The icon handle to convert.</param>
|
||||
/// <returns>A SoftwareBitmap if successful, null otherwise.</returns>
|
||||
private static unsafe SoftwareBitmap? ConvertHIconToSoftwareBitmap(DestroyIconSafeHandle hIcon)
|
||||
{
|
||||
// Get icon information
|
||||
if (!PInvoke.GetIconInfo(hIcon, out var iconInfo))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Create device contexts
|
||||
var hdcScreen = PInvoke.GetDC(HWND.Null);
|
||||
var hdcMem = PInvoke.CreateCompatibleDC(hdcScreen);
|
||||
|
||||
// Get the icon size (assuming 32x32 for now, could be enhanced to get actual size)
|
||||
const int iconSize = 32;
|
||||
|
||||
// Create a bitmap to draw the icon onto
|
||||
using var hBitmap = PInvoke.CreateCompatibleBitmap_SafeHandle(hdcScreen, iconSize, iconSize);
|
||||
using var hOldBitmap = PInvoke.SelectObject(hdcMem, hBitmap);
|
||||
|
||||
// Draw the icon onto the bitmap
|
||||
PInvoke.DrawIconEx(hdcMem, 0, 0, hIcon, iconSize, iconSize, 0, null, (DI_FLAGS)0x0003); // DI_NORMAL
|
||||
|
||||
// Get bitmap info
|
||||
var bitmapInfo = new BITMAPINFO();
|
||||
bitmapInfo.bmiHeader.biSize = (uint)sizeof(BITMAPINFOHEADER);
|
||||
bitmapInfo.bmiHeader.biWidth = iconSize;
|
||||
bitmapInfo.bmiHeader.biHeight = -iconSize; // Top-down DIB
|
||||
bitmapInfo.bmiHeader.biPlanes = 1;
|
||||
bitmapInfo.bmiHeader.biBitCount = 32;
|
||||
bitmapInfo.bmiHeader.biCompression = 0; // BI_RGB
|
||||
|
||||
// Allocate buffer for pixel data
|
||||
var pixelDataSize = iconSize * iconSize * 4; // 4 bytes per pixel (BGRA)
|
||||
var pixelData = new byte[pixelDataSize];
|
||||
|
||||
// Get the pixel data - need to pin the byte array
|
||||
fixed (byte* pixelPtr = pixelData)
|
||||
{
|
||||
var result = PInvoke.GetDIBits(
|
||||
hdcMem,
|
||||
hBitmap,
|
||||
0,
|
||||
iconSize,
|
||||
pixelPtr,
|
||||
&bitmapInfo,
|
||||
0); // DIB_RGB_COLORS
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Create SoftwareBitmap from pixel data
|
||||
var softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(
|
||||
pixelData.AsBuffer(),
|
||||
BitmapPixelFormat.Bgra8,
|
||||
iconSize,
|
||||
iconSize,
|
||||
BitmapAlphaMode.Premultiplied);
|
||||
|
||||
return softwareBitmap;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.UITests</RootNamespace>
|
||||
@@ -21,6 +21,6 @@
|
||||
<PackageReference Include="System.Net.Http" />
|
||||
<PackageReference Include="System.Private.Uri" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
<ProjectReference Include="..\..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user