diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index bfbba4b263..3a50dce6b3 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1479,6 +1479,7 @@ rescap resgen resheader resizers +RESIZETOFIT resmimetype RESOURCEID resourcemanager diff --git a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs index 3cc5832c5c..ef30ab672d 100644 --- a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs +++ b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs @@ -180,10 +180,17 @@ namespace Wox.Infrastructure.Image image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.ThumbnailOnly); } } + else if (extension == ".pdf" && WindowsThumbnailProvider.DoesPdfUseAcrobatAsProvider()) + { + // The PDF thumbnail provider from Adobe Reader and Acrobat Pro lets crash PT Run with an Dispatcher exception. (https://github.com/microsoft/PowerToys/issues/18166) + // To not run into the crash, we only request the icon of PDF files if the PDF thumbnail handler is set to Adobe Reader/Acrobat Pro. + type = ImageType.File; + image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.IconOnly); + } else { type = ImageType.File; - image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.None); + image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.RESIZETOFIT); } } else diff --git a/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs b/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs index 267b062c09..8200a6409e 100644 --- a/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs +++ b/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs @@ -4,17 +4,20 @@ 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 { - None = 0x00, + RESIZETOFIT = 0x00, BiggerSizeOk = 0x01, InMemoryOnly = 0x02, IconOnly = 0x04, @@ -125,5 +128,97 @@ namespace Wox.Infrastructure.Image throw new InvalidComObjectException($"Error while extracting thumbnail for {fileName}", Marshal.GetExceptionForHR((int)hr)); } + + 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); + } + + // 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; + } + } } }