diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt
index 8746ea6d2b3a..2cd8c6234c8c 100644
--- a/src/Files.App.CsWin32/NativeMethods.txt
+++ b/src/Files.App.CsWin32/NativeMethods.txt
@@ -147,3 +147,11 @@ GetModuleHandle
RegisterClassEx
CREATESTRUCTW
AssocQueryString
+IPreviewHandlerFrame
+IPreviewHandlerVisuals
+IObjectWithSite
+IInitializeWithStream
+IInitializeWithStreamNative
+IInitializeWithFile
+IInitializeWithItem
+SHCreateStreamOnFileEx
diff --git a/src/Files.App/Utils/Shell/ItemStreamHelper.cs b/src/Files.App/Utils/Shell/ItemStreamHelper.cs
deleted file mode 100644
index 523845e21d8c..000000000000
--- a/src/Files.App/Utils/Shell/ItemStreamHelper.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using Vanara.PInvoke;
-
-namespace Files.App.Utils.Shell
-{
- public static class ItemStreamHelper
- {
- static readonly Guid IShellItemIid = Guid.ParseExact("43826d1e-e718-42ee-bc55-a1e261c37bfe", "d");
-
- public static IntPtr IShellItemFromPath(string path)
- {
- IntPtr psi;
- Guid iid = IShellItemIid;
- var hr = Win32PInvoke.SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid, out psi);
- if ((int)hr < 0)
- return IntPtr.Zero;
- return psi;
- }
-
- public static IntPtr IStreamFromPath(string path)
- {
- IntPtr pstm;
- var hr = Win32PInvoke.SHCreateStreamOnFileEx(path,
- STGM.STGM_READ | STGM.STGM_FAILIFTHERE | STGM.STGM_SHARE_DENY_NONE,
- 0, 0, IntPtr.Zero, out pstm);
- if ((int)hr < 0)
- return IntPtr.Zero;
- return pstm;
- }
-
- public static void ReleaseObject(IntPtr obj)
- {
- Marshal.Release(obj);
- }
- }
-}
diff --git a/src/Files.App/Utils/Shell/PreviewHandler.cs b/src/Files.App/Utils/Shell/PreviewHandler.cs
index 06160b4c2a21..18e7978a11ee 100644
--- a/src/Files.App/Utils/Shell/PreviewHandler.cs
+++ b/src/Files.App/Utils/Shell/PreviewHandler.cs
@@ -1,443 +1,261 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
+using LibGit2Sharp;
using System.Runtime.InteropServices;
using Windows.UI;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.System.Com;
+using Windows.Win32.System.Ole;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.UI.Shell.PropertiesSystem;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Files.App.Utils.Shell
{
///
- /// Credits: https://github.com/GeeLaw/PreviewHost/
+ /// Provides a set of functionalities to interact with Windows preview handlers.
///
- public sealed class PreviewHandler : IDisposable
+ ///
+ /// Credit:
+ ///
+ public unsafe sealed class PreviewHandler : IDisposable
{
// Fields
- private static readonly Guid IPreviewHandlerIid = Guid.ParseExact("8895b1c6-b41f-4c1c-a562-0d564250836f", "d");
+ private readonly IPreviewHandlerFrame.Interface _previewHandlerFrame;
+ private readonly IPreviewHandler* _pPreviewHandler;
+ private readonly IPreviewHandlerVisuals* _previewHandlerVisuals;
+ private readonly HWND _hWnd;
+ private bool _disposed;
+ private bool _initialized;
+ private bool _shown;
- #region IPreviewHandlerFrame support
-
- [StructLayout(LayoutKind.Sequential)]
- public struct PreviewHandlerFrameInfo
- {
- public nint AcceleratorTableHandle;
- public uint AcceleratorEntryCount;
- }
-
- [ComImport, Guid("fec87aaf-35f9-447a-adb7-20234491401a"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IPreviewHandlerFrame
- {
- [PreserveSig]
- HRESULT GetWindowContext(out PreviewHandlerFrameInfo pinfo);
- [PreserveSig]
- HRESULT TranslateAccelerator(ref MSG pmsg);
- }
+ // Initializer
- public sealed class PreviewHandlerFrame : IPreviewHandlerFrame, IDisposable
+ ///
+ /// Initializes an instance of class.
+ ///
+ ///
+ ///
+ public PreviewHandler(Guid clsid, HWND frame)
{
- bool disposed;
- nint hwnd;
+ _disposed = true;
+ _initialized = false;
+ _shown = false;
+ _hWnd = frame;
- public PreviewHandlerFrame(nint frame)
- {
- disposed = true;
- disposed = false;
- hwnd = frame;
- }
+ // Initialize preview handler's frame
+ _previewHandlerFrame = new CPreviewHandlerFrame(frame);
- public void Dispose()
- {
- disposed = true;
- }
-
- public HRESULT GetWindowContext(out PreviewHandlerFrameInfo pinfo)
+ try
{
- pinfo.AcceleratorTableHandle = nint.Zero;
- pinfo.AcceleratorEntryCount = 0;
- if (disposed)
- return HRESULT.E_FAIL;
- return HRESULT.S_OK;
- }
+ HRESULT hr = PInvoke.CoCreateInstance(clsid, null, CLSCTX.CLSCTX_LOCAL_SERVER, out IPreviewHandler* pPreviewHandler);
+ if (hr.Value < 0)
+ throw new COMException("Cannot create class " + clsid.ToString() + " as IPreviewHandler.", hr.Value);
+ else if (pPreviewHandler is null)
+ throw new COMException("Cannot create class " + clsid.ToString() + " as IPreviewHandler.");
- public HRESULT TranslateAccelerator(ref MSG pmsg)
- {
- if (disposed)
- return HRESULT.E_FAIL;
- return HRESULT.S_FALSE;
- }
- }
-
- #endregion IPreviewHandlerFrame support
-
- #region IPreviewHandler major interfaces
-
- [ComImport, Guid("8895b1c6-b41f-4c1c-a562-0d564250836f"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IPreviewHandler
- {
- [PreserveSig]
- HRESULT SetWindow(nint hwnd, ref RECT prc);
- [PreserveSig]
- HRESULT SetRect(ref RECT prc);
- [PreserveSig]
- HRESULT DoPreview();
- [PreserveSig]
- HRESULT Unload();
- [PreserveSig]
- HRESULT SetFocus();
- [PreserveSig]
- HRESULT QueryFocus(out nint phwnd);
- // TranslateAccelerator is not used here.
- }
+ _pPreviewHandler = pPreviewHandler;
- [ComImport, Guid("196bf9a5-b346-4ef0-aa1e-5dcdb76768b1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IPreviewHandlerVisuals
- {
- [PreserveSig]
- HRESULT SetBackgroundColor(uint color);
- [PreserveSig]
- HRESULT SetFont(ref LOGFONTW plf);
- [PreserveSig]
- HRESULT SetTextColor(uint color);
- }
+ Debug.WriteLine($"IPreviewHandler was successfully initialized from {clsid:B}.");
- static uint ColorRefFromColor(Color color)
- {
- return (((uint)color.B) << 16) | (((uint)color.G) << 8) | ((uint)color.R);
- }
+ // Get IObjectWithSite
+ ComPtr pObjectWithSite = default;
+ _pPreviewHandler->QueryInterface(typeof(IObjectWithSite).GUID, out *(void**)pObjectWithSite.GetAddressOf());
+ if (pObjectWithSite.IsNull)
+ throw new COMException("Cannot cast class " + clsid.ToString() + " as IObjectWithSite.");
- [ComImport, Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IObjectWithSite
- {
- [PreserveSig]
- HRESULT SetSite([In, MarshalAs(UnmanagedType.IUnknown)] object pUnkSite);
- // GetSite is not used.
- }
+ // Set site
+ var pPreviewHandlerFrame = Marshal.GetIUnknownForObject(_previewHandlerFrame);
+ hr = pObjectWithSite.Get()->SetSite((IUnknown*)pPreviewHandlerFrame);
+ if (hr.Value < 0)
+ throw new COMException("Cannot set site to the preview handler object.", hr.Value);
- #endregion IPreviewHandler major interfaces
+ Debug.WriteLine($"Site IPreviewHandlerFrame was successfully set to IPreviewHandler.");
- bool disposed;
- bool init;
- bool shown;
- PreviewHandlerFrame comSite;
- nint hwnd;
- IPreviewHandler previewHandler;
- IPreviewHandlerVisuals visuals;
- nint pPreviewHandler;
+ // Get IPreviewHandlerVisuals
+ IPreviewHandlerVisuals* previewHandlerVisuals = default;
+ _pPreviewHandler->QueryInterface(typeof(IPreviewHandlerVisuals).GUID, out *(void**)&previewHandlerVisuals);
+ if (previewHandlerVisuals == null)
+ throw new COMException("Cannot cast class " + clsid.ToString() + " as IPreviewHandlerVisuals.");
- // Initializer
+ _previewHandlerVisuals = previewHandlerVisuals;
- public PreviewHandler(Guid clsid, nint frame)
- {
- disposed = true;
- init = false;
- shown = false;
- comSite = new PreviewHandlerFrame(frame);
- hwnd = frame;
+ Debug.WriteLine($"IPreviewHandlerVisuals was successfully queried from IPreviewHandler.");
- try
- {
- SetupHandler(clsid);
- disposed = false;
+ _disposed = false;
}
catch
{
- if (previewHandler != null)
- Marshal.ReleaseComObject(previewHandler);
- previewHandler = null;
- if (pPreviewHandler != nint.Zero)
- Marshal.Release(pPreviewHandler);
- pPreviewHandler = nint.Zero;
- comSite.Dispose();
- comSite = null;
+ if (_pPreviewHandler is not null)
+ {
+ _pPreviewHandler->Release();
+ _pPreviewHandler = null;
+ }
+
throw;
}
}
// Methods
- unsafe void SetupHandler(Guid clsid)
+ ///
+ /// Initializes the preview handler with file.
+ ///
+ /// The file name to use to initialize the preview handler.
+ /// True If succeeded, otherwise, false.
+ public bool Initialize(string path)
{
- nint pph;
- var iid = IPreviewHandlerIid;
- var cannotCreate = "Cannot create class " + clsid.ToString() + " as IPreviewHandler.";
- var cannotCast = "Cannot cast class " + clsid.ToString() + " as IObjectWithSite.";
- var cannotSetSite = "Cannot set site to the preview handler object.";
-
- // Important: manually calling CoCreateInstance is necessary.
- // If we use Activator.CreateInstance(Type.GetTypeFromCLSID(...)),
- // CLR will allow in-process server, which defeats isolation and
- // creates strange bugs.
- HRESULT hr = PInvoke.CoCreateInstance(
- clsid,
- null,
- CLSCTX.CLSCTX_LOCAL_SERVER,
- ref iid,
- out void* previewHandlerPtr);
-
- object previewHandlerObject = *(IPreviewHandler*)previewHandlerPtr;
-
- // See https://blogs.msdn.microsoft.com/adioltean/2005/06/24/when-cocreateinstance-returns-0x80080005-co_e_server_exec_failure/
- // CO_E_SERVER_EXEC_FAILURE also tends to happen when debugging in Visual Studio.
- // Moreover, to create the instance in a server at low integrity level, we need
- // to use another thread with low mandatory label. We keep it simple by creating
- // a same-integrity object.
- //if (hr == HRESULT.CO_E_SERVER_EXEC_FAILURE)
- // hr = CoCreateInstance(ref clsid, nint.Zero, ClassContext.LocalServer, ref iid, out pph);
- if ((int)hr < 0)
- throw new COMException(cannotCreate, (int)hr);
-
- pPreviewHandler = new(previewHandlerPtr);
- previewHandler = (IPreviewHandler)previewHandlerObject;
- if (previewHandler == null)
+ List exceptions = [];
+
+ // We try IStream first because this gives us the best security.
+ // If we initialize with string or IShellItem, we have no control over
+ // how the preview handler opens the file, which might decide to open the file for read/write exclusively.
+ try
{
- Marshal.ReleaseComObject(previewHandlerObject);
- throw new COMException(cannotCreate);
- }
+ using ComPtr pStream = default;
+ HRESULT hr = PInvoke.SHCreateStreamOnFileEx(path, (uint)(STGM.STGM_READ | STGM.STGM_FAILIFTHERE | STGM.STGM_SHARE_DENY_NONE), 0, false, null, pStream.GetAddressOf());
+ if (hr.Value < 0)
+ throw new InvalidComObjectException($"SHCreateItemFromParsingName failed to get IShellItem for preview handling with the error {hr.Value:X}.");
- var objectWithSite = previewHandlerObject as IObjectWithSite;
- if (objectWithSite == null)
- throw new COMException(cannotCast);
+ if (!pStream.IsNull)
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
- hr = objectWithSite.SetSite(comSite);
- if ((int)hr < 0)
- throw new COMException(cannotSetSite, (int)hr);
+ if (_initialized)
+ throw new InvalidOperationException("Preview handler is already initialized and cannot be initialized again.");
- visuals = previewHandlerObject as IPreviewHandlerVisuals;
- }
+ using ComPtr pInitializeWithStream = default;
+ _pPreviewHandler->QueryInterface(typeof(IInitializeWithStream).GUID, out *(void**)pInitializeWithStream.GetAddressOf());
+ if (pInitializeWithStream.IsNull)
+ throw new COMException($"{nameof(IInitializeWithStream)} could not queried from IPreviewHandler.");
- #region Initialization interfaces
-
- [ComImport, Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IInitializeWithStream
- {
- [PreserveSig]
- HRESULT Initialize(IStream psi, STGM grfMode);
- }
+ hr = pInitializeWithStream.Get()->Initialize(pStream.Get(), (uint)STGM.STGM_READ);
+ if (hr == HRESULT.E_NOTIMPL)
+ throw new NotImplementedException($"{nameof(IInitializeWithStream)}.Initialize() is not implemented.");
+ else if ((int)hr < 0)
+ throw new COMException($"{nameof(IInitializeWithStream)}.Initialize() failed.", (int)hr);
- [ComImport, Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IInitializeWithStreamNative
- {
- [PreserveSig]
- HRESULT Initialize(nint psi, STGM grfMode);
- }
-
- static readonly Guid IInitializeWithStreamIid = Guid.ParseExact("b824b49d-22ac-4161-ac8a-9916e8fa3f7f", "d");
-
- [ComImport, Guid("b7d14566-0509-4cce-a71f-0a554233bd9b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IInitializeWithFile
- {
- [PreserveSig]
- HRESULT Initialize([MarshalAs(UnmanagedType.LPWStr)] string pszFilePath, STGM grfMode);
- }
+ _initialized = true;
- static readonly Guid IInitializeWithFileIid = Guid.ParseExact("b7d14566-0509-4cce-a71f-0a554233bd9b", "d");
+ Debug.WriteLine($"Preview handler was successfully initialized with {nameof(IInitializeWithStream)}.");
- [ComImport, Guid("7f73be3f-fb79-493c-a6c7-7ee14e245841"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IInitializeWithItem
- {
- [PreserveSig]
- HRESULT Initialize(nint psi, STGM grfMode);
- }
+ return true;
+ }
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ }
- static readonly Guid IInitializeWithItemIid = Guid.ParseExact("7f73be3f-fb79-493c-a6c7-7ee14e245841", "d");
+ try
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
- #endregion
+ if (_initialized)
+ throw new InvalidOperationException("Preview handler is already initialized and cannot be initialized again.");
- ///
- /// Tries to initialize the preview handler with an IStream.
- ///
- /// This exception is thrown if IInitializeWithStream.Initialize fails for reason other than E_NOTIMPL.
- /// Thrown if mode is neither Read nor ReadWrite.
- /// The IStream interface used to initialize the preview handler.
- /// The storage mode, must be Read or ReadWrite.
- /// If the handler supports initialization with IStream, true; otherwise, false.
- public bool InitWithStream(IStream stream, STGM mode)
- {
- if (mode != STGM.STGM_READ && mode != STGM.STGM_READWRITE)
- throw new ArgumentOutOfRangeException("mode", mode, "The argument mode must be Read or ReadWrite.");
+ using ComPtr pInitializeWithFile = default;
+ _pPreviewHandler->QueryInterface(typeof(IInitializeWithFile).GUID, out *(void**)pInitializeWithFile.GetAddressOf());
+ if (pInitializeWithFile.IsNull)
+ throw new COMException($"{nameof(IInitializeWithFile)} could not queried from IPreviewHandler.");
- var iws = previewHandler as IInitializeWithStream;
- if (iws == null)
- return false;
+ HRESULT hr = pInitializeWithFile.Get()->Initialize(path, (uint)STGM.STGM_READ);
+ if (hr == HRESULT.E_NOTIMPL)
+ throw new NotImplementedException($"{nameof(IInitializeWithFile)}.Initialize() is not implemented.");
+ else if ((int)hr < 0)
+ throw new COMException($"{nameof(IInitializeWithFile)}.Initialize() failed.", (int)hr);
- var hr = iws.Initialize(stream, mode);
- if (hr == HRESULT.E_NOTIMPL)
- return false;
- if ((int)hr < 0)
- throw new COMException("IInitializeWithStream.Initialize failed.", (int)hr);
+ _initialized = true;
- init = true;
+ Debug.WriteLine($"Preview handler was successfully initialized with {nameof(IInitializeWithFile)}.");
- return true;
- }
+ return true;
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ }
- ///
- /// Same as InitWithStream(IStream, STGM).
- ///
- /// See InitWithStream(IStream, STGM).
- /// See InitWithStream(IStream, STGM).
- /// The native pointer to the IStream interface.
- /// The storage mode.
- /// True or false, see InitWithStream(IStream, STGM).
- public bool InitWithStream(nint pStream, STGM mode)
- {
- EnsureNotDisposed();
- EnsureNotInitialized();
+ try
+ {
+ using ComPtr pShellItem = default;
+ HRESULT hr = PInvoke.SHCreateItemFromParsingName(path, null, typeof(IShellItem).GUID, out *(void**)&pShellItem);
+ if (hr.Value < 0)
+ throw new InvalidComObjectException($"SHCreateItemFromParsingName failed to get IShellItem for preview handling with the error {hr.Value:X}.");
- if (mode != STGM.STGM_READ && mode != STGM.STGM_READWRITE)
- throw new ArgumentOutOfRangeException("mode", mode, "The argument mode must be Read or ReadWrite.");
+ if (!pShellItem.IsNull)
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
- var iws = previewHandler as IInitializeWithStreamNative;
- if (iws == null)
- return false;
+ if (_initialized)
+ throw new InvalidOperationException("Preview handler is already initialized and cannot be initialized again.");
- var hr = iws.Initialize(pStream, mode);
- if (hr == HRESULT.E_NOTIMPL)
- return false;
- if ((int)hr < 0)
- throw new COMException("IInitializeWithStream.Initialize failed.", (int)hr);
+ using ComPtr pInitializeWithItem = default;
+ _pPreviewHandler->QueryInterface(typeof(IInitializeWithItem).GUID, out *(void**)pInitializeWithItem.GetAddressOf());
+ if (pInitializeWithItem.IsNull)
+ throw new COMException($"{nameof(IInitializeWithItem)} could not queried from IPreviewHandler.");
- init = true;
+ hr = pInitializeWithItem.Get()->Initialize(pShellItem.Get(), (uint)STGM.STGM_READ);
+ if (hr == HRESULT.E_NOTIMPL)
+ throw new NotImplementedException($"{nameof(IInitializeWithItem)}.Initialize() is not implemented.");
+ else if ((int)hr < 0)
+ throw new COMException($"{nameof(IInitializeWithItem)}.Initialize() failed.", (int)hr);
- return true;
- }
+ _initialized = true;
- ///
- /// Same as InitWithStream(IStream, STGM).
- ///
- /// See InitWithStream(IStream, STGM).
- /// See InitWithStream(IStream, STGM).
- /// The native pointer to the IShellItem interface.
- /// The storage mode.
- /// True or false, see InitWithStream(IStream, STGM).
- public bool InitWithItem(nint psi, STGM mode)
- {
- EnsureNotDisposed();
- EnsureNotInitialized();
-
- if (mode != STGM.STGM_READ && mode != STGM.STGM_READWRITE)
- throw new ArgumentOutOfRangeException("mode", mode, "The argument mode must be Read or ReadWrite.");
+ Debug.WriteLine($"Preview handler was successfully initialized with {nameof(IInitializeWithItem)}.");
- var iwi = previewHandler as IInitializeWithItem;
- if (iwi == null)
- return false;
+ return true;
+ }
- var hr = iwi.Initialize(psi, mode);
- if (hr == HRESULT.E_NOTIMPL)
- return false;
- if ((int)hr < 0)
- throw new COMException("IInitializeWithItem.Initialize failed.", (int)hr);
+ if (exceptions.Count is 0)
+ throw new NotSupportedException("Preview handler could not be initialized at all.");
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ }
- init = true;
+ Debug.WriteLine($"Preview handler could not be initialized at all.");
- return true;
+ throw new AggregateException(exceptions);
}
///
- /// Same as InitWithStream(IStream, STGM).
+ /// Loads the preview data and renders the preview.
///
- /// See InitWithStream(IStream, STGM).
- /// See InitWithStream(IStream, STGM).
- /// The path to the file.
- /// The storage mode.
- /// True or false, see InitWithStream(IStream, STGM).
- public bool InitWithFile(string path, STGM mode)
+ public void DoPreview()
{
- EnsureNotDisposed();
- EnsureNotInitialized();
+ ObjectDisposedException.ThrowIf(_disposed, this);
- if (mode != STGM.STGM_READ && mode != STGM.STGM_READWRITE)
- throw new ArgumentOutOfRangeException("mode", mode, "The argument mode must be Read or ReadWrite.");
+ if (!_initialized)
+ // throw new InvalidOperationException("Object must be initialized before calling this method.");
+ return;
- var iwf = previewHandler as IInitializeWithFile;
- if (iwf == null)
- return false;
+ if (_shown)
+ throw new InvalidOperationException("The preview handler must not be shown to call this method.");
- var hr = iwf.Initialize(path, mode);
- if (hr == HRESULT.E_NOTIMPL)
- return false;
- if ((int)hr < 0)
- throw new COMException("IInitializeWithFile.Initialize failed.", (int)hr);
+ bool res = ResetWindow();
- init = true;
+ Debug.WriteLine($"Window of the preview handler" + (res ? "was successfully reset." : "failed to be reset."));
- return true;
+ _pPreviewHandler->DoPreview();
+
+ _shown = true;
+
+ Debug.WriteLine($"IPreviewHandler.DoPreview was successfully done.");
}
///
- /// Tries each way to initialize the object with a file.
+ /// Unloads the preview handler and disposes this instance.
///
- /// The file name.
- /// If initialization was successful, true; otherwise, an exception is thrown.
- public bool InitWithFileWithEveryWay(string path)
+ public void UnloadPreview()
{
- var exceptions = new List();
- var pobj = nint.Zero;
-
- // Why should we try IStream first?
- // Because that gives us the best security.
- // If we initialize with string or IShellItem,
- // we have no control over how the preview handler
- // opens the file, which might decide to open the
- // file for read/write exclusively.
- try
- {
- pobj = ItemStreamHelper.IStreamFromPath(path);
- if (pobj != nint.Zero
- && InitWithStream(pobj, STGM.STGM_READ))
- return true;
- }
- catch (Exception ex)
- {
- exceptions.Add(ex);
- }
- finally
- {
- if (pobj != nint.Zero)
- ItemStreamHelper.ReleaseObject(pobj);
-
- pobj = nint.Zero;
- }
-
- // Next try file because that could save us some P/Invokes.
- try
- {
- if (InitWithFile(path, STGM.STGM_READ))
- return true;
- }
- catch (Exception ex)
- {
- exceptions.Add(ex);
- }
-
- try
- {
- pobj = ItemStreamHelper.IShellItemFromPath(path);
- if (pobj != nint.Zero
- && InitWithItem(pobj, STGM.STGM_READ))
- return true;
-
- if (exceptions.Count == 0)
- throw new NotSupportedException("The object cannot be initialized at all.");
- }
- catch (Exception ex)
- {
- exceptions.Add(ex);
- }
- finally
- {
- if (pobj != nint.Zero)
- ItemStreamHelper.ReleaseObject(pobj);
-
- pobj = nint.Zero;
- }
-
- throw new AggregateException(exceptions);
+ Dispose(true);
}
///
@@ -445,15 +263,14 @@ public bool InitWithFileWithEveryWay(string path)
///
public bool ResetWindow()
{
- EnsureNotDisposed();
-
- //EnsureInitialized();
+ ObjectDisposedException.ThrowIf(_disposed, this);
- if (!init)
+ if (!_initialized)
+ // throw new InvalidOperationException("Object must be initialized before calling this method.");
return false;
- var hr = previewHandler.SetWindow(hwnd, new());
- return (int)hr >= 0;
+ HRESULT hr = _pPreviewHandler->SetWindow(_hWnd, new RECT());
+ return hr.Value >= 0;
}
///
@@ -461,14 +278,15 @@ public bool ResetWindow()
///
public bool ResetBounds(RECT previewerBounds)
{
- EnsureNotDisposed();
+ ObjectDisposedException.ThrowIf(_disposed, this);
- //EnsureInitialized();
+ //if (!_initialized)
+ // throw new InvalidOperationException("Object must be initialized before calling this method.");
- if (!init)
+ if (!_initialized)
return false;
- var hr = previewHandler.SetRect(previewerBounds);
+ HRESULT hr = _pPreviewHandler->SetRect(previewerBounds);
return (int)hr >= 0;
}
@@ -479,8 +297,8 @@ public bool ResetBounds(RECT previewerBounds)
/// Whether the call succeeds.
public bool SetBackground(Color color)
{
- var hr = visuals?.SetBackgroundColor(ColorRefFromColor(color));
- return hr.HasValue && (int)hr.Value >= 0;
+ HRESULT hr = _previewHandlerVisuals->SetBackgroundColor(new(ConvertColorToColorRef(color)));
+ return hr.Value >= 0;
}
///
@@ -490,8 +308,8 @@ public bool SetBackground(Color color)
/// Whether the call succeeds.
public bool SetForeground(Color color)
{
- var hr = visuals?.SetTextColor(ColorRefFromColor(color));
- return hr.HasValue && (int)hr.Value >= 0;
+ HRESULT hr = _previewHandlerVisuals->SetTextColor(new(ConvertColorToColorRef(color)));
+ return hr.Value >= 0;
}
///
@@ -501,29 +319,8 @@ public bool SetForeground(Color color)
/// Whether the call succeeds.
public bool SetFont(ref LOGFONTW font)
{
- var hr = visuals?.SetFont(ref font);
- return hr.HasValue && (int)hr.Value >= 0;
- }
-
- ///
- /// Shows the preview if the object has been successfully initialized.
- ///
- public void DoPreview()
- {
- EnsureNotDisposed();
-
- //EnsureInitialized();
-
- if (!init)
- return;
-
- EnsureNotShown();
-
- ResetWindow();
-
- previewHandler.DoPreview();
-
- shown = true;
+ HRESULT hr = _previewHandlerVisuals->SetFont(font);
+ return hr.Value >= 0;
}
///
@@ -531,15 +328,16 @@ public void DoPreview()
///
public void Focus()
{
- EnsureNotDisposed();
-
- //EnsureInitialized();
+ ObjectDisposedException.ThrowIf(_disposed, this);
- if (!init)
+ if (!_initialized)
+ // throw new InvalidOperationException("Object must be initialized before calling this method.");
return;
- EnsureShown();
- previewHandler.SetFocus();
+ if (!_shown)
+ throw new InvalidOperationException("The preview handler must be shown to call this method.");
+
+ _pPreviewHandler->SetFocus();
}
///
@@ -548,99 +346,107 @@ public void Focus()
/// The focused window.
public nint QueryFocus()
{
- EnsureNotDisposed();
-
- //EnsureInitialized();
+ ObjectDisposedException.ThrowIf(_disposed, this);
- if (!init)
+ if (!_initialized)
+ // throw new InvalidOperationException("Object must be initialized before calling this method.");
return nint.Zero;
- EnsureShown();
-
- nint result;
+ if (!_shown)
+ throw new InvalidOperationException("The preview handler must be shown to call this method.");
- var hr = previewHandler.QueryFocus(out result);
- if ((int)hr < 0)
+ HRESULT hr = _pPreviewHandler->QueryFocus(out HWND hWnd);
+ if (hr.Value < 0)
return nint.Zero;
- return result;
- }
-
- ///
- /// Unloads the preview and disposes the object. This method is idempotent.
- ///
- public void UnloadPreview()
- {
- Dispose(true);
- }
-
- void EnsureNotDisposed()
- {
- if (disposed)
- throw new ObjectDisposedException("PreviewHandler");
+ return hWnd.Value;
}
- void EnsureInitialized()
+ private uint ConvertColorToColorRef(Color color)
{
- if (!init)
- throw new InvalidOperationException("Object must be initialized before calling this method.");
+ return (((uint)color.B) << 16) | (((uint)color.G) << 8) | ((uint)color.R);
}
- void EnsureNotInitialized()
- {
- if (init)
- throw new InvalidOperationException("Object is already initialized and cannot be initialized again.");
- }
+ // Disposers
- void EnsureShown()
+ ~PreviewHandler()
{
- if (!shown)
- throw new InvalidOperationException("The preview handler must be shown to call this method.");
+ Dispose(false);
}
- void EnsureNotShown()
+ void IDisposable.Dispose()
{
- if (shown)
- throw new InvalidOperationException("The preview handler must not be shown to call this method.");
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
- // Dispose
-
void Dispose(bool disposing)
{
- if (disposed)
+ if (_disposed)
return;
- disposed = true;
- init = false;
+
+ _disposed = true;
+ _initialized = false;
+
if (disposing)
{
- previewHandler.Unload();
- comSite.Dispose();
- Marshal.ReleaseComObject(previewHandler);
+ _pPreviewHandler->Unload();
+ _pPreviewHandler->Release();
}
else
{
// We're in the finalizer.
// Field previewHandler might have been finalized at this point.
// Get a new RCW.
- var phObject = Marshal.GetUniqueObjectForIUnknown(pPreviewHandler);
- var ph = phObject as IPreviewHandler;
- if (ph != null)
- ph.Unload();
- Marshal.ReleaseComObject(phObject);
+
+ //var phObject = Marshal.GetUniqueObjectForIUnknown(_pPreviewHandler);
+ //var ph = phObject as IPreviewHandler;
+ //if (ph != null)
+ // ph.Unload();
+
+ //Marshal.ReleaseComObject(phObject);
}
- Marshal.Release(pPreviewHandler);
- }
- ~PreviewHandler()
- {
- Dispose(false);
+ _pPreviewHandler->Release();
+
+ Debug.WriteLine($"Preview handler was successfully disposed.");
}
- void IDisposable.Dispose()
+ // Private class
+
+ private unsafe class CPreviewHandlerFrame : IPreviewHandlerFrame.Interface
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ private bool _disposed = false;
+ private readonly HWND _hWnd = default;
+
+ public CPreviewHandlerFrame(HWND frame)
+ {
+ _hWnd = frame;
+ }
+
+ public void Dispose()
+ {
+ _disposed = true;
+ }
+
+ public HRESULT GetWindowContext(PREVIEWHANDLERFRAMEINFO* pInfo)
+ {
+ pInfo->haccel = HACCEL.Null;
+ pInfo->cAccelEntries = 0u;
+
+ if (_disposed)
+ return HRESULT.E_FAIL; // Disposed already
+
+ return HRESULT.S_OK;
+ }
+
+ public HRESULT TranslateAccelerator(MSG* pMsg)
+ {
+ if (_disposed)
+ return HRESULT.E_FAIL; // Disposed already
+
+ return HRESULT.S_FALSE;
+ }
}
}
}
diff --git a/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs b/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs
index 3062e47e9669..46cc16c22a8e 100644
--- a/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs
+++ b/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs
@@ -13,14 +13,11 @@
using Windows.Win32.Graphics.DirectComposition;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.Graphics.Dxgi;
+using Windows.Win32.System.Com;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.WindowsAndMessaging;
using WinRT;
-// Description: Feature is for evaluation purposes only and is subject to change or removal in future updates.
-// Justification: We have to use ContentExternalOutputLink for shell previews.
-#pragma warning disable CS8305
-
#pragma warning disable CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
namespace Files.App.ViewModels.Previews
@@ -103,8 +100,8 @@ private unsafe LRESULT WndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam
try
{
- _currentPreviewHandler = new PreviewHandler(clsid.Value, hwnd.Value);
- _currentPreviewHandler.InitWithFileWithEveryWay(Item.ItemPath);
+ _currentPreviewHandler = new PreviewHandler(clsid.Value, hwnd);
+ _currentPreviewHandler.Initialize(Item.ItemPath);
_currentPreviewHandler.DoPreview();
}
catch
@@ -204,14 +201,16 @@ private unsafe bool ChildWindowToXaml(nint parent, UIElement presenter)
if (pD3D11Device is null)
return false;
- IDXGIDevice dxgiDevice = (IDXGIDevice)d3d11Device;
- if (PInvoke.DCompositionCreateDevice(dxgiDevice, typeof(IDCompositionDevice).GUID, out var compDevicePtr).Failed)
+ IDXGIDevice* pDXGIDevice = (IDXGIDevice*)pD3D11Device;
+ if (PInvoke.DCompositionCreateDevice(pDXGIDevice, typeof(IDCompositionDevice).GUID, out var compositionDevicePtr).Failed)
return false;
- IDCompositionDevice compDevice = (IDCompositionDevice)compDevicePtr;
+ var pDCompositionDevice = (IDCompositionDevice*)compositionDevicePtr;
+ IDCompositionVisual* pChildVisual = default;
+ IUnknown* pControlSurface = default;
pDCompositionDevice->CreateVisual(&pChildVisual);
- pDCompositionDevice->CreateSurfaceFromHwnd(_hWnd, &pControlSurface);
+ pDCompositionDevice->CreateSurfaceFromHwnd(new(_hWnd), &pControlSurface);
pChildVisual->SetContent(pControlSurface);
if (pChildVisual is null || pControlSurface is null)
return false;
@@ -223,7 +222,7 @@ private unsafe bool ChildWindowToXaml(nint parent, UIElement presenter)
target.SetRoot(pChildVisual);
_contentExternalOutputLink.PlacementVisual.Size = new(0, 0);
- _contentExternalOutputLink.PlacementVisual.Scale = new(1/(float)presenter.XamlRoot.RasterizationScale);
+ _contentExternalOutputLink.PlacementVisual.Scale = new(1 / (float)presenter.XamlRoot.RasterizationScale);
ElementCompositionPreview.SetElementChildVisual(presenter, _contentExternalOutputLink.PlacementVisual);
pDCompositionDevice->Commit();
@@ -286,8 +285,16 @@ public void UnloadPreview()
if (_hWnd != HWND.Null)
PInvoke.DestroyWindow(_hWnd);
- //outputLink?.Dispose();
- _contentExternalOutputLink = null;
+ try
+ {
+ var target = _contentExternalOutputLink.As();
+ Marshal.ReleaseComObject(target);
+ _contentExternalOutputLink?.Dispose();
+ }
+ finally
+ {
+ _contentExternalOutputLink = null;
+ }
PInvoke.UnregisterClass(_windowClass.lpszClassName, PInvoke.GetModuleHandle(default(PWSTR)));
}