Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
466bd0c0a5 Fix: subscribe to ItemsChanged before fetching items to avoid missing early events
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/3f380bd3-a56a-4708-80ef-3e7d534c4f6f

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 08:58:21 +00:00
copilot-swe-agent[bot]
8db30b7e46 Initial plan 2026-04-29 08:49:46 +00:00
5 changed files with 28 additions and 44 deletions

View File

@@ -129,8 +129,8 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
UpdateDetails();
FetchContent();
model.ItemsChanged += Model_ItemsChanged;
FetchContent();
DoOnUiThread(
() =>

View File

@@ -44,9 +44,9 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
UpdateProperty(nameof(Root));
}
FetchContent();
model.PropChanged += Model_PropChanged;
model.ItemsChanged += Model_ItemsChanged;
FetchContent();
}
// Theoretically, we should unify this with the one in CommandPalettePageViewModelFactory

View File

@@ -209,8 +209,8 @@ public sealed partial class DockBandViewModel : ExtensionObjectViewModel
var list = command.Model.Unsafe as IListPage;
if (list is not null)
{
InitializeFromList(list);
list.ItemsChanged += HandleItemsChanged;
InitializeFromList(list);
}
else
{

View File

@@ -957,8 +957,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
Filters?.InitializeProperties();
UpdateProperty(nameof(Filters));
FetchItems(true);
model.ItemsChanged += Model_ItemsChanged;
FetchItems(true);
}
private static IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)

View File

@@ -61,10 +61,12 @@ public static class ThumbnailHelper
// these are windows constants and mangling them is goofy
#pragma warning disable SA1310 // Field names should not contain underscore
#pragma warning disable SA1306 // Field names should begin with lower-case letter
private const uint SHGFI_ICON = 0x000000100;
private const uint SHGFI_LARGEICON = 0x000000000;
private const uint SHGFI_SHELLICONSIZE = 0x000000004;
private const uint SHGFI_SYSICONINDEX = 0x000004000;
private const uint SHGFI_PIDL = 0x000000008;
private const int SHIL_LARGE = 0; // 32×32 px system large-icon list
private const int SHIL_JUMBO = 4; // 256×256 px jumbo-icon list
private const int SHIL_JUMBO = 4;
private const int ILD_TRANSPARENT = 1;
#pragma warning restore SA1306 // Field names should begin with lower-case letter
#pragma warning restore SA1310 // Field names should not contain underscore
@@ -110,18 +112,17 @@ public static class ThumbnailHelper
{
hIcon = GetLargestIcon(pidl);
}
else
if (hIcon == 0)
{
hIcon = GetNormalSizeIcon(pidl);
var shinfo = default(NativeMethods.SHFILEINFO);
var fileInfoResult = NativeMethods.SHGetFileInfo(pidl, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SHELLICONSIZE | SHGFI_LARGEICON | SHGFI_PIDL);
if (fileInfoResult != IntPtr.Zero && shinfo.hIcon != IntPtr.Zero)
{
hIcon = shinfo.hIcon;
}
}
// Both GetLargestIcon and GetNormalSizeIcon use SHGFI_SYSICONINDEX|SHGFI_PIDL (which
// does NOT trigger IShellIconOverlayIdentifier handlers) to obtain the icon index, then
// retrieve the icon from the system image list. This avoids the SHGFI_ICON path that
// fatally aborts the process in .NET 9 when certain third-party shell extensions (e.g.
// PlasticSCM / Unity Version Control) are installed. The crash is a C-runtime
// STATUS_STACK_BUFFER_OVERRUN / __fastfail that bypasses all .NET exception handlers
// and cannot be caught; it must be prevented by not invoking the unsafe path.
if (hIcon == 0)
{
return null;
@@ -142,26 +143,6 @@ public static class ThumbnailHelper
}
}
private static nint GetNormalSizeIcon(IntPtr pidl)
{
var shinfo = default(NativeMethods.SHFILEINFO);
// SHGFI_SYSICONINDEX with SHGFI_PIDL returns the system image list index without
// loading icon overlay handlers. We then retrieve the icon directly from the system
// large-icon image list, which is safe even with third-party shell extensions installed.
NativeMethods.SHGetFileInfo(pidl, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX | SHGFI_PIDL);
var hIcon = IntPtr.Zero;
var iID_IImageList = IID_IImageList;
if (NativeMethods.SHGetImageList(SHIL_LARGE, ref iID_IImageList, out var imageListPtr) == 0 && imageListPtr != IntPtr.Zero)
{
hIcon = NativeMethods.ImageList_GetIcon(imageListPtr, shinfo.iIcon, ILD_TRANSPARENT);
}
return hIcon;
}
private static async Task<IRandomAccessStream?> GetFileIconStreamUsingFilePath(string filePath, bool jumbo)
{
nint hIcon = 0;
@@ -173,16 +154,19 @@ public static class ThumbnailHelper
}
// If we didn't want the JUMBO icon, or didn't find it, fall back to
// extracting the icon directly from the file. We intentionally avoid
// SHGetFileInfo with SHGFI_ICON here because it invokes shell icon
// overlay handlers (IShellIconOverlayIdentifier) which can fatally crash
// the process in .NET 9 when certain third-party shell extensions
// (e.g. PlasticSCM / Unity Version Control) are installed.
// SHDefExtractIconW (used by ExtractIconHandle) only invokes the file
// type's IExtractIcon handler and does not load overlay handlers.
// the normal icon lookup
if (hIcon == 0)
{
hIcon = ExtractIconHandle(filePath, 0, jumbo);
var shinfo = default(NativeMethods.SHFILEINFO);
var hr = NativeMethods.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SHELLICONSIZE);
if (hr == 0 || shinfo.hIcon == 0)
{
return null;
}
hIcon = shinfo.hIcon;
}
if (hIcon == 0)
@@ -281,7 +265,7 @@ public static class ThumbnailHelper
return null;
}
// if it's an .exe and without a path, let's find on path:
// if it's and .exe and without a path, let's find on path:
if (Path.GetExtension(path).Equals(".exe", StringComparison.OrdinalIgnoreCase) && !Path.IsPathRooted(path))
{
var paths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [];