diff --git a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
index 4ce0df0260d..9164e434aa5 100644
--- a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
@@ -1,13 +1,14 @@
-using System;
-using System.Runtime.InteropServices;
+using System;
using System.IO;
+using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
+using IniParser;
using Windows.Win32;
using Windows.Win32.Foundation;
-using Windows.Win32.UI.Shell;
using Windows.Win32.Graphics.Gdi;
+using Windows.Win32.UI.Shell;
namespace Flow.Launcher.Infrastructure.Image
{
@@ -35,9 +36,32 @@ public class WindowsThumbnailProvider
private static readonly HRESULT S_PATHNOTFOUND = (HRESULT)0x8004B205;
+ private const string UrlExtension = ".url";
+
+ ///
+ /// Obtains a BitmapSource thumbnail for the specified file.
+ ///
+ ///
+ /// If the file is a Windows URL shortcut (".url"), the method attempts to resolve the shortcut's icon and use that for the thumbnail; otherwise it requests a thumbnail for the file path. The native HBITMAP used to create the BitmapSource is always released to avoid native memory leaks.
+ ///
+ /// Path to the file (can be a regular file or a ".url" shortcut).
+ /// Requested thumbnail width in pixels.
+ /// Requested thumbnail height in pixels.
+ /// Thumbnail extraction options (flags) controlling fallback and caching behavior.
+ /// A BitmapSource representing the requested thumbnail.
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
- HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
+ HBITMAP hBitmap;
+
+ var extension = Path.GetExtension(fileName)?.ToLowerInvariant();
+ if (extension is UrlExtension)
+ {
+ hBitmap = GetHBitmapForUrlFile(fileName, width, height, options);
+ }
+ else
+ {
+ hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
+ }
try
{
@@ -50,6 +74,21 @@ public static BitmapSource GetThumbnail(string fileName, int width, int height,
}
}
+ ///
+ /// Obtains a native HBITMAP for the specified file at the requested size using the Windows Shell image factory.
+ ///
+ ///
+ /// If is and thumbnail extraction fails
+ /// due to extraction errors or a missing path, the method falls back to requesting an icon ().
+ /// The returned HBITMAP is a raw GDI handle; the caller is responsible for releasing it (e.g., via DeleteObject) to avoid native memory leaks.
+ ///
+ /// Path to the file to thumbnail.
+ /// Requested thumbnail width in pixels.
+ /// Requested thumbnail height in pixels.
+ /// Thumbnail request flags that control behavior (e.g., ThumbnailOnly, IconOnly).
+ /// An HBITMAP handle containing the image. Caller must free the handle when finished.
+ /// If creating the shell item fails (HRESULT returned by SHCreateItemFromParsingName).
+ /// If the shell item does not expose IShellItemImageFactory or if an unexpected error occurs while obtaining the image.
private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
{
var retCode = PInvoke.SHCreateItemFromParsingName(
@@ -108,5 +147,43 @@ private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height,
return hBitmap;
}
+
+ ///
+ /// Obtains an HBITMAP for a Windows .url shortcut by resolving its IconFile entry and delegating to GetHBitmap.
+ ///
+ ///
+ /// The method parses the .url file as an INI, looks in the "InternetShortcut" section for the "IconFile" entry,
+ /// and requests a bitmap for that icon path. If no IconFile is present or any error occurs while reading or
+ /// resolving the icon, it falls back to requesting a thumbnail for the .url file itself.
+ ///
+ /// Path to the .url shortcut file.
+ /// Requested thumbnail width (pixels).
+ /// Requested thumbnail height (pixels).
+ /// ThumbnailOptions flags controlling extraction behavior.
+ /// An HBITMAP containing the requested image; callers are responsible for freeing the native handle.
+ private static unsafe HBITMAP GetHBitmapForUrlFile(string fileName, int width, int height, ThumbnailOptions options)
+ {
+ HBITMAP hBitmap;
+
+ try
+ {
+ var parser = new FileIniDataParser();
+ var data = parser.ReadFile(fileName);
+ var urlSection = data["InternetShortcut"];
+
+ var iconPath = urlSection?["IconFile"];
+ if (string.IsNullOrEmpty(iconPath))
+ {
+ throw new FileNotFoundException();
+ }
+ hBitmap = GetHBitmap(Path.GetFullPath(iconPath), width, height, options);
+ }
+ catch
+ {
+ hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
+ }
+
+ return hBitmap;
+ }
}
}