diff --git a/src/Files.App/NativeMethods.txt b/src/Files.App/NativeMethods.txt index 38676b61de48d..ed83e399814ff 100644 --- a/src/Files.App/NativeMethods.txt +++ b/src/Files.App/NativeMethods.txt @@ -94,3 +94,17 @@ DesktopWallpaper SHCreateShellItemArrayFromIDLists ILCreateFromPath CLSIDFromString +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 d0f09e7aed50c..935227a1cc076 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 6cb5f40017981..e449e7374df6a 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 { @@ -17,7 +21,7 @@ public sealed class PreviewHandler : IDisposable [StructLayout(LayoutKind.Sequential)] public struct PreviewHandlerFrameInfo { - public IntPtr AcceleratorTableHandle; + public nint AcceleratorTableHandle; public uint AcceleratorEntryCount; } @@ -49,7 +53,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 +76,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 +86,7 @@ interface IPreviewHandler [PreserveSig] HRESULT SetFocus(); [PreserveSig] - HRESULT QueryFocus(out IntPtr phwnd); + HRESULT QueryFocus(out nint phwnd); // TranslateAccelerator is not used here. } @@ -92,7 +96,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 +123,7 @@ interface IObjectWithSite nint hwnd; IPreviewHandler previewHandler; IPreviewHandlerVisuals visuals; - IntPtr pPreviewHandler; + nint pPreviewHandler; public PreviewHandler(Guid clsid, nint frame) { @@ -138,9 +142,9 @@ 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; @@ -149,41 +153,51 @@ public PreviewHandler(Guid clsid, nint frame) static readonly Guid IPreviewHandlerIid = Guid.ParseExact("8895b1c6-b41f-4c1c-a562-0d564250836f", "d"); - 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 object previewHandlerObject); + // 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(&previewHandlerObject); + 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 +214,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 +232,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 +251,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 +275,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 +306,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 +341,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 +368,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 +379,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 +389,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 +405,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 +422,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 +437,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 +453,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 +490,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 +502,18 @@ public bool SetFont(ref LOGFONT font) public void DoPreview() { EnsureNotDisposed(); + //EnsureInitialized(); + if (!init) return; + EnsureNotShown(); + ResetWindow(); + previewHandler.DoPreview(); + shown = true; } @@ -471,9 +523,12 @@ public void DoPreview() public void Focus() { EnsureNotDisposed(); + //EnsureInitialized(); + if (!init) return; + EnsureShown(); previewHandler.SetFocus(); } @@ -482,17 +537,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 +595,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 +633,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 4a932c1de4dfa..cf006728fb975 100644 --- a/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Previews/ShellPreviewViewModel.cs @@ -1,93 +1,116 @@ -using Files.App.ViewModels.Properties; +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Files.App.ViewModels.Properties; using Microsoft.UI.Content; 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.Foundation; using Windows.Win32.Graphics.Direct3D; using Windows.Win32.Graphics.Direct3D11; using Windows.Win32.Graphics.Dxgi; using Windows.Win32.Graphics.DirectComposition; -using WinRT; -using Windows.Win32; using Windows.Win32.Graphics.Dwm; -using static Vanara.PInvoke.ShlwApi; -using static Vanara.PInvoke.User32; +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 namespace Files.App.ViewModels.Previews { public sealed class ShellPreviewViewModel : BasePreviewModel { - public ShellPreviewViewModel(ListedItem item) - : base(item) + PreviewHandler? currentHandler; + ContentExternalOutputLink? outputLink; + WNDCLASSEXW? wCls; + HWND hwnd = HWND.Null; + bool isOfficePreview = false; + + public ShellPreviewViewModel(ListedItem item) : base(item) { } public async override Task> LoadPreviewAndDetailsAsync() => []; - 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"); - - PreviewHandler? currentHandler; - ContentExternalOutputLink? outputLink; - WindowClass? wCls; - HWND hwnd = HWND.NULL; - bool isOfficePreview = false; - - public static Guid? FindPreviewHandlerFor(string extension, IntPtr 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, IntPtr.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); + if (currentHandler != null) currentHandler.ResetBounds(new(0, 0, size.Width, size.Height)); + if (outputLink is not null) outputLink.PlacementVisual.Size = new(size.Width, size.Height); } - private IntPtr WndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) + private unsafe LRESULT WndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { - if (msg == (uint)WindowMessage.WM_CREATE) + //if (msg == 0x0081 /*WM_NCCREATE*/) + //{ + // try + // { + // var cp = Marshal.PtrToStructure(lParam).lpCreateParams; + // var pCreateParams = new nint(cp); + // if (pCreateParams != nint.Zero && GCHandle.FromIntPtr(pCreateParams).Target is IWindowInit wnd) + // return wnd.InitWndProcOnNCCreate( + // hwnd, + // msg, + // Marshal.GetFunctionPointerForDelegate(wndProc ?? throw new NullReferenceException()), + // lParam); + // } + // catch { } + //} + //else + if (msg == 0x0001 /*WM_CREATE*/) { - var clsid = FindPreviewHandlerFor(Item.FileExtension, hwnd.DangerousGetHandle()); + var clsid = FindPreviewHandlerFor(Item.FileExtension, hwnd.Value); + isOfficePreview = new Guid?[] { - Guid.Parse("84F66100-FF7C-4fb4-B0C0-02CD7FB668FE"), + Guid.Parse("84F66100-FF7C-4fb4-B0C0-02CD7FB668FE"), // Guid.Parse("65235197-874B-4A07-BDC5-E65EA825B718"), - Guid.Parse("00020827-0000-0000-C000-000000000046") }.Contains(clsid); + Guid.Parse("00020827-0000-0000-C000-000000000046") + }.Contains(clsid); + try { - currentHandler = new PreviewHandler(clsid.Value, hwnd.DangerousGetHandle()); + currentHandler = new PreviewHandler(clsid.Value, hwnd.Value); currentHandler.InitWithFileWithEveryWay(Item.ItemPath); currentHandler.DoPreview(); } @@ -96,7 +119,7 @@ private IntPtr WndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) UnloadPreview(); } } - else if (msg == (uint)WindowMessage.WM_DESTROY) + else if (msg == 0x0002 /*WM_DESTROY*/) { if (currentHandler is not null) { @@ -104,21 +127,53 @@ private IntPtr WndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) currentHandler = 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); - 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); + fixed (char* pszClassName = szClassName) + { + wCls = new WNDCLASSEXW + { + cbSize = (uint)Marshal.SizeOf(typeof(WNDCLASSEXW)), + lpfnWndProc = new(WndProc), + hInstance = hInst, + lpszClassName = pszClassName, + style = 0, + hIcon = default, + hIconSm = default, + hCursor = default, + hbrBackground = default, + lpszMenuName = null, + cbClsExtra = 0, + cbWndExtra = 0, + }; + + fixed (char* pszWindowName = szWindowName) + { + hwnd = PInvoke.CreateWindowEx( + WINDOW_EX_STYLE.WS_EX_LAYERED | WINDOW_EX_STYLE.WS_EX_COMPOSITED, + wCls.Value.lpszClassName, + 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); } - private unsafe bool ChildWindowToXaml(IntPtr parent, UIElement presenter) + private unsafe bool ChildWindowToXaml(nint parent, UIElement presenter) { D3D_DRIVER_TYPE[] driverTypes = [ @@ -134,7 +189,7 @@ private unsafe bool ChildWindowToXaml(IntPtr parent, UIElement presenter) var hr = PInvoke.D3D11CreateDevice( null, driveType, - new(IntPtr.Zero), + new(nint.Zero), D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_BGRA_SUPPORT, null, 0, @@ -149,13 +204,15 @@ private unsafe bool ChildWindowToXaml(IntPtr parent, UIElement presenter) if (d3d11Device is null) return false; + IDXGIDevice dxgiDevice = (IDXGIDevice)d3d11Device; if (PInvoke.DCompositionCreateDevice(dxgiDevice, typeof(IDCompositionDevice).GUID, out var compDevicePtr).Failed) return false; + IDCompositionDevice compDevice = (IDCompositionDevice)compDevicePtr; compDevice.CreateVisual(out var childVisual); - compDevice.CreateSurfaceFromHwnd(new(hwnd.DangerousGetHandle()), out var controlSurface); + compDevice.CreateSurfaceFromHwnd(hwnd, out var controlSurface); childVisual.SetContent(controlSurface); if (childVisual is null || controlSurface is null) return false; @@ -196,12 +253,13 @@ private unsafe bool ChildWindowToXaml(IntPtr parent, UIElement presenter) public void UnloadPreview() { - if (hwnd != HWND.NULL) - DestroyWindow(hwnd); - if (outputLink is not null) - outputLink.Dispose(); + if (hwnd != HWND.Null) + PInvoke.DestroyWindow(hwnd); + + outputLink?.Dispose(); + if (wCls is not null) - UnregisterClass(wCls.ClassName, Kernel32.GetModuleHandle()); + PInvoke.UnregisterClass(wCls.Value.lpszClassName, PInvoke.GetModuleHandle(default(PWSTR))); } public void PointerEntered(bool onPreview) @@ -220,12 +278,14 @@ public void PointerEntered(bool onPreview) } if (isOfficePreview) - Win32Helper.SetWindowLong(hwnd, WindowLongFlags.GWL_EXSTYLE, 0); + 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)); unsafe {