diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index c0ab46439d4a..aff793714220 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -1429,6 +1429,7 @@ ppsi
ppsid
ppsrm
ppsrree
+ppstm
ppsz
pptal
ppv
@@ -1471,6 +1472,8 @@ psfi
Psr
psrm
psrree
+pstatstg
+pstm
pstr
pstream
pstrm
@@ -1823,6 +1826,7 @@ STDMETHODCALLTYPE
STDMETHODIMP
stefan
Stereolithography
+STGC
STGM
STGMEDIUM
sticpl
@@ -2188,6 +2192,7 @@ wnd
WNDCLASS
WNDCLASSEX
WNDCLASSEXW
+WNDCLASSW
WNDPROC
wordpad
workaround
diff --git a/src/modules/peek/Peek.Common/NativeMethods.txt b/src/modules/peek/Peek.Common/NativeMethods.txt
index c5ac7c039947..5fed179af898 100644
--- a/src/modules/peek/Peek.Common/NativeMethods.txt
+++ b/src/modules/peek/Peek.Common/NativeMethods.txt
@@ -2,4 +2,9 @@
_SHCONTF
SIGDN
SHGDNF
-SIATTRIBFLAGS
\ No newline at end of file
+SIATTRIBFLAGS
+IInitializeWithFile
+IInitializeWithItem
+IInitializeWithStream
+IPreviewHandler
+IPreviewHandlerVisuals
\ No newline at end of file
diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml b/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml
new file mode 100644
index 000000000000..b67292ec4b4f
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml.cs b/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml.cs
new file mode 100644
index 000000000000..006fd76a844b
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml.cs
@@ -0,0 +1,209 @@
+// 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.Runtime.CompilerServices;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.UI;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Gdi;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.UI.WindowsAndMessaging;
+
+namespace Peek.FilePreviewer.Controls
+{
+ [INotifyPropertyChanged]
+ public unsafe sealed partial class ShellPreviewHandlerControl : UserControl
+ {
+ // Mica fallback colors
+ private static readonly COLORREF LightThemeBgColor = new(0x00f3f3f3);
+ private static readonly COLORREF DarkThemeBgColor = new(0x00202020);
+
+ private static readonly HBRUSH LightThemeBgBrush = PInvoke.CreateSolidBrush(LightThemeBgColor);
+ private static readonly HBRUSH DarkThemeBgBrush = PInvoke.CreateSolidBrush(DarkThemeBgColor);
+
+ [ObservableProperty]
+ private IPreviewHandler? source;
+
+ private HWND containerHwnd;
+ private WNDPROC containerWndProc;
+ private HBRUSH containerBgBrush;
+ private RECT controlRect;
+
+ public event EventHandler? HandlerLoaded;
+
+ public event EventHandler? HandlerError;
+
+ public static readonly DependencyProperty HandlerVisibilityProperty = DependencyProperty.Register(
+ nameof(HandlerVisibility),
+ typeof(Visibility),
+ typeof(ShellPreviewHandlerControl),
+ new PropertyMetadata(Visibility.Collapsed, new PropertyChangedCallback((d, e) => ((ShellPreviewHandlerControl)d).OnHandlerVisibilityChanged())));
+
+ // Must have its own visibility property so resize events can still fire
+ public Visibility HandlerVisibility
+ {
+ get { return (Visibility)GetValue(HandlerVisibilityProperty); }
+ set { SetValue(HandlerVisibilityProperty, value); }
+ }
+
+ public ShellPreviewHandlerControl()
+ {
+ InitializeComponent();
+
+ containerWndProc = ContainerWndProc;
+ }
+
+ partial void OnSourceChanged(IPreviewHandler? value)
+ {
+ if (Source != null)
+ {
+ UpdatePreviewerTheme();
+
+ try
+ {
+ // Attach the preview handler to the container window
+ Source.SetWindow(containerHwnd, (RECT*)Unsafe.AsPointer(ref controlRect));
+ Source.DoPreview();
+
+ HandlerLoaded?.Invoke(this, EventArgs.Empty);
+ }
+ catch
+ {
+ HandlerError?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ }
+
+ private void OnHandlerVisibilityChanged()
+ {
+ if (HandlerVisibility == Visibility.Visible)
+ {
+ PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_SHOW);
+ IsEnabled = true;
+
+ // Clears the background from the last previewer
+ // The brush can only be drawn here because flashes will occur during resize
+ PInvoke.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, containerBgBrush);
+ PInvoke.UpdateWindow(containerHwnd);
+ PInvoke.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, IntPtr.Zero);
+ PInvoke.InvalidateRect(containerHwnd, (RECT*)null, true);
+ }
+ else
+ {
+ PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_HIDE);
+ IsEnabled = false;
+ }
+ }
+
+ private void UpdatePreviewerTheme()
+ {
+ COLORREF bgColor, fgColor;
+ switch (ActualTheme)
+ {
+ case ElementTheme.Light:
+ bgColor = LightThemeBgColor;
+ fgColor = new COLORREF(0x00000000); // Black
+
+ containerBgBrush = LightThemeBgBrush;
+ break;
+
+ case ElementTheme.Dark:
+ default:
+ bgColor = DarkThemeBgColor;
+ fgColor = new COLORREF(0x00FFFFFF); // White
+
+ containerBgBrush = DarkThemeBgBrush;
+ break;
+ }
+
+ if (Source is IPreviewHandlerVisuals visuals)
+ {
+ visuals.SetBackgroundColor(bgColor);
+ visuals.SetTextColor(fgColor);
+
+ // Changing the previewer colors might not always redraw itself
+ PInvoke.InvalidateRect(containerHwnd, (RECT*)null, true);
+ }
+ }
+
+ private LRESULT ContainerWndProc(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam)
+ {
+ // Here for future use :)
+ return PInvoke.DefWindowProc(hWnd, msg, wParam, lParam);
+ }
+
+ private void UserControl_Loaded(object sender, RoutedEventArgs e)
+ {
+ fixed (char* pContainerClassName = "PeekShellPreviewHandlerContainer")
+ {
+ PInvoke.RegisterClass(new WNDCLASSW()
+ {
+ lpfnWndProc = containerWndProc,
+ lpszClassName = pContainerClassName,
+ });
+
+ // Create the container window to host the preview handler
+ containerHwnd = PInvoke.CreateWindowEx(
+ WINDOW_EX_STYLE.WS_EX_LAYERED,
+ pContainerClassName,
+ null,
+ WINDOW_STYLE.WS_CHILD,
+ 0, // X
+ 0, // Y
+ 0, // Width
+ 0, // Height
+ (HWND)Win32Interop.GetWindowFromWindowId(XamlRoot.ContentIslandEnvironment.AppWindowId), // Peek UI window
+ HMENU.Null,
+ HINSTANCE.Null);
+
+ // Allows the preview handlers to display properly
+ PInvoke.SetLayeredWindowAttributes(containerHwnd, default, byte.MaxValue, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA);
+ }
+ }
+
+ private void UserControl_EffectiveViewportChanged(FrameworkElement sender, EffectiveViewportChangedEventArgs args)
+ {
+ var dpi = (float)PInvoke.GetDpiForWindow(containerHwnd) / 96;
+
+ // Resize the container window
+ PInvoke.SetWindowPos(
+ containerHwnd,
+ (HWND)0, // HWND_TOP
+ (int)(Math.Abs(args.EffectiveViewport.X) * dpi),
+ (int)(Math.Abs(args.EffectiveViewport.Y) * dpi),
+ (int)(ActualWidth * dpi),
+ (int)(ActualHeight * dpi),
+ SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);
+
+ // Resize the preview handler window
+ controlRect.right = (int)(ActualWidth * dpi);
+ controlRect.bottom = (int)(ActualHeight * dpi);
+ try
+ {
+ Source?.SetRect((RECT*)Unsafe.AsPointer(ref controlRect));
+ }
+ catch
+ {
+ }
+
+ // Resizing the previewer might not always redraw itself
+ PInvoke.InvalidateRect(containerHwnd, (RECT*)null, false);
+ }
+
+ private void UserControl_GotFocus(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ Source?.SetFocus();
+ }
+ catch
+ {
+ }
+ }
+ }
+}
diff --git a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
index 90d9d8544bd5..d7636b87319d 100644
--- a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
+++ b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
@@ -18,6 +18,13 @@
VerticalAlignment="Center"
IsActive="{x:Bind MatchPreviewState(Previewer.State, previewers:PreviewState.Loading), Mode=OneWay}" />
+
+
Previewer as IArchivePreviewer;
+ public IShellPreviewHandlerPreviewer? ShellPreviewHandlerPreviewer => Previewer as IShellPreviewHandlerPreviewer;
+
public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;
public IFileSystemItem Item
@@ -220,6 +223,9 @@ partial void OnPreviewerChanging(IPreviewer? value)
ArchivePreview.Source = null;
BrowserPreview.Source = null;
+ ShellPreviewHandlerPreviewer?.Clear();
+ ShellPreviewHandlerPreview.Source = null;
+
if (Previewer != null)
{
Previewer.PropertyChanged -= Previewer_PropertyChanged;
@@ -268,6 +274,22 @@ private void PreviewBrowser_NavigationCompleted(WebView2 sender, CoreWebView2Nav
}
}
+ private void ShellPreviewHandlerPreview_HandlerLoaded(object sender, EventArgs e)
+ {
+ if (ShellPreviewHandlerPreviewer != null)
+ {
+ ShellPreviewHandlerPreviewer.State = PreviewState.Loaded;
+ }
+ }
+
+ private void ShellPreviewHandlerPreview_HandlerError(object sender, EventArgs e)
+ {
+ if (ShellPreviewHandlerPreviewer != null)
+ {
+ ShellPreviewHandlerPreviewer.State = PreviewState.Error;
+ }
+ }
+
private async void KeyboardAccelerator_CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
if (Previewer != null)
diff --git a/src/modules/peek/Peek.FilePreviewer/NativeMethods.json b/src/modules/peek/Peek.FilePreviewer/NativeMethods.json
new file mode 100644
index 000000000000..dc43b5886133
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/NativeMethods.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://aka.ms/CsWin32.schema.json",
+ "public": false
+}
\ No newline at end of file
diff --git a/src/modules/peek/Peek.FilePreviewer/NativeMethods.txt b/src/modules/peek/Peek.FilePreviewer/NativeMethods.txt
new file mode 100644
index 000000000000..2a6906e2e909
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/NativeMethods.txt
@@ -0,0 +1,14 @@
+IClassFactory
+CoGetClassObject
+CreateSolidBrush
+CreateWindowEx
+DefWindowProc
+GetDpiForWindow
+InvalidateRect
+RegisterClass
+SetClassLongPtr
+SetLayeredWindowAttributes
+SetWindowPos
+ShowWindow
+UpdateWindow
+SHCreateItemFromParsingName
\ No newline at end of file
diff --git a/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj b/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj
index 02eb4c4dc39f..1a8832a4e0e7 100644
--- a/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj
+++ b/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj
@@ -8,6 +8,7 @@
win10-x86;win10-x64;win10-arm64
true
enable
+ true
@@ -17,6 +18,7 @@
+
@@ -29,6 +31,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
@@ -38,6 +44,12 @@
+
+
+ MSBuild:Compile
+
+
+
MSBuild:Compile
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IShellPreviewHandlerPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IShellPreviewHandlerPreviewer.cs
new file mode 100644
index 000000000000..3d71ea926a24
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IShellPreviewHandlerPreviewer.cs
@@ -0,0 +1,15 @@
+// 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 Windows.Win32.UI.Shell;
+
+namespace Peek.FilePreviewer.Previewers
+{
+ public interface IShellPreviewHandlerPreviewer : IPreviewer
+ {
+ public IPreviewHandler? Preview { get; }
+
+ public void Clear();
+ }
+}
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs
index 3ac9ceeceec7..8a505d9f9cf5 100644
--- a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs
@@ -29,6 +29,10 @@ public IPreviewer Create(IFileSystemItem file)
{
return new ArchivePreviewer(file);
}
+ else if (ShellPreviewHandlerPreviewer.IsFileTypeSupported(file.Extension))
+ {
+ return new ShellPreviewHandlerPreviewer(file);
+ }
// Other previewer types check their supported file types here
return CreateDefaultPreviewer(file);
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/Helpers/IStreamWrapper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/Helpers/IStreamWrapper.cs
new file mode 100644
index 000000000000..786bd0d3b81d
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/Helpers/IStreamWrapper.cs
@@ -0,0 +1,104 @@
+// 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;
+using System.Runtime.InteropServices;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.Com;
+
+namespace Peek.FilePreviewer.Previewers.Helpers
+{
+ public unsafe class IStreamWrapper : IStream
+ {
+ public Stream Stream { get; }
+
+ public IStreamWrapper(Stream stream) => Stream = stream;
+
+ public HRESULT Read(void* pv, uint cb, [Optional] uint* pcbRead)
+ {
+ try
+ {
+ int read = Stream.Read(new Span(pv, (int)cb));
+ if (pcbRead != null)
+ {
+ *pcbRead = (uint)read;
+ }
+
+ return (HRESULT)0;
+ }
+ catch (Exception ex)
+ {
+ return (HRESULT)Marshal.GetHRForException(ex);
+ }
+ }
+
+ public HRESULT Write(void* pv, uint cb, [Optional] uint* pcbWritten)
+ {
+ try
+ {
+ Stream.Write(new ReadOnlySpan(pv, (int)cb));
+ if (pcbWritten != null)
+ {
+ *pcbWritten = cb;
+ }
+
+ return (HRESULT)0;
+ }
+ catch (Exception ex)
+ {
+ return (HRESULT)Marshal.GetHRForException(ex);
+ }
+ }
+
+ public void Seek(long dlibMove, STREAM_SEEK dwOrigin, [Optional] ulong* plibNewPosition)
+ {
+ long position = Stream.Seek(dlibMove, (SeekOrigin)dwOrigin);
+ if (plibNewPosition != null)
+ {
+ *plibNewPosition = (ulong)position;
+ }
+ }
+
+ public void SetSize(ulong libNewSize)
+ {
+ Stream.SetLength((long)libNewSize);
+ }
+
+ public void CopyTo(IStream pstm, ulong cb, [Optional] ulong* pcbRead, [Optional] ulong* pcbWritten)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Commit(STGC grfCommitFlags)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Revert()
+ {
+ throw new NotSupportedException();
+ }
+
+ public void LockRegion(ulong libOffset, ulong cb, uint dwLockType)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void UnlockRegion(ulong libOffset, ulong cb, uint dwLockType)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Stat(STATSTG* pstatstg, uint grfStatFlag)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Clone(out IStream ppstm)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/ShellPreviewHandlerPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/ShellPreviewHandlerPreviewer.cs
new file mode 100644
index 000000000000..1d13c4412d79
--- /dev/null
+++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/ShellPreviewHandlerPreviewer.cs
@@ -0,0 +1,237 @@
+// 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.Collections.Concurrent;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.UI.Dispatching;
+using Microsoft.Win32;
+using Peek.Common.Extensions;
+using Peek.Common.Helpers;
+using Peek.Common.Models;
+using Peek.FilePreviewer.Models;
+using Peek.FilePreviewer.Previewers.Helpers;
+using Windows.Win32;
+using Windows.Win32.System.Com;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.UI.Shell.PropertiesSystem;
+using IShellItem = Windows.Win32.UI.Shell.IShellItem;
+
+namespace Peek.FilePreviewer.Previewers
+{
+ public partial class ShellPreviewHandlerPreviewer : ObservableObject, IShellPreviewHandlerPreviewer, IDisposable
+ {
+ private static readonly ConcurrentDictionary HandlerFactories = new();
+
+ [ObservableProperty]
+ private IPreviewHandler? preview;
+
+ [ObservableProperty]
+ private PreviewState state;
+
+ private Stream? fileStream;
+
+ public ShellPreviewHandlerPreviewer(IFileSystemItem file)
+ {
+ FileItem = file;
+ Dispatcher = DispatcherQueue.GetForCurrentThread();
+ }
+
+ private IFileSystemItem FileItem { get; }
+
+ private DispatcherQueue Dispatcher { get; }
+
+ public void Dispose()
+ {
+ Clear();
+ GC.SuppressFinalize(this);
+ }
+
+ public async Task CopyAsync()
+ {
+ await Dispatcher.RunOnUiThread(async () =>
+ {
+ var storageItem = await FileItem.GetStorageItemAsync();
+ ClipboardHelper.SaveToClipboard(storageItem);
+ });
+ }
+
+ public Task GetPreviewSizeAsync(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new PreviewSize { MonitorSize = null });
+ }
+
+ public async Task LoadPreviewAsync(CancellationToken cancellationToken)
+ {
+ Clear();
+ State = PreviewState.Loading;
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Create the preview handler
+ var previewHandler = await Task.Run(() =>
+ {
+ var previewHandlerGuid = GetPreviewHandlerGuid(FileItem.Extension);
+ if (!string.IsNullOrEmpty(previewHandlerGuid))
+ {
+ var clsid = Guid.Parse(previewHandlerGuid);
+
+ bool retry = false;
+ do
+ {
+ unsafe
+ {
+ // This runs the preview handler in a separate process (prevhost.exe)
+ // TODO: Figure out how to get it to run in a low integrity level
+ if (!HandlerFactories.TryGetValue(clsid, out var factory))
+ {
+ var hr = PInvoke.CoGetClassObject(clsid, CLSCTX.CLSCTX_LOCAL_SERVER, null, typeof(IClassFactory).GUID, out var pFactory);
+ Marshal.ThrowExceptionForHR(hr);
+
+ // Storing the factory in memory helps makes the handlers load faster
+ // TODO: Maybe free them after some inactivity or when Peek quits?
+ factory = (IClassFactory)Marshal.GetObjectForIUnknown((IntPtr)pFactory);
+ factory.LockServer(true);
+ HandlerFactories.AddOrUpdate(clsid, factory, (_, _) => factory);
+ }
+
+ try
+ {
+ var iid = typeof(IPreviewHandler).GUID;
+ factory.CreateInstance(null, &iid, out var instance);
+ return instance as IPreviewHandler;
+ }
+ catch
+ {
+ if (!retry)
+ {
+ // Process is probably dead, attempt to get the factory again (once)
+ HandlerFactories.TryRemove(new(clsid, factory));
+ retry = true;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ while (retry);
+ }
+
+ return null;
+ });
+
+ if (previewHandler == null)
+ {
+ State = PreviewState.Error;
+ return;
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Initialize the preview handler with the selected file
+ bool success = await Task.Run(() =>
+ {
+ const uint STGM_READ = 0x00000000;
+ if (previewHandler is IInitializeWithStream initWithStream)
+ {
+ fileStream = File.OpenRead(FileItem.Path);
+ initWithStream.Initialize(new IStreamWrapper(fileStream), STGM_READ);
+ }
+ else if (previewHandler is IInitializeWithItem initWithItem)
+ {
+ var hr = PInvoke.SHCreateItemFromParsingName(FileItem.Path, null, typeof(IShellItem).GUID, out var item);
+ Marshal.ThrowExceptionForHR(hr);
+
+ initWithItem.Initialize((IShellItem)item, STGM_READ);
+ }
+ else if (previewHandler is IInitializeWithFile initWithFile)
+ {
+ unsafe
+ {
+ fixed (char* pPath = FileItem.Path)
+ {
+ initWithFile.Initialize(pPath, STGM_READ);
+ }
+ }
+ }
+ else
+ {
+ // Handler is missing the required interfaces
+ return false;
+ }
+
+ return true;
+ });
+
+ if (!success)
+ {
+ State = PreviewState.Error;
+ return;
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Preview.SetWindow() needs to be set in the control
+ Preview = previewHandler;
+ }
+
+ public void Clear()
+ {
+ if (Preview != null)
+ {
+ try
+ {
+ Preview.Unload();
+ Marshal.FinalReleaseComObject(Preview);
+ }
+ catch
+ {
+ }
+
+ Preview = null;
+ }
+
+ if (fileStream != null)
+ {
+ fileStream.Dispose();
+ fileStream = null;
+ }
+ }
+
+ public static bool IsFileTypeSupported(string fileExt)
+ {
+ return !string.IsNullOrEmpty(GetPreviewHandlerGuid(fileExt));
+ }
+
+ private static string? GetPreviewHandlerGuid(string fileExt)
+ {
+ const string PreviewHandlerKeyPath = "shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}";
+
+ // Search by file extension
+ using var classExtensionKey = Registry.ClassesRoot.OpenSubKey(fileExt);
+ using var classExtensionPreviewHandlerKey = classExtensionKey?.OpenSubKey(PreviewHandlerKeyPath);
+
+ if (classExtensionKey != null && classExtensionPreviewHandlerKey == null)
+ {
+ // Search by file class
+ var className = classExtensionKey.GetValue(null) as string;
+ if (!string.IsNullOrEmpty(className))
+ {
+ using var classKey = Registry.ClassesRoot.OpenSubKey(className);
+ using var classPreviewHandlerKey = classKey?.OpenSubKey(PreviewHandlerKeyPath);
+
+ return classPreviewHandlerKey?.GetValue(null) as string;
+ }
+ }
+
+ return classExtensionPreviewHandlerKey?.GetValue(null) as string;
+ }
+ }
+}