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))); }