Files
PowerToys/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs

265 lines
12 KiB
C#
Raw Normal View History

// 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;
using System.IO.Abstractions;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using Wox.Plugin.Logger;
namespace Wox.Infrastructure.Image
{
[Flags]
public enum ThumbnailOptions
{
RESIZETOFIT = 0x00,
BiggerSizeOk = 0x01,
InMemoryOnly = 0x02,
IconOnly = 0x04,
ThumbnailOnly = 0x08,
InCacheOnly = 0x10,
}
[fxcop] Wox.Infrastructure (#7590) * CA1052: Static holder types should be Static or NotInheritable * CA1041: Provide ObsoleteAttribute message * CA1062: Validate arguments of public methods * CA1304: Specify CultureInfo / CA1305: Specify IFormatProvider / CA1307: Specify StringComparison for clarity * CA1802: Use Literals Where Appropriate * CA1820: Test for empty strings using string length * CA1707: Identifiers should not contain underscores * CA1805: Do not initialize unnecessarily. * CA1822: Mark members as static * CA2227: Collection properties should be read only * CA1054: URI parameters should not be strings * CA1031: Do not catch general exception types * CA1060: Move P/Invokes to NativeMethods class * CA1308: Normalize strings to uppercase * CA2000: Dispose objects before losing scope / CA2234: Pass System.Uri objects instead of strings * CA2234: Pass System.Uri objects instead of strings * CA1044: Properties should not be write only * CA1716: Identifiers should not match keywords * CA2007: Do not directly await a Task * CA2007: Do not directly await a Task (Suppressed) * CA5350: Do Not Use Weak Cryptographic Algorithms (Suppressed) * CA1724: Type names should not match namespaces (renamed Settings.cs to PowerToysRunSettings.cs) * CA1033: Interface methods should be callable by child types (Added sealed modifier to class) * CA1724: Type names should not match namespaces (Renamed Plugin.cs to RunPlugin.cs) * CA1724: Type names should not match namespaces (Renamed Http.cs to HttpClient.cs) * CA5364: Do not use deprecated security protocols (Remove unused code) * Enabled FxCopAnalyzer for Wox.Infrastructure * fixed comment * Addressed comments - Changed Ordinal to InvariantCulture - Added comments - Removed unused obsolete code - Removed unused method (CA2007: Do not directly await a Task) * Addressed comments - fixed justification for CA1031 suppression * Addressed comments - Fixed justification for CA1031 suppression in Wox.Core/Wox.Plugin
2020-10-29 17:52:35 -07:00
public static class WindowsThumbnailProvider
{
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
internal enum HResult
{
Ok = 0x0000,
False = 0x0001,
InvalidArguments = unchecked((int)0x80070057),
OutOfMemory = unchecked((int)0x8007000E),
NoInterface = unchecked((int)0x80004002),
Fail = unchecked((int)0x80004005),
ExtractionFailed = unchecked((int)0x8004B200),
ElementNotFound = unchecked((int)0x80070490),
TypeElementNotFound = unchecked((int)0x8002802B),
NoObject = unchecked((int)0x800401E5),
Win32ErrorCanceled = 1223,
Canceled = unchecked((int)0x800704C7),
ResourceInUse = unchecked((int)0x800700AA),
AccessDenied = unchecked((int)0x80030005),
}
[ComImport]
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItemImageFactory
{
[PreserveSig]
HResult GetImage(
[In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
[In] ThumbnailOptions flags,
[Out] out IntPtr phbm);
}
[StructLayout(LayoutKind.Sequential)]
internal struct NativeSize
{
private int width;
private int height;
public int Width
{
set { width = value; }
}
public int Height
{
set { height = value; }
}
}
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
IntPtr hBitmap = IntPtr.Zero;
if (Path.GetExtension(fileName).Equals(".lnk", StringComparison.OrdinalIgnoreCase))
{
hBitmap = ExtractIconToHBitmap(fileName);
}
else
{
hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
}
try
{
return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
finally
{
// delete HBitmap to avoid memory leaks
[fxcop] Wox.Infrastructure (#7590) * CA1052: Static holder types should be Static or NotInheritable * CA1041: Provide ObsoleteAttribute message * CA1062: Validate arguments of public methods * CA1304: Specify CultureInfo / CA1305: Specify IFormatProvider / CA1307: Specify StringComparison for clarity * CA1802: Use Literals Where Appropriate * CA1820: Test for empty strings using string length * CA1707: Identifiers should not contain underscores * CA1805: Do not initialize unnecessarily. * CA1822: Mark members as static * CA2227: Collection properties should be read only * CA1054: URI parameters should not be strings * CA1031: Do not catch general exception types * CA1060: Move P/Invokes to NativeMethods class * CA1308: Normalize strings to uppercase * CA2000: Dispose objects before losing scope / CA2234: Pass System.Uri objects instead of strings * CA2234: Pass System.Uri objects instead of strings * CA1044: Properties should not be write only * CA1716: Identifiers should not match keywords * CA2007: Do not directly await a Task * CA2007: Do not directly await a Task (Suppressed) * CA5350: Do Not Use Weak Cryptographic Algorithms (Suppressed) * CA1724: Type names should not match namespaces (renamed Settings.cs to PowerToysRunSettings.cs) * CA1033: Interface methods should be callable by child types (Added sealed modifier to class) * CA1724: Type names should not match namespaces (Renamed Plugin.cs to RunPlugin.cs) * CA1724: Type names should not match namespaces (Renamed Http.cs to HttpClient.cs) * CA5364: Do not use deprecated security protocols (Remove unused code) * Enabled FxCopAnalyzer for Wox.Infrastructure * fixed comment * Addressed comments - Changed Ordinal to InvariantCulture - Added comments - Removed unused obsolete code - Removed unused method (CA2007: Do not directly await a Task) * Addressed comments - fixed justification for CA1031 suppression * Addressed comments - Fixed justification for CA1031 suppression in Wox.Core/Wox.Plugin
2020-10-29 17:52:35 -07:00
NativeMethods.DeleteObject(hBitmap);
}
}
private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
{
IntPtr hBitmap = IntPtr.Zero;
IShellItem nativeShellItem = null;
try
{
Guid shellItem2Guid = new Guid(IShellItem2Guid);
int retCode = NativeMethods.SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);
if (retCode != 0)
{
Log.Error($"Error while creating item. retCode:{retCode} ", MethodBase.GetCurrentMethod().DeclaringType);
throw Marshal.GetExceptionForHR(retCode);
}
NativeSize nativeSize = new NativeSize
{
Width = width,
Height = height,
};
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap);
// if extracting image thumbnail and failed, extract shell icon
if (options == ThumbnailOptions.ThumbnailOnly && hr == HResult.ExtractionFailed)
{
hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, ThumbnailOptions.IconOnly, out hBitmap);
}
if (hr != HResult.Ok)
{
throw Marshal.GetExceptionForHR((int)hr);
}
return hBitmap;
}
catch (System.Exception ex)
{
Log.Exception($"Error while extracting thumbnail for {fileName}", ex, MethodBase.GetCurrentMethod().DeclaringType);
throw;
}
finally
{
if (nativeShellItem != null)
{
Marshal.ReleaseComObject(nativeShellItem);
}
}
}
public static IntPtr ExtractIconToHBitmap(string fileName)
{
// Extracts the icon associated with the file
using (System.Drawing.Icon thumbnailIcon = System.Drawing.Icon.ExtractAssociatedIcon(fileName))
{
// Convert to Bitmap
using (System.Drawing.Bitmap bitmap = thumbnailIcon.ToBitmap())
{
return bitmap.GetHbitmap();
}
}
}
private static bool logReportedAdobeReaderDetected; // Keep track if Adobe Reader detection has been logged yet.
private static bool logReportedErrorInDetectingAdobeReader; // Keep track if we reported an exception while trying to detect Adobe Reader yet.
private static bool adobeReaderDetectionLastResult; // The last result when Adobe Reader detection has read the registry.
private static DateTime adobeReaderDetectionLastTime; // The last time when Adobe Reader detection has read the registry.
// We have to evaluate this in real time to not crash, if the user switches to Adobe Reader/Acrobat Pro after starting PT Run.
// Adobe registers its thumbnail handler always in "HKCR\Acrobat.Document.*\shellex\{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}".
public static bool DoesPdfUseAcrobatAsProvider()
{
// If the last run is not more than five seconds ago use its result.
// Doing this we minimize the amount of Registry queries, if more than one new PDF file is shown in the results.
if ((DateTime.Now - adobeReaderDetectionLastTime).TotalSeconds < 5)
{
return adobeReaderDetectionLastResult;
}
// If cache condition is false, then query the registry.
try
{
// First detect if there is a provider other than Adobe. For example PowerToys.
// Generic GUIDs used by Explorer to identify the configured provider types: {E357FCCD-A995-4576-B01F-234630154E96} = File thumbnail, {BB2E617C-0920-11d1-9A0B-00C04FC2D6C1} = Image thumbnail.
RegistryKey key1 = Registry.ClassesRoot.OpenSubKey(".pdf\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", false);
string value1 = (string)key1?.GetValue(string.Empty);
key1?.Close();
RegistryKey key2 = Registry.ClassesRoot.OpenSubKey(".pdf\\shellex\\{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}", false);
string value2 = (string)key2?.GetValue(string.Empty);
key2?.Close();
if (!string.IsNullOrEmpty(value1) || !string.IsNullOrEmpty(value2))
{
// A provider other than Adobe is used. (For example the PowerToys PDF Thumbnail provider.)
logReportedAdobeReaderDetected = false; // Reset log marker to report when Adobe is reused. (Example: Adobe -> Test PowerToys. -> Back to Adobe.)
adobeReaderDetectionLastResult = false;
adobeReaderDetectionLastTime = DateTime.Now;
return false;
}
// Then detect if Adobe is the default PDF application.
// The global config can be found under "HKCR\.pdf", but the "UserChoice" key under HKCU has precedence.
RegistryKey pdfKeyUser = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.pdf\\UserChoice", false);
string pdfAppUser = (string)pdfKeyUser?.GetValue("ProgId");
pdfKeyUser?.Close();
RegistryKey pdfKeyGlobal = Registry.ClassesRoot.OpenSubKey(".pdf", false);
string pdfAppGlobal = (string)pdfKeyGlobal?.GetValue(string.Empty);
pdfKeyGlobal?.Close();
string pdfApp = !string.IsNullOrEmpty(pdfAppUser) ? pdfAppUser : pdfAppGlobal; // User choice has precedence.
if (string.IsNullOrEmpty(pdfApp) || !pdfApp.StartsWith("Acrobat.Document.", StringComparison.OrdinalIgnoreCase))
{
// Adobe is not used as PDF application.
logReportedAdobeReaderDetected = false; // Reset log marker to report when Adobe is reused. (Example: Adobe -> Test PowerToys. -> Back to Adobe.)
adobeReaderDetectionLastResult = false;
adobeReaderDetectionLastTime = DateTime.Now;
return false;
}
// Detect if the thumbnail handler from Adobe is disabled.
RegistryKey adobeAppKey = Registry.ClassesRoot.OpenSubKey(pdfApp + "\\shellex\\{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}", false);
string adobeAppProvider = (string)adobeAppKey?.GetValue(string.Empty);
adobeAppKey?.Close();
if (string.IsNullOrEmpty(adobeAppProvider))
{
// No Adobe handler.
logReportedAdobeReaderDetected = false; // Reset log marker to report when Adobe is reused. (Example: Adobe -> Test PowerToys. -> Back to Adobe.)
adobeReaderDetectionLastResult = false;
adobeReaderDetectionLastTime = DateTime.Now;
return false;
}
// Thumbnail handler from Adobe is enabled and used.
if (!logReportedAdobeReaderDetected)
{
logReportedAdobeReaderDetected = true;
Log.Info("Adobe Reader / Adobe Acrobat Pro has been detected as the PDF thumbnail provider.", MethodBase.GetCurrentMethod().DeclaringType);
}
adobeReaderDetectionLastResult = true;
adobeReaderDetectionLastTime = DateTime.Now;
return true;
}
catch (System.Exception ex)
{
if (!logReportedErrorInDetectingAdobeReader)
{
logReportedErrorInDetectingAdobeReader = true;
Log.Exception("Got an exception while trying to detect Adobe Reader / Adobe Acrobat Pro as PDF thumbnail provider. To prevent PT Run from a Dispatcher crash, we report that Adobe Reader / Adobe Acrobat Pro is used and show only the PDF icon in the results.", ex, MethodBase.GetCurrentMethod().DeclaringType);
}
Updates for check-spelling v0.0.25 (#40386) ## Summary of the Pull Request - #39572 updated check-spelling but ignored: > 🐣 Breaking Changes [Code Scanning action requires a Code Scanning Ruleset](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset) If you use SARIF reporting, then instead of the workflow yielding an ❌ when it fails, it will rely on [github-advanced-security 🤖](https://github.com/apps/github-advanced-security) to report the failure. You will need to adjust your checks for PRs. This means that check-spelling hasn't been properly doing its job 😦. I'm sorry, I should have pushed a thing to this repo earlier,... Anyway, as with most refreshes, this comes with a number of fixes, some are fixes for typos that snuck in before the 0.0.25 upgrade, some are for things that snuck in after, some are based on new rules in spell-check-this, and some are hand written patterns based on running through this repository a few times. About the 🐣 **breaking change**: someone needs to create a ruleset for this repository (see [Code Scanning action requires a Code Scanning Ruleset: Sample ruleset ](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset#sample-ruleset)). The alternative to adding a ruleset is to change the condition to not use sarif for this repository. In general, I think the github integration from sarif is prettier/more helpful, so I think that it's the better choice. You can see an example of it working in: - https://github.com/check-spelling-sandbox/PowerToys/pull/23 --------- Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Co-authored-by: Mike Griese <migrie@microsoft.com> Co-authored-by: Dustin L. Howett <dustin@howett.net>
2025-07-08 18:16:52 -04:00
// If we fail to detect it, we return that Adobe is used. Otherwise, we could run into the Dispatcher crash.
// (This only results in showing the icon instead of a thumbnail. It has no other functional impact.)
return true;
}
}
}
}