diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt index 9d7c337d7c4d..8746ea6d2b3a 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -133,3 +133,17 @@ IFileOperation IShellItem2 PSGetPropertyKeyFromName ShellExecuteEx +E_FAIL +S_OK +S_FALSE +MSG +E_NOTIMPL +LOGFONTW +AssocCreate +IQueryAssociations +UnregisterClass +SetWindowLong +GetModuleHandle +RegisterClassEx +CREATESTRUCTW +AssocQueryString diff --git a/src/Files.App/UserControls/FilePreviews/ShellPreview.xaml.cs b/src/Files.App/UserControls/FilePreviews/ShellPreview.xaml.cs index d0f09e7aed50..935227a1cc07 100644 --- a/src/Files.App/UserControls/FilePreviews/ShellPreview.xaml.cs +++ b/src/Files.App/UserControls/FilePreviews/ShellPreview.xaml.cs @@ -1,8 +1,11 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + using Files.App.ViewModels.Previews; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Vanara.PInvoke; using Windows.Foundation; +using Windows.Win32.Foundation; namespace Files.App.UserControls.FilePreviews { @@ -13,13 +16,15 @@ public sealed partial class ShellPreview : UserControl public ShellPreview(ShellPreviewViewModel model) { ViewModel = model; - this.InitializeComponent(); + + InitializeComponent(); } private void PreviewHost_Loaded(object sender, RoutedEventArgs e) { ViewModel.LoadPreview(contentPresenter); ViewModel.SizeChanged(GetPreviewSize()); + if (XamlRoot.Content is FrameworkElement element) { element.SizeChanged += PreviewHost_SizeChanged; @@ -38,11 +43,13 @@ private RECT GetPreviewSize() var physicalSize = contentPresenter.RenderSize; var physicalPos = source.TransformPoint(new Point(0, 0)); var scale = XamlRoot.RasterizationScale; - var result = new RECT(); - result.Left = (int)(physicalPos.X * scale + 0.5); - result.Top = (int)(physicalPos.Y * scale + 0.5); - result.Width = (int)(physicalSize.Width * scale + 0.5); - result.Height = (int)(physicalSize.Height * scale + 0.5); + + var result = RECT.FromXYWH( + (int)(physicalPos.X * scale + 0.5), + (int)(physicalPos.Y * scale + 0.5), + (int)(physicalSize.Width * scale + 0.5), + (int)(physicalSize.Height * scale + 0.5)); + return result; } @@ -53,6 +60,7 @@ private void PreviewHost_Unloaded(object sender, RoutedEventArgs e) element.SizeChanged -= PreviewHost_SizeChanged; element.PointerEntered -= PreviewHost_PointerEntered; } + ViewModel.UnloadPreview(); } diff --git a/src/Files.App/Utils/Shell/PreviewHandler.cs b/src/Files.App/Utils/Shell/PreviewHandler.cs index 6cb5f4001798..06160b4c2a21 100644 --- a/src/Files.App/Utils/Shell/PreviewHandler.cs +++ b/src/Files.App/Utils/Shell/PreviewHandler.cs @@ -1,9 +1,13 @@ -using System; -using System.Collections.Generic; +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using Vanara.PInvoke; using Windows.UI; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.System.Com; +using Windows.Win32.UI.WindowsAndMessaging; namespace Files.App.Utils.Shell { @@ -12,12 +16,16 @@ namespace Files.App.Utils.Shell /// public sealed class PreviewHandler : IDisposable { + // Fields + + private static readonly Guid IPreviewHandlerIid = Guid.ParseExact("8895b1c6-b41f-4c1c-a562-0d564250836f", "d"); + #region IPreviewHandlerFrame support [StructLayout(LayoutKind.Sequential)] public struct PreviewHandlerFrameInfo { - public IntPtr AcceleratorTableHandle; + public nint AcceleratorTableHandle; public uint AcceleratorEntryCount; } @@ -49,7 +57,7 @@ public void Dispose() public HRESULT GetWindowContext(out PreviewHandlerFrameInfo pinfo) { - pinfo.AcceleratorTableHandle = IntPtr.Zero; + pinfo.AcceleratorTableHandle = nint.Zero; pinfo.AcceleratorEntryCount = 0; if (disposed) return HRESULT.E_FAIL; @@ -72,7 +80,7 @@ public HRESULT TranslateAccelerator(ref MSG pmsg) interface IPreviewHandler { [PreserveSig] - HRESULT SetWindow(IntPtr hwnd, ref RECT prc); + HRESULT SetWindow(nint hwnd, ref RECT prc); [PreserveSig] HRESULT SetRect(ref RECT prc); [PreserveSig] @@ -82,7 +90,7 @@ interface IPreviewHandler [PreserveSig] HRESULT SetFocus(); [PreserveSig] - HRESULT QueryFocus(out IntPtr phwnd); + HRESULT QueryFocus(out nint phwnd); // TranslateAccelerator is not used here. } @@ -92,7 +100,7 @@ interface IPreviewHandlerVisuals [PreserveSig] HRESULT SetBackgroundColor(uint color); [PreserveSig] - HRESULT SetFont(ref LOGFONT plf); + HRESULT SetFont(ref LOGFONTW plf); [PreserveSig] HRESULT SetTextColor(uint color); } @@ -119,7 +127,9 @@ interface IObjectWithSite nint hwnd; IPreviewHandler previewHandler; IPreviewHandlerVisuals visuals; - IntPtr pPreviewHandler; + nint pPreviewHandler; + + // Initializer public PreviewHandler(Guid clsid, nint frame) { @@ -128,6 +138,7 @@ public PreviewHandler(Guid clsid, nint frame) shown = false; comSite = new PreviewHandlerFrame(frame); hwnd = frame; + try { SetupHandler(clsid); @@ -138,52 +149,64 @@ public PreviewHandler(Guid clsid, nint frame) if (previewHandler != null) Marshal.ReleaseComObject(previewHandler); previewHandler = null; - if (pPreviewHandler != IntPtr.Zero) + if (pPreviewHandler != nint.Zero) Marshal.Release(pPreviewHandler); - pPreviewHandler = IntPtr.Zero; + pPreviewHandler = nint.Zero; comSite.Dispose(); comSite = null; throw; } } - static readonly Guid IPreviewHandlerIid = Guid.ParseExact("8895b1c6-b41f-4c1c-a562-0d564250836f", "d"); + // Methods - void SetupHandler(Guid clsid) + unsafe void SetupHandler(Guid clsid) { - IntPtr pph; + 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: manully calling CoCreateInstance is necessary. + + // 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 = Win32PInvoke.CoCreateInstance(ref clsid, IntPtr.Zero, Win32PInvoke.ClassContext.LocalServer, ref iid, out pph); + 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, IntPtr.Zero, ClassContext.LocalServer, ref iid, out pph); + // hr = CoCreateInstance(ref clsid, nint.Zero, ClassContext.LocalServer, ref iid, out pph); if ((int)hr < 0) throw new COMException(cannotCreate, (int)hr); - pPreviewHandler = pph; - var previewHandlerObject = Marshal.GetUniqueObjectForIUnknown(pph); - previewHandler = previewHandlerObject as IPreviewHandler; + + pPreviewHandler = new(previewHandlerPtr); + previewHandler = (IPreviewHandler)previewHandlerObject; if (previewHandler == null) { Marshal.ReleaseComObject(previewHandlerObject); throw new COMException(cannotCreate); } + var objectWithSite = previewHandlerObject as IObjectWithSite; if (objectWithSite == null) throw new COMException(cannotCast); + hr = objectWithSite.SetSite(comSite); if ((int)hr < 0) throw new COMException(cannotSetSite, (int)hr); + visuals = previewHandlerObject as IPreviewHandlerVisuals; } @@ -200,7 +223,7 @@ interface IInitializeWithStream interface IInitializeWithStreamNative { [PreserveSig] - HRESULT Initialize(IntPtr psi, STGM grfMode); + HRESULT Initialize(nint psi, STGM grfMode); } static readonly Guid IInitializeWithStreamIid = Guid.ParseExact("b824b49d-22ac-4161-ac8a-9916e8fa3f7f", "d"); @@ -218,7 +241,7 @@ interface IInitializeWithFile interface IInitializeWithItem { [PreserveSig] - HRESULT Initialize(IntPtr psi, STGM grfMode); + HRESULT Initialize(nint psi, STGM grfMode); } static readonly Guid IInitializeWithItemIid = Guid.ParseExact("7f73be3f-fb79-493c-a6c7-7ee14e245841", "d"); @@ -237,15 +260,19 @@ 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."); + var iws = previewHandler as IInitializeWithStream; if (iws == null) return false; + 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); + init = true; + return true; } @@ -257,21 +284,26 @@ public bool InitWithStream(IStream stream, STGM mode) /// The native pointer to the IStream interface. /// The storage mode. /// True or false, see InitWithStream(IStream, STGM). - public bool InitWithStream(IntPtr pStream, STGM mode) + public bool InitWithStream(nint pStream, 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."); + var iws = previewHandler as IInitializeWithStreamNative; if (iws == null) return false; + 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); + init = true; + return true; } @@ -283,21 +315,26 @@ public bool InitWithStream(IntPtr pStream, STGM mode) /// The native pointer to the IShellItem interface. /// The storage mode. /// True or false, see InitWithStream(IStream, STGM). - public bool InitWithItem(IntPtr psi, STGM mode) + 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."); + var iwi = previewHandler as IInitializeWithItem; if (iwi == null) return false; + 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); + init = true; + return true; } @@ -313,17 +350,22 @@ public bool InitWithFile(string path, 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."); + var iwf = previewHandler as IInitializeWithFile; if (iwf == null) return false; + 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); + init = true; + return true; } @@ -335,7 +377,8 @@ public bool InitWithFile(string path, STGM mode) public bool InitWithFileWithEveryWay(string path) { var exceptions = new List(); - var pobj = IntPtr.Zero; + var pobj = nint.Zero; + // Why should we try IStream first? // Because that gives us the best security. // If we initialize with string or IShellItem, @@ -345,7 +388,7 @@ public bool InitWithFileWithEveryWay(string path) try { pobj = ItemStreamHelper.IStreamFromPath(path); - if (pobj != IntPtr.Zero + if (pobj != nint.Zero && InitWithStream(pobj, STGM.STGM_READ)) return true; } @@ -355,10 +398,12 @@ public bool InitWithFileWithEveryWay(string path) } finally { - if (pobj != IntPtr.Zero) + if (pobj != nint.Zero) ItemStreamHelper.ReleaseObject(pobj); - pobj = IntPtr.Zero; + + pobj = nint.Zero; } + // Next try file because that could save us some P/Invokes. try { @@ -369,12 +414,14 @@ public bool InitWithFileWithEveryWay(string path) { exceptions.Add(ex); } + try { pobj = ItemStreamHelper.IShellItemFromPath(path); - if (pobj != IntPtr.Zero + 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."); } @@ -384,10 +431,12 @@ public bool InitWithFileWithEveryWay(string path) } finally { - if (pobj != IntPtr.Zero) + if (pobj != nint.Zero) ItemStreamHelper.ReleaseObject(pobj); - pobj = IntPtr.Zero; + + pobj = nint.Zero; } + throw new AggregateException(exceptions); } @@ -397,9 +446,12 @@ public bool InitWithFileWithEveryWay(string path) public bool ResetWindow() { EnsureNotDisposed(); + //EnsureInitialized(); + if (!init) return false; + var hr = previewHandler.SetWindow(hwnd, new()); return (int)hr >= 0; } @@ -410,9 +462,12 @@ public bool ResetWindow() public bool ResetBounds(RECT previewerBounds) { EnsureNotDisposed(); + //EnsureInitialized(); + if (!init) return false; + var hr = previewHandler.SetRect(previewerBounds); return (int)hr >= 0; } @@ -444,7 +499,7 @@ public bool SetForeground(Color color) /// /// The LogFontW reference. /// Whether the call succeeds. - public bool SetFont(ref LOGFONT font) + public bool SetFont(ref LOGFONTW font) { var hr = visuals?.SetFont(ref font); return hr.HasValue && (int)hr.Value >= 0; @@ -456,12 +511,18 @@ public bool SetFont(ref LOGFONT font) public void DoPreview() { EnsureNotDisposed(); + //EnsureInitialized(); + if (!init) return; + EnsureNotShown(); + ResetWindow(); + previewHandler.DoPreview(); + shown = true; } @@ -471,9 +532,12 @@ public void DoPreview() public void Focus() { EnsureNotDisposed(); + //EnsureInitialized(); + if (!init) return; + EnsureShown(); previewHandler.SetFocus(); } @@ -482,17 +546,23 @@ public void Focus() /// Tells the preview handler to query focus. /// /// The focused window. - public IntPtr QueryFocus() + public nint QueryFocus() { EnsureNotDisposed(); + //EnsureInitialized(); + if (!init) - return IntPtr.Zero; + return nint.Zero; + EnsureShown(); - IntPtr result; + + nint result; + var hr = previewHandler.QueryFocus(out result); if ((int)hr < 0) - return IntPtr.Zero; + return nint.Zero; + return result; } @@ -534,7 +604,7 @@ void EnsureNotShown() throw new InvalidOperationException("The preview handler must not be shown to call this method."); } - #region IDisposable pattern + // Dispose void Dispose(bool disposing) { @@ -572,8 +642,5 @@ void IDisposable.Dispose() Dispose(true); GC.SuppressFinalize(this); } - - #endregion - } } diff --git a/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs b/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs index 1dd27cb8ad03..3062e47e9669 100644 --- a/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs @@ -6,18 +6,20 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Hosting; using System.Runtime.InteropServices; -using System.Text; -using Vanara.PInvoke; using Windows.Win32; -using Windows.Win32.System.Com; +using Windows.Win32.Foundation; using Windows.Win32.Graphics.Direct3D; using Windows.Win32.Graphics.Direct3D11; using Windows.Win32.Graphics.DirectComposition; using Windows.Win32.Graphics.Dwm; using Windows.Win32.Graphics.Dxgi; +using Windows.Win32.UI.Shell; +using Windows.Win32.UI.WindowsAndMessaging; using WinRT; -using static Vanara.PInvoke.ShlwApi; -using static Vanara.PInvoke.User32; + +// 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. @@ -25,75 +27,74 @@ namespace Files.App.ViewModels.Previews { public sealed class ShellPreviewViewModel : BasePreviewModel { - private const string IPreviewHandlerIid = "{8895b1c6-b41f-4c1c-a562-0d564250836f}"; - private static readonly Guid QueryAssociationsClsid = new Guid(0xa07034fd, 0x6caa, 0x4954, 0xac, 0x3f, 0x97, 0xa2, 0x72, 0x16, 0xf9, 0x8a); - private static readonly Guid IQueryAssociationsIid = Guid.ParseExact("c46ca590-3c3f-11d2-bee6-0000f805ca57", "d"); + // Fields + + PreviewHandler? _currentPreviewHandler; + ContentExternalOutputLink? _contentExternalOutputLink; + WNDCLASSEXW _windowClass; + WNDPROC _windProc = null!; + HWND _hWnd = HWND.Null; + bool _isOfficePreview = false; - PreviewHandler? currentHandler; - ContentExternalOutputLink? outputLink; - WindowClass? wCls; - HWND hwnd = HWND.NULL; - bool isOfficePreview = false; + // Initializer public ShellPreviewViewModel(ListedItem item) : base(item) { } + // Methods + public async override Task> LoadPreviewAndDetailsAsync() => []; - public static Guid? FindPreviewHandlerFor(string extension, nint hwnd) + public static unsafe Guid? FindPreviewHandlerFor(string extension, nint hwnd) { if (string.IsNullOrEmpty(extension)) return null; - var hr = AssocCreate(QueryAssociationsClsid, IQueryAssociationsIid, out var queryAssoc); - if (!hr.Succeeded) - return null; - try { - if (queryAssoc == null) - return null; - - queryAssoc.Init(ASSOCF.ASSOCF_INIT_DEFAULTTOSTAR, extension, nint.Zero, hwnd); - - var sb = new StringBuilder(128); - uint cch = 64; - - queryAssoc.GetString(ASSOCF.ASSOCF_NOTRUNCATE, ASSOCSTR.ASSOCSTR_SHELLEXTENSION, IPreviewHandlerIid, sb, ref cch); - - Debug.WriteLine($"Preview handler for {extension}: {sb}"); - return Guid.Parse(sb.ToString()); + fixed (char* pszOutput = new char[1024]) + { + PWSTR pwszOutput = new(pszOutput); + uint cchOutput = 512u; + + // Try to find registered preview handler associated with specified extension name + var res = PInvoke.AssocQueryString( + ASSOCF.ASSOCF_NOTRUNCATE, + ASSOCSTR.ASSOCSTR_SHELLEXTENSION, + extension, + "{8895b1c6-b41f-4c1c-a562-0d564250836f}", + pszOutput, + ref cchOutput); + + return Guid.Parse(pwszOutput.ToString()); + } } catch { return null; } - finally - { - Marshal.ReleaseComObject(queryAssoc); - } } public void SizeChanged(RECT size) { - if (hwnd != HWND.NULL) - SetWindowPos(hwnd, HWND.HWND_TOP, size.Left, size.Top, size.Width, size.Height, SetWindowPosFlags.SWP_NOACTIVATE); + if (_hWnd != HWND.Null) + PInvoke.SetWindowPos(_hWnd, (HWND)0, size.left, size.top, size.Width, size.Height, SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); - currentHandler?.ResetBounds(new(0, 0, size.Width, size.Height)); + _currentPreviewHandler?.ResetBounds(new(0, 0, size.Width, size.Height)); - if (outputLink is not null) - outputLink.PlacementVisual.Size = new(size.Width, size.Height); + if (_contentExternalOutputLink is not null) + _contentExternalOutputLink.PlacementVisual.Size = new(size.Width, size.Height); } - private nint WndProc(HWND hwnd, uint msg, nint wParam, nint lParam) + private unsafe LRESULT WndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { - if (msg == (uint)WindowMessage.WM_CREATE) + if (msg == 0x0001 /*WM_CREATE*/) { - var clsid = FindPreviewHandlerFor(Item.FileExtension, hwnd.DangerousGetHandle()); + var clsid = FindPreviewHandlerFor(Item.FileExtension, hwnd); - isOfficePreview = new Guid?[] + _isOfficePreview = new Guid?[] { Guid.Parse("84F66100-FF7C-4fb4-B0C0-02CD7FB668FE"), // preview handler for Word files Guid.Parse("65235197-874B-4A07-BDC5-E65EA825B718"), // preview handler for PowerPoint files @@ -102,43 +103,71 @@ private nint WndProc(HWND hwnd, uint msg, nint wParam, nint lParam) try { - currentHandler = new PreviewHandler(clsid.Value, hwnd.DangerousGetHandle()); - currentHandler.InitWithFileWithEveryWay(Item.ItemPath); - currentHandler.DoPreview(); + _currentPreviewHandler = new PreviewHandler(clsid.Value, hwnd.Value); + _currentPreviewHandler.InitWithFileWithEveryWay(Item.ItemPath); + _currentPreviewHandler.DoPreview(); } catch { UnloadPreview(); } } - else if (msg == (uint)WindowMessage.WM_DESTROY) + else if (msg == 0x0002 /*WM_DESTROY*/) { - if (currentHandler is not null) + if (_currentPreviewHandler is not null) { - currentHandler.UnloadPreview(); - currentHandler = null; + _currentPreviewHandler.UnloadPreview(); + _currentPreviewHandler = null; } } - return DefWindowProc(hwnd, msg, wParam, lParam); + return PInvoke.DefWindowProc(hwnd, msg, wParam, lParam); } - public void LoadPreview(UIElement presenter) + public unsafe void LoadPreview(UIElement presenter) { var parent = MainWindow.Instance.WindowHandle; + var hInst = PInvoke.GetModuleHandle(default(PWSTR)); + var szClassName = $"{GetType().Name}-{Guid.NewGuid()}"; + var szWindowName = $"Preview"; - HINSTANCE hInst = Kernel32.GetModuleHandle(); - - wCls = new WindowClass($"{GetType().Name}{Guid.NewGuid()}", hInst, WndProc); + fixed (char* pszClassName = szClassName) + { + _windProc = new(WndProc); + var pWindProc = Marshal.GetFunctionPointerForDelegate(_windProc); + var pfnWndProc = (delegate* unmanaged[Stdcall])pWindProc; - hwnd = CreateWindowEx( - WindowStylesEx.WS_EX_LAYERED | WindowStylesEx.WS_EX_COMPOSITED, - wCls.ClassName, - "Preview", - WindowStyles.WS_CHILD | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_VISIBLE, - 0, 0, 0, 0, - hWndParent: parent, - hInstance: hInst); + _windowClass = new WNDCLASSEXW() + { + cbSize = (uint)Marshal.SizeOf(typeof(WNDCLASSEXW)), + lpfnWndProc = pfnWndProc, + hInstance = hInst, + lpszClassName = pszClassName, + style = 0, + hIcon = default, + hIconSm = default, + hCursor = default, + hbrBackground = default, + lpszMenuName = null, + cbClsExtra = 0, + cbWndExtra = 0, + }; + + PInvoke.RegisterClassEx(_windowClass); + + fixed (char* pszWindowName = szWindowName) + { + _hWnd = PInvoke.CreateWindowEx( + WINDOW_EX_STYLE.WS_EX_LAYERED | WINDOW_EX_STYLE.WS_EX_COMPOSITED, + pszClassName, + pszWindowName, + WINDOW_STYLE.WS_CHILD | WINDOW_STYLE.WS_CLIPSIBLINGS | WINDOW_STYLE.WS_VISIBLE, + 0, 0, 0, 0, + new(parent), + HMENU.Null, + hInst); + } + } _ = ChildWindowToXaml(parent, presenter); } @@ -175,29 +204,27 @@ private unsafe bool ChildWindowToXaml(nint parent, UIElement presenter) if (pD3D11Device is null) return false; - IDXGIDevice* pDXGIDevice = (IDXGIDevice*)pD3D11Device; - if (PInvoke.DCompositionCreateDevice(pDXGIDevice, typeof(IDCompositionDevice).GUID, out var compositionDevicePtr).Failed) + IDXGIDevice dxgiDevice = (IDXGIDevice)d3d11Device; + if (PInvoke.DCompositionCreateDevice(dxgiDevice, typeof(IDCompositionDevice).GUID, out var compDevicePtr).Failed) return false; - var pDCompositionDevice = (IDCompositionDevice*)compositionDevicePtr; - IDCompositionVisual* pChildVisual = default; - IUnknown* pControlSurface = default; + IDCompositionDevice compDevice = (IDCompositionDevice)compDevicePtr; pDCompositionDevice->CreateVisual(&pChildVisual); - pDCompositionDevice->CreateSurfaceFromHwnd(new(hwnd.DangerousGetHandle()), &pControlSurface); + pDCompositionDevice->CreateSurfaceFromHwnd(_hWnd, &pControlSurface); pChildVisual->SetContent(pControlSurface); if (pChildVisual is null || pControlSurface is null) return false; var compositor = ElementCompositionPreview.GetElementVisual(presenter).Compositor; - outputLink = ContentExternalOutputLink.Create(compositor); + _contentExternalOutputLink = ContentExternalOutputLink.Create(compositor); - var target = outputLink.As(); + var target = _contentExternalOutputLink.As(); target.SetRoot(pChildVisual); - outputLink.PlacementVisual.Size = new(0, 0); - outputLink.PlacementVisual.Scale = new(1/(float)presenter.XamlRoot.RasterizationScale); - ElementCompositionPreview.SetElementChildVisual(presenter, outputLink.PlacementVisual); + _contentExternalOutputLink.PlacementVisual.Size = new(0, 0); + _contentExternalOutputLink.PlacementVisual.Scale = new(1/(float)presenter.XamlRoot.RasterizationScale); + ElementCompositionPreview.SetElementChildVisual(presenter, _contentExternalOutputLink.PlacementVisual); pDCompositionDevice->Commit(); @@ -213,25 +240,13 @@ private unsafe bool ChildWindowToXaml(nint parent, UIElement presenter) return PInvoke.DwmSetWindowAttribute( - new((nint)hwnd), + new((nint)_hWnd), DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &dwAttrib, (uint)Marshal.SizeOf(dwAttrib)) .Succeeded; } - public void UnloadPreview() - { - if (hwnd != HWND.NULL) - DestroyWindow(hwnd); - - //outputLink?.Dispose(); - outputLink = null; - - if (wCls is not null) - UnregisterClass(wCls.ClassName, Kernel32.GetModuleHandle()); - } - public unsafe void PointerEntered(bool onPreview) { if (onPreview) @@ -239,30 +254,43 @@ public unsafe void PointerEntered(bool onPreview) var dwAttrib = Convert.ToUInt32(false); PInvoke.DwmSetWindowAttribute( - new((nint)hwnd), + new((nint)_hWnd), DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &dwAttrib, (uint)Marshal.SizeOf(dwAttrib)); - if (isOfficePreview) - Win32Helper.SetWindowLong(hwnd, WindowLongFlags.GWL_EXSTYLE, 0); + if (_isOfficePreview) + PInvoke.SetWindowLong(_hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, 0); } else { - Win32Helper.SetWindowLong( - hwnd, - WindowLongFlags.GWL_EXSTYLE, - (nint)(WindowStylesEx.WS_EX_LAYERED | WindowStylesEx.WS_EX_COMPOSITED)); + PInvoke.SetWindowLong( + _hWnd, + WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, + (int)(WINDOW_EX_STYLE.WS_EX_LAYERED | WINDOW_EX_STYLE.WS_EX_COMPOSITED)); var dwAttrib = Convert.ToUInt32(true); PInvoke.DwmSetWindowAttribute( - new((nint)hwnd), + new((nint)_hWnd), DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &dwAttrib, (uint)Marshal.SizeOf(dwAttrib)); } } + + // Disposer + + public void UnloadPreview() + { + if (_hWnd != HWND.Null) + PInvoke.DestroyWindow(_hWnd); + + //outputLink?.Dispose(); + _contentExternalOutputLink = null; + + PInvoke.UnregisterClass(_windowClass.lpszClassName, PInvoke.GetModuleHandle(default(PWSTR))); + } } }