From 4fe3e27fa4cba7257bc765b26206c25342ccc121 Mon Sep 17 00:00:00 2001 From: Samuel Chapleau Date: Tue, 24 Jan 2023 11:02:01 -0800 Subject: [PATCH] [Peek] Fix selecting files from the correct focused opened File Explorer tab & from Desktop (#23489) * Get file based on active tab handle instead of window title * Refactor code to get active tab * Getting all items from the shell API working again, except for desktop * Refactor and cleanup com & native code * Add back removed peek xaml assets in Product.wxs * Remove some dependencies that do not seem necessary in Product.wxs --- installer/PowerToysSetup/Product.wxs | 4 +- .../Extensions/IPropertyStoreExtensions.cs | 122 ++++ .../Helpers/PropertyStoreHelper.cs | 65 ++ src/modules/peek/Peek.Common/Models/Blob.cs | 16 + .../peek/Peek.Common/Models/CALPWSTR.cs | 16 + .../peek/Peek.Common/Models/FileTime.cs | 15 + .../peek/Peek.Common/Models/IEnumIDList.cs | 27 + .../peek/Peek.Common/Models/IFolderView.cs | 46 ++ .../peek/Peek.Common/Models/IPropertyStore.cs | 26 + .../Peek.Common/Models/IServiceProvider.cs | 17 + .../peek/Peek.Common/Models/IShellBrowser.cs | 46 ++ .../peek/Peek.Common/Models/IShellFolder2.cs | 67 ++ .../peek/Peek.Common/Models/IShellItem.cs | 16 +- .../peek/Peek.Common/Models/IShellItem2.cs | 71 ++ .../Peek.Common/Models/IShellItemArray.cs | 38 + .../peek/Peek.Common/Models/IShellView.cs | 18 + .../peek/Peek.Common/Models/PropVariant.cs | 60 ++ .../peek/Peek.Common/Models/PropertyKey.cs | 65 ++ .../Models/PropertyStoreShellApi.cs | 650 ------------------ .../peek/Peek.Common/Models/SHELLDETAILS.cs | 17 + .../peek/Peek.Common/Models/SHFILEINFO.cs | 21 + src/modules/peek/Peek.Common/Models/Strret.cs | 30 + .../peek/Peek.Common/NativeMethods.json | 4 + .../peek/Peek.Common/NativeMethods.txt | 5 + .../peek/Peek.Common/Peek.Common.csproj | 4 + .../peek/Peek.FilePreviewer/FilePreview.xaml | 2 +- .../ImagePreviewer/Helpers/PropertyHelper.cs | 18 +- .../peek/Peek.UI/Extensions/HWNDExtensions.cs | 45 ++ src/modules/peek/Peek.UI/FolderItemsQuery.cs | 65 +- .../Peek.UI/Helpers/FileExplorerHelper.cs | 119 +++- src/modules/peek/Peek.UI/MainWindow.xaml.cs | 13 +- .../peek/Peek.UI/Native/NativeMethods.cs | 6 +- src/modules/peek/Peek.UI/NativeMethods.txt | 6 +- 33 files changed, 972 insertions(+), 768 deletions(-) create mode 100644 src/modules/peek/Peek.Common/Extensions/IPropertyStoreExtensions.cs create mode 100644 src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs create mode 100644 src/modules/peek/Peek.Common/Models/Blob.cs create mode 100644 src/modules/peek/Peek.Common/Models/CALPWSTR.cs create mode 100644 src/modules/peek/Peek.Common/Models/FileTime.cs create mode 100644 src/modules/peek/Peek.Common/Models/IEnumIDList.cs create mode 100644 src/modules/peek/Peek.Common/Models/IFolderView.cs create mode 100644 src/modules/peek/Peek.Common/Models/IPropertyStore.cs create mode 100644 src/modules/peek/Peek.Common/Models/IServiceProvider.cs create mode 100644 src/modules/peek/Peek.Common/Models/IShellBrowser.cs create mode 100644 src/modules/peek/Peek.Common/Models/IShellFolder2.cs create mode 100644 src/modules/peek/Peek.Common/Models/IShellItem2.cs create mode 100644 src/modules/peek/Peek.Common/Models/IShellItemArray.cs create mode 100644 src/modules/peek/Peek.Common/Models/IShellView.cs create mode 100644 src/modules/peek/Peek.Common/Models/PropVariant.cs create mode 100644 src/modules/peek/Peek.Common/Models/PropertyKey.cs delete mode 100644 src/modules/peek/Peek.Common/Models/PropertyStoreShellApi.cs create mode 100644 src/modules/peek/Peek.Common/Models/SHELLDETAILS.cs create mode 100644 src/modules/peek/Peek.Common/Models/SHFILEINFO.cs create mode 100644 src/modules/peek/Peek.Common/Models/Strret.cs create mode 100644 src/modules/peek/Peek.Common/NativeMethods.json create mode 100644 src/modules/peek/Peek.Common/NativeMethods.txt create mode 100644 src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index ca6cbcc2dc..33f47678f4 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -126,7 +126,7 @@ - + @@ -147,6 +147,8 @@ + + diff --git a/src/modules/peek/Peek.Common/Extensions/IPropertyStoreExtensions.cs b/src/modules/peek/Peek.Common/Extensions/IPropertyStoreExtensions.cs new file mode 100644 index 0000000000..78a9c81a51 --- /dev/null +++ b/src/modules/peek/Peek.Common/Extensions/IPropertyStoreExtensions.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Peek.Common.Models; + +namespace Peek.Common.Extensions +{ + public static class IPropertyStoreExtensions + { + /// + /// Helper method that retrieves a uint value from the given property store. + /// Returns 0 if the value is not a VT_UI4 (4-byte unsigned integer in little-endian order). + /// + /// The property store + /// The pkey + /// The uint value + public static uint GetUInt(this IPropertyStore propertyStore, PropertyKey key) + { + if (propertyStore == null) + { + throw new ArgumentNullException("propertyStore"); + } + + PropVariant propVar; + + propertyStore.GetValue(ref key, out propVar); + + // VT_UI4 Indicates a 4-byte unsigned integer formatted in little-endian byte order. + if ((VarEnum)propVar.Vt == VarEnum.VT_UI4) + { + return propVar.UlVal; + } + else + { + return 0; + } + } + + /// + /// Helper method that retrieves a ulong value from the given property store. + /// Returns 0 if the value is not a VT_UI8 (8-byte unsigned integer in little-endian order). + /// + /// The property store + /// the pkey + /// the ulong value + public static ulong GetULong(this IPropertyStore propertyStore, PropertyKey key) + { + if (propertyStore == null) + { + throw new ArgumentNullException("propertyStore"); + } + + PropVariant propVar; + + propertyStore.GetValue(ref key, out propVar); + + // VT_UI8 Indicates an 8-byte unsigned integer formatted in little-endian byte order. + if ((VarEnum)propVar.Vt == VarEnum.VT_UI8) + { + return propVar.UhVal; + } + else + { + return 0; + } + } + + /// + /// Helper method that retrieves a string value from the given property store. + /// + /// The property store + /// The pkey + /// The string value + public static string GetString(this IPropertyStore propertyStore, PropertyKey key) + { + PropVariant propVar; + + propertyStore.GetValue(ref key, out propVar); + + if ((VarEnum)propVar.Vt == VarEnum.VT_LPWSTR) + { + return Marshal.PtrToStringUni(propVar.P) ?? string.Empty; + } + else + { + return string.Empty; + } + } + + /// + /// Helper method that retrieves an array of string values from the given property store. + /// + /// The property store + /// The pkey + /// The array of string values + public static string[] GetStringArray(this IPropertyStore propertyStore, PropertyKey key) + { + PropVariant propVar; + propertyStore.GetValue(ref key, out propVar); + + List values = new List(); + + if ((VarEnum)propVar.Vt == (VarEnum.VT_LPWSTR | VarEnum.VT_VECTOR)) + { + for (int elementIndex = 0; elementIndex < propVar.Calpwstr.CElems; elementIndex++) + { + var stringVal = Marshal.PtrToStringUni(Marshal.ReadIntPtr(propVar.Calpwstr.PElems, elementIndex)); + if (stringVal != null) + { + values.Add(stringVal); + } + } + } + + return values.ToArray(); + } + } +} diff --git a/src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs b/src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs new file mode 100644 index 0000000000..8bc8f32738 --- /dev/null +++ b/src/modules/peek/Peek.Common/Helpers/PropertyStoreHelper.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Peek.Common.Models; +using Windows.Win32.UI.Shell.PropertiesSystem; + +namespace Peek.Common.Helpers +{ + public static partial class PropertyStoreHelper + { + /// + /// Gets a IPropertyStore interface from the given path. + /// + /// The file/folder path + /// The property store flags + /// an IPropertyStroe interface + public static IPropertyStore GetPropertyStoreFromPath(string path, GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_EXTRINSICPROPERTIES) + { + IShellItem2? shellItem2 = null; + IntPtr ppPropertyStore = IntPtr.Zero; + + try + { + SHCreateItemFromParsingName(path, IntPtr.Zero, typeof(IShellItem2).GUID, out shellItem2); + + if (shellItem2 == null) + { + throw new InvalidOperationException(string.Format("Unable to get an IShellItem2 reference from file {0}.", path)); + } + + int hr = shellItem2.GetPropertyStore((int)flags, typeof(IPropertyStore).GUID, out ppPropertyStore); + + if (hr != 0) + { + throw new InvalidOperationException(string.Format("GetPropertyStore retunred hresult={0}", hr)); + } + + return (IPropertyStore)Marshal.GetObjectForIUnknown(ppPropertyStore); + } + finally + { + if (ppPropertyStore != IntPtr.Zero) + { + Marshal.Release(ppPropertyStore); + } + + if (shellItem2 != null) + { + Marshal.ReleaseComObject(shellItem2); + } + } + } + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern void SHCreateItemFromParsingName( + [In][MarshalAs(UnmanagedType.LPWStr)] string pszPath, + [In] IntPtr pbc, + [In][MarshalAs(UnmanagedType.LPStruct)] Guid riid, + [Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem2 ppv); + } +} diff --git a/src/modules/peek/Peek.Common/Models/Blob.cs b/src/modules/peek/Peek.Common/Models/Blob.cs new file mode 100644 index 0000000000..eea74f9ece --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/Blob.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [StructLayout(LayoutKind.Sequential)] + public struct Blob + { + public int CbSize; + public IntPtr PBlobData; + } +} diff --git a/src/modules/peek/Peek.Common/Models/CALPWSTR.cs b/src/modules/peek/Peek.Common/Models/CALPWSTR.cs new file mode 100644 index 0000000000..c30c6dcc45 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/CALPWSTR.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [StructLayout(LayoutKind.Sequential)] + public struct CALPWSTR + { + public uint CElems; + public IntPtr PElems; + } +} diff --git a/src/modules/peek/Peek.Common/Models/FileTime.cs b/src/modules/peek/Peek.Common/Models/FileTime.cs new file mode 100644 index 0000000000..219bcb02e4 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/FileTime.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [StructLayout(LayoutKind.Sequential)] + public struct FileTime + { + public int DWHighDateTime; + public int DWLowDateTime; + } +} diff --git a/src/modules/peek/Peek.Common/Models/IEnumIDList.cs b/src/modules/peek/Peek.Common/Models/IEnumIDList.cs new file mode 100644 index 0000000000..e3bdd50602 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IEnumIDList.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("000214F2-0000-0000-C000-000000000046")] + public interface IEnumIDList + { + [PreserveSig] + int Next(int celt, out IntPtr rgelt, out int pceltFetched); + + [PreserveSig] + int Skip(int celt); + + [PreserveSig] + int Reset(); + + [PreserveSig] + int Clone(out IEnumIDList ppenum); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IFolderView.cs b/src/modules/peek/Peek.Common/Models/IFolderView.cs new file mode 100644 index 0000000000..0fb8b2567b --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IFolderView.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Security; + +namespace Peek.Common.Models +{ + [ComImport] + [Guid("cde725b0-ccc9-4519-917e-325d72fab4ce")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurity] + public interface IFolderView + { + void GetCurrentViewMode([Out] out uint pViewMode); + + void SetCurrentViewMode([In] uint viewMode); + + void GetFolder([In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out object ppv); + + void Item([In] int iItemIndex, [Out] out IntPtr ppidl); + + void ItemCount([In] uint uFlags, [Out] out int pcItems); + + void Items([In] uint uFlags, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out object ppv); + + void GetSelectionMarkedItem([Out] out int piItem); + + void GetFocusedItem([Out] out int piItem); + + void GetItemPosition([In] IntPtr pidl, [Out] out Point ppt); + + void GetSpacing([In, Out] ref Point ppt); + + void GetDefaultSpacing([Out] out Point ppt); + + void GetAutoArrange(); + + void SelectItem([In] int iItem, [In] uint dwFlags); + + void SelectAndPositionItems([In] uint cidl, [In] IntPtr apidl, [In] IntPtr apt, [In] uint dwFlags); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IPropertyStore.cs b/src/modules/peek/Peek.Common/Models/IPropertyStore.cs new file mode 100644 index 0000000000..7a4e4b8842 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IPropertyStore.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using static Peek.Common.Helpers.PropertyStoreHelper; + +namespace Peek.Common.Models +{ + [ComImport] + [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IPropertyStore + { + void GetCount(out uint propertyCount); + + void GetAt(uint iProp, out PropertyKey pkey); + + void GetValue(ref PropertyKey key, out PropVariant pv); + + void SetValue(ref PropertyKey key, ref PropVariant pv); + + void Commit(); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IServiceProvider.cs b/src/modules/peek/Peek.Common/Models/IServiceProvider.cs new file mode 100644 index 0000000000..5a5fac2753 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IServiceProvider.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [Guid("6D5140C1-7436-11CE-8034-00AA006009FA")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IServiceProvider + { + [return: MarshalAs(UnmanagedType.IUnknown)] + object QueryService([MarshalAs(UnmanagedType.LPStruct)] Guid service, [MarshalAs(UnmanagedType.LPStruct)] Guid riid); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IShellBrowser.cs b/src/modules/peek/Peek.Common/Models/IShellBrowser.cs new file mode 100644 index 0000000000..ca19c9ea4c --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IShellBrowser.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("000214E2-0000-0000-C000-000000000046")] + public interface IShellBrowser + { + void GetWindow(out IntPtr phwnd); + + void ContextSensitiveHelp(bool fEnterMode); + + void InsertMenusSB(IntPtr hmenuShared, IntPtr lpMenuWidths); + + void SetMenuSB(IntPtr hmenuShared, IntPtr holemenuRes, IntPtr hwndActiveObject); + + void RemoveMenusSB(IntPtr hmenuShared); + + void SetStatusTextSB(IntPtr pszStatusText); + + void EnableModelessSB(bool fEnable); + + void TranslateAcceleratorSB(IntPtr pmsg, ushort wID); + + void BrowseObject(IntPtr pidl, uint wFlags); + + void GetViewStateStream(uint grfMode, IntPtr ppStrm); + + void GetControlWindow(uint id, out IntPtr lpIntPtr); + + void SendControlMsg(uint id, uint uMsg, uint wParam, uint lParam, IntPtr pret); + + [return: MarshalAs(UnmanagedType.IUnknown)] + object QueryActiveShellView(); + + void OnViewWindowActive(IShellView ppshv); + + void SetToolbarItems(IntPtr lpButtons, uint nButtons, uint uFlags); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IShellFolder2.cs b/src/modules/peek/Peek.Common/Models/IShellFolder2.cs new file mode 100644 index 0000000000..8925d9ed37 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IShellFolder2.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using Windows.Win32.UI.Shell; + +namespace Peek.Common.Models +{ + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("93F2F68C-1D1B-11D3-A30E-00C04F79ABD1")] + public interface IShellFolder2 + { + [PreserveSig] + int ParseDisplayName(IntPtr hwnd, IntPtr pbc, [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, ref int pchEaten, out IntPtr ppidl, ref int pdwAttributes); + + [PreserveSig] + int EnumObjects(IntPtr hwnd, _SHCONTF grfFlags, out IntPtr enumIDList); + + [PreserveSig] + int BindToObject(IntPtr pidl, IntPtr pbc, ref Guid riid, out IntPtr ppv); + + [PreserveSig] + int BindToStorage(IntPtr pidl, IntPtr pbc, ref Guid riid, out IntPtr ppv); + + [PreserveSig] + int CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2); + + [PreserveSig] + int CreateViewObject(IntPtr hwndOwner, Guid riid, out IntPtr ppv); + + [PreserveSig] + int GetAttributesOf(int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, ref IntPtr rgfInOut); + + [PreserveSig] + int GetUIObjectOf(IntPtr hwndOwner, int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, ref Guid riid, IntPtr rgfReserved, out IntPtr ppv); + + [PreserveSig] + int GetDisplayNameOf(IntPtr pidl, SHGDNF uFlags, out Strret lpName); + + [PreserveSig] + int SetNameOf(IntPtr hwnd, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string pszName, int uFlags, out IntPtr ppidlOut); + + [PreserveSig] + int EnumSearches(out IntPtr ppenum); + + [PreserveSig] + int GetDefaultColumn(int dwReserved, ref IntPtr pSort, out IntPtr pDisplay); + + [PreserveSig] + int GetDefaultColumnState(int iColumn, out IntPtr pcsFlags); + + [PreserveSig] + int GetDefaultSearchGUID(out IntPtr guid); + + [PreserveSig] + int GetDetailsEx(IntPtr pidl, IntPtr pscid, out IntPtr pv); + + [PreserveSig] + int GetDetailsOf(IntPtr pidl, int iColumn, ref SHELLDETAILS psd); + + [PreserveSig] + int MapColumnToSCID(int icolumn, IntPtr pscid); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IShellItem.cs b/src/modules/peek/Peek.Common/Models/IShellItem.cs index ca2c636dca..e4c76fb913 100644 --- a/src/modules/peek/Peek.Common/Models/IShellItem.cs +++ b/src/modules/peek/Peek.Common/Models/IShellItem.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using Windows.Win32.UI.Shell; namespace Peek.Common.Models { @@ -20,22 +21,11 @@ namespace Peek.Common.Models void GetParent(out IShellItem ppsi); - void GetDisplayName(Sigdn sigdnName, out IntPtr ppszName); + [return: MarshalAs(UnmanagedType.LPWStr)] + string GetDisplayName(SIGDN sigdnName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItem psi, uint hint, out int piOrder); } - - public enum Sigdn : uint - { - NormalDisplay = 0, - ParentRelativeParsing = 0x80018001, - ParentRelativeForAddressBar = 0x8001c001, - DesktopAbsoluteParsing = 0x80028000, - ParentRelativeEditing = 0x80031001, - DesktopAbsoluteEditing = 0x8004c000, - FileSysPath = 0x80058000, - Url = 0x80068000, - } } diff --git a/src/modules/peek/Peek.Common/Models/IShellItem2.cs b/src/modules/peek/Peek.Common/Models/IShellItem2.cs new file mode 100644 index 0000000000..3410620592 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IShellItem2.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Windows.Win32.UI.Shell.PropertiesSystem; + +namespace Peek.Common.Models +{ + [ComImport] + [Guid("7E9FB0D3-919F-4307-AB2E-9B1860310C93")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IShellItem2 : IShellItem + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public int GetDisplayName([In] int sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public void GetAttributes([In] int sfgaoMask, out int psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + + [PreserveSig] + public int GetPropertyStore(int flags, ref Guid riid, out IntPtr ppv); + + [PreserveSig] + internal int GetPropertyStoreWithCreateObject(ref GETPROPERTYSTOREFLAGS flags, ref IntPtr punkFactory, ref Guid riid, out IntPtr ppv); + + [PreserveSig] + internal int GetPropertyStoreForKeys(ref PropertyKey keys, uint cKeys, ref GETPROPERTYSTOREFLAGS flags, ref Guid riid, out IntPtr ppv); + + [PreserveSig] + public int GetPropertyDescriptionList(ref PropertyKey key, ref Guid riid, out IntPtr ppv); + + [PreserveSig] + public int Update(ref IntPtr pbc); + + [PreserveSig] + public int GetProperty(ref PropertyKey key, out PropVariant pPropVar); + + [PreserveSig] + public int GetCLSID(ref PropertyKey key, out Guid clsid); + + [PreserveSig] + public int GetFileTime(ref PropertyKey key, out FileTime pft); + + [PreserveSig] + public int GetInt32(ref PropertyKey key, out int pi); + + [PreserveSig] + public int GetString(ref PropertyKey key, [MarshalAs(UnmanagedType.LPWStr)] string ppsz); + + [PreserveSig] + public int GetUint32(ref PropertyKey key, out uint pui); + + [PreserveSig] + public int GetUint64(ref PropertyKey key, out uint pull); + + [PreserveSig] + public int GetBool(ref PropertyKey key, bool pf); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IShellItemArray.cs b/src/modules/peek/Peek.Common/Models/IShellItemArray.cs new file mode 100644 index 0000000000..5a8df7e7c1 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IShellItemArray.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Windows.Win32.UI.Shell; + +namespace Peek.Common.Models +{ + [ComImport] + [Guid("B63EA76D-1F85-456F-A19C-48159EFA858B")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IShellItemArray + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyStore([In] int flags, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyDescriptionList([In] ref PropertyKey keyType, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributes([In] SIATTRIBFLAGS dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + int GetCount(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + Common.Models.IShellItem GetItemAt(int dwIndex); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems); + } +} diff --git a/src/modules/peek/Peek.Common/Models/IShellView.cs b/src/modules/peek/Peek.Common/Models/IShellView.cs new file mode 100644 index 0000000000..7a0a8d20a4 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/IShellView.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Peek.Common.Models +{ + [ComImport] + [Guid("000214E3-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurity] + public interface IShellView + { + } +} diff --git a/src/modules/peek/Peek.Common/Models/PropVariant.cs b/src/modules/peek/Peek.Common/Models/PropVariant.cs new file mode 100644 index 0000000000..bd53af2981 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/PropVariant.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [StructLayout(LayoutKind.Explicit)] + public struct PropVariant + { + [FieldOffset(0)] + public short Vt; + [FieldOffset(2)] + public short WReserved1; + [FieldOffset(4)] + public short WReserved2; + [FieldOffset(6)] + public short WReserved3; + [FieldOffset(8)] + public sbyte CVal; + [FieldOffset(8)] + public byte BVal; + [FieldOffset(8)] + public short IVal; + [FieldOffset(8)] + public ushort UiVal; + [FieldOffset(8)] + public int LVal; + [FieldOffset(8)] + public uint UlVal; + [FieldOffset(8)] + public int IntVal; + [FieldOffset(8)] + public uint UintVal; + [FieldOffset(8)] + public long HVal; + [FieldOffset(8)] + public ulong UhVal; + [FieldOffset(8)] + public float FltVal; + [FieldOffset(8)] + public double DblVal; + [FieldOffset(8)] + public bool BoolVal; + [FieldOffset(8)] + public int Scode; + [FieldOffset(8)] + public DateTime Date; + [FieldOffset(8)] + public FileTime Filetime; + [FieldOffset(8)] + public Blob Blob; + [FieldOffset(8)] + public IntPtr P; + [FieldOffset(8)] + public CALPWSTR Calpwstr; + } +} diff --git a/src/modules/peek/Peek.Common/Models/PropertyKey.cs b/src/modules/peek/Peek.Common/Models/PropertyKey.cs new file mode 100644 index 0000000000..892c85f73f --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/PropertyKey.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct PropertyKey + { + public Guid FormatId; + public int PropertyId; + + public PropertyKey(Guid guid, int propertyId) + { + this.FormatId = guid; + this.PropertyId = propertyId; + } + + public PropertyKey(uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h, uint i, uint j, uint k, int propertyId) + : this(new Guid((uint)a, (ushort)b, (ushort)c, (byte)d, (byte)e, (byte)f, (byte)g, (byte)h, (byte)i, (byte)j, (byte)k), propertyId) + { + } + + public override bool Equals(object? obj) + { + if ((obj == null) || !(obj is PropertyKey)) + { + return false; + } + + PropertyKey pk = (PropertyKey)obj; + + return FormatId.Equals(pk.FormatId) && (PropertyId == pk.PropertyId); + } + + public static bool operator ==(PropertyKey a, PropertyKey b) + { + if (((object)a == null) || ((object)b == null)) + { + return false; + } + + return a.FormatId == b.FormatId && a.PropertyId == b.PropertyId; + } + + public static bool operator !=(PropertyKey a, PropertyKey b) + { + return !(a == b); + } + + public override int GetHashCode() + { + return FormatId.GetHashCode() ^ PropertyId; + } + + // File properties: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wsp/2dbe759c-c955-4770-a545-e46d7f6332ed + public static readonly PropertyKey ImageHorizontalSize = new PropertyKey(new Guid(0x6444048F, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 3); + public static readonly PropertyKey ImageVerticalSize = new PropertyKey(new Guid(0x6444048F, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 4); + public static readonly PropertyKey FileSizeBytes = new PropertyKey(new Guid(0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac), 12); + public static readonly PropertyKey FileType = new PropertyKey(new Guid(0xd5cdd502, 0x2e9c, 0x101b, 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae), 26); + } +} diff --git a/src/modules/peek/Peek.Common/Models/PropertyStoreShellApi.cs b/src/modules/peek/Peek.Common/Models/PropertyStoreShellApi.cs deleted file mode 100644 index d3208b00d9..0000000000 --- a/src/modules/peek/Peek.Common/Models/PropertyStoreShellApi.cs +++ /dev/null @@ -1,650 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -namespace Peek.Common.Models -{ - public static class PropertyStoreShellApi - { - /// - /// Gets the path to the given known folder. - /// - /// Guid for given known folder - /// The path to the known folder. - public static string GetKnownFolderPath(Guid knownFolderId) - { - string path; - int hResult = SHGetKnownFolderPath(knownFolderId, 0, IntPtr.Zero, out path); - Marshal.ThrowExceptionForHR(hResult); - - return path; - } - - /// - /// Gets a IPropertyStore interface from the given path. - /// - /// The file/folder path - /// The property store flags - /// an IPropertyStroe interface - public static IPropertyStore GetPropertyStoreFromPath(string path, PropertyStoreFlags flags = PropertyStoreFlags.EXTRINSICPROPERTIES) - { - PropertyStoreShellApi.IShellItem2? shellItem2 = null; - IntPtr ppPropertyStore = IntPtr.Zero; - - try - { - PropertyStoreShellApi.SHCreateItemFromParsingName(path, IntPtr.Zero, typeof(PropertyStoreShellApi.IShellItem2).GUID, out shellItem2); - - if (shellItem2 == null) - { - throw new InvalidOperationException(string.Format("Unable to get an IShellItem2 reference from file {0}.", path)); - } - - int hr = shellItem2.GetPropertyStore((int)flags, typeof(PropertyStoreShellApi.IPropertyStore).GUID, out ppPropertyStore); - - if (hr != 0) - { - throw new InvalidOperationException(string.Format("GetPropertyStore retunred hresult={0}", hr)); - } - - return (PropertyStoreShellApi.IPropertyStore)Marshal.GetObjectForIUnknown(ppPropertyStore); - } - finally - { - if (ppPropertyStore != IntPtr.Zero) - { - Marshal.Release(ppPropertyStore); - } - - if (shellItem2 != null) - { - Marshal.ReleaseComObject(shellItem2); - } - } - } - - /// - /// Helper method that retrieves a uint value from the given property store. - /// Returns 0 if the value is not a VT_UI4 (4-byte unsigned integer in little-endian order). - /// - /// The property store - /// The pkey - /// The uint value - public static uint GetUIntFromPropertyStore(IPropertyStore propertyStore, PropertyKey key) - { - if (propertyStore == null) - { - throw new ArgumentNullException("propertyStore"); - } - - PropVariant propVar; - - propertyStore.GetValue(ref key, out propVar); - - // VT_UI4 Indicates a 4-byte unsigned integer formatted in little-endian byte order. - if ((VarEnum)propVar.Vt == VarEnum.VT_UI4) - { - return propVar.UlVal; - } - else - { - return 0; - } - } - - /// - /// Helper method that retrieves a ulong value from the given property store. - /// Returns 0 if the value is not a VT_UI8 (8-byte unsigned integer in little-endian order). - /// - /// The property store - /// the pkey - /// the ulong value - public static ulong GetULongFromPropertyStore(IPropertyStore propertyStore, PropertyKey key) - { - if (propertyStore == null) - { - throw new ArgumentNullException("propertyStore"); - } - - PropVariant propVar; - - propertyStore.GetValue(ref key, out propVar); - - // VT_UI8 Indicates an 8-byte unsigned integer formatted in little-endian byte order. - if ((VarEnum)propVar.Vt == VarEnum.VT_UI8) - { - return propVar.UhVal; - } - else - { - return 0; - } - } - - /// - /// Helper method that retrieves a string value from the given property store. - /// - /// The property store - /// The pkey - /// The string value - public static string GetStringFromPropertyStore(IPropertyStore propertyStore, PropertyKey key) - { - PropVariant propVar; - - propertyStore.GetValue(ref key, out propVar); - - if ((VarEnum)propVar.Vt == VarEnum.VT_LPWSTR) - { - return Marshal.PtrToStringUni(propVar.P) ?? string.Empty; - } - else - { - return string.Empty; - } - } - - /// - /// Helper method that retrieves an array of string values from the given property store. - /// - /// The property store - /// The pkey - /// The array of string values - public static string[] GetStringArrayFromPropertyStore(IPropertyStore propertyStore, PropertyKey key) - { - PropVariant propVar; - propertyStore.GetValue(ref key, out propVar); - - List values = new List(); - - if ((VarEnum)propVar.Vt == (VarEnum.VT_LPWSTR | VarEnum.VT_VECTOR)) - { - for (int elementIndex = 0; elementIndex < propVar.Calpwstr.CElems; elementIndex++) - { - var stringVal = Marshal.PtrToStringUni(Marshal.ReadIntPtr(propVar.Calpwstr.PElems, elementIndex)); - if (stringVal != null) - { - values.Add(stringVal); - } - } - } - - return values.ToArray(); - } - - private const string IIDIShellItem = "43826D1E-E718-42EE-BC55-A1E261C37BFE"; - private const string IIDIShellFolder2 = "93F2F68C-1D1B-11D3-A30E-00C04F79ABD1"; - private const string IIDIEnumIDList = "000214F2-0000-0000-C000-000000000046"; - private const string BHIDSFObject = "3981e224-f559-11d3-8e3a-00c04f6837d5"; - private const int KfFlagDefaultPath = 0x00000400; - private const int SigdnNormalDisplay = 0; - - [Flags] - public enum Shcontf : int - { - Folders = 0x0020, - NonFolders = 0x0040, - IncludeHidden = 0x0080, - InitOnFirstNext = 0x0100, - NetPrinterSearch = 0x0200, - Shareable = 0x0400, - Storage = 0x0800, - } - - [SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", Justification = "Targeting Windows (X86/AMD64/ARM) only")] - [StructLayout(LayoutKind.Explicit)] - public struct Strret - { - [FieldOffset(0)] - public int UType; - - [FieldOffset(4)] - public IntPtr POleStr; - - [FieldOffset(4)] - public IntPtr PStr; - - [FieldOffset(4)] - public int UOffset; - - [FieldOffset(4)] - public IntPtr CStr; - } - - public enum Shgno - { - Normal = 0x0000, - InFolder = 0x0001, - ForEditing = 0x1000, - ForAddressBar = 0x4000, - ForParsing = 0x8000, - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct SHELLDETAILS - { - public int Fmt; - public int CxChar; - public Strret Str; - } - - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid(IIDIShellFolder2)] - public interface IShellFolder2 - { - [PreserveSig] - int ParseDisplayName(IntPtr hwnd, IntPtr pbc, [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, ref int pchEaten, out IntPtr ppidl, ref int pdwAttributes); - - [PreserveSig] - int EnumObjects(IntPtr hwnd, Shcontf grfFlags, out IntPtr enumIDList); - - [PreserveSig] - int BindToObject(IntPtr pidl, IntPtr pbc, ref Guid riid, out IntPtr ppv); - - [PreserveSig] - int BindToStorage(IntPtr pidl, IntPtr pbc, ref Guid riid, out IntPtr ppv); - - [PreserveSig] - int CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2); - - [PreserveSig] - int CreateViewObject(IntPtr hwndOwner, Guid riid, out IntPtr ppv); - - [PreserveSig] - int GetAttributesOf(int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, ref IntPtr rgfInOut); - - [PreserveSig] - int GetUIObjectOf(IntPtr hwndOwner, int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, ref Guid riid, IntPtr rgfReserved, out IntPtr ppv); - - [PreserveSig] - int GetDisplayNameOf(IntPtr pidl, Shgno uFlags, out Strret lpName); - - [PreserveSig] - int SetNameOf(IntPtr hwnd, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string pszName, int uFlags, out IntPtr ppidlOut); - - [PreserveSig] - int EnumSearches(out IntPtr ppenum); - - [PreserveSig] - int GetDefaultColumn(int dwReserved, ref IntPtr pSort, out IntPtr pDisplay); - - [PreserveSig] - int GetDefaultColumnState(int iColumn, out IntPtr pcsFlags); - - [PreserveSig] - int GetDefaultSearchGUID(out IntPtr guid); - - [PreserveSig] - int GetDetailsEx(IntPtr pidl, IntPtr pscid, out IntPtr pv); - - [PreserveSig] - int GetDetailsOf(IntPtr pidl, int iColumn, ref SHELLDETAILS psd); - - [PreserveSig] - int MapColumnToSCID(int icolumn, IntPtr pscid); - } - - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid(IIDIEnumIDList)] - public interface IEnumIDList - { - [PreserveSig] - int Next(int celt, out IntPtr rgelt, out int pceltFetched); - - [PreserveSig] - int Skip(int celt); - - [PreserveSig] - int Reset(); - - [PreserveSig] - int Clone(out IEnumIDList ppenum); - } - - [ComImport] - [Guid(IIDIShellItem)] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IShellItem - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - int GetDisplayName([In] int sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetAttributes([In] int sfgaoMask, out int psfgaoAttribs); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct SHFILEINFO - { - public IntPtr HIcon; - public int IIcon; - public uint DwAttributes; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] - public string SzDisplayName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] - public string SzTypeName; - } - - [StructLayout(LayoutKind.Sequential)] - public struct Blob - { - public int CbSize; - public IntPtr PBlobData; - } - - [StructLayout(LayoutKind.Explicit)] - public struct PropVariant - { - [FieldOffset(0)] - public short Vt; - [FieldOffset(2)] - public short WReserved1; - [FieldOffset(4)] - public short WReserved2; - [FieldOffset(6)] - public short WReserved3; - [FieldOffset(8)] - public sbyte CVal; - [FieldOffset(8)] - public byte BVal; - [FieldOffset(8)] - public short IVal; - [FieldOffset(8)] - public ushort UiVal; - [FieldOffset(8)] - public int LVal; - [FieldOffset(8)] - public uint UlVal; - [FieldOffset(8)] - public int IntVal; - [FieldOffset(8)] - public uint UintVal; - [FieldOffset(8)] - public long HVal; - [FieldOffset(8)] - public ulong UhVal; - [FieldOffset(8)] - public float FltVal; - [FieldOffset(8)] - public double DblVal; - [FieldOffset(8)] - public bool BoolVal; - [FieldOffset(8)] - public int Scode; - [FieldOffset(8)] - public DateTime Date; - [FieldOffset(8)] - public FileTime Filetime; - [FieldOffset(8)] - public Blob Blob; - [FieldOffset(8)] - public IntPtr P; - [FieldOffset(8)] - public CALPWSTR Calpwstr; - } - - [StructLayout(LayoutKind.Sequential)] - public struct CALPWSTR - { - public uint CElems; - public IntPtr PElems; - } - - public enum SIGDN : uint - { - NORMALDISPLAY = 0, - PARENTRELATIVEPARSING = 0x80018001, - PARENTRELATIVEFORADDRESSBAR = 0x8001c001, - DESKTOPABSOLUTEPARSING = 0x80028000, - PARENTRELATIVEEDITING = 0x80031001, - DESKTOPABSOLUTEEDITING = 0x8004c000, - FILESYSPATH = 0x80058000, - URL = 0x80068000, - } - - [StructLayout(LayoutKind.Sequential)] - private struct SYSTEMTIME - { - [MarshalAs(UnmanagedType.U2)] - public short Year; - [MarshalAs(UnmanagedType.U2)] - public short Month; - [MarshalAs(UnmanagedType.U2)] - public short DayOfWeek; - [MarshalAs(UnmanagedType.U2)] - public short Day; - [MarshalAs(UnmanagedType.U2)] - public short Hour; - [MarshalAs(UnmanagedType.U2)] - public short Minute; - [MarshalAs(UnmanagedType.U2)] - public short Second; - [MarshalAs(UnmanagedType.U2)] - public short Milliseconds; - - public SYSTEMTIME(DateTime dt) - { - dt = dt.ToUniversalTime(); // SetSystemTime expects the SYSTEMTIME in UTC - Year = (short)dt.Year; - Month = (short)dt.Month; - DayOfWeek = (short)dt.DayOfWeek; - Day = (short)dt.Day; - Hour = (short)dt.Hour; - Minute = (short)dt.Minute; - Second = (short)dt.Second; - Milliseconds = (short)dt.Millisecond; - } - } - - [ComImport] - [Guid("7E9FB0D3-919F-4307-AB2E-9B1860310C93")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IShellItem2 : IShellItem - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - new void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - new void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - new int GetDisplayName([In] int sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - new void GetAttributes([In] int sfgaoMask, out int psfgaoAttribs); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - new void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); - - [PreserveSig] - int GetPropertyStore(int flags, ref Guid riid, out IntPtr ppv); - - [PreserveSig] - int GetPropertyStoreWithCreateObject(ref PropertyStoreFlags flags, ref IntPtr punkFactory, ref Guid riid, out IntPtr ppv); - - [PreserveSig] - int GetPropertyStoreForKeys(ref PropertyKey keys, uint cKeys, ref PropertyStoreFlags flags, ref Guid riid, out IntPtr ppv); - - [PreserveSig] - int GetPropertyDescriptionList(ref PropertyKey key, ref Guid riid, out IntPtr ppv); - - [PreserveSig] - int Update(ref IntPtr pbc); - - [PreserveSig] - int GetProperty(ref PropertyKey key, out PropVariant pPropVar); - - [PreserveSig] - int GetCLSID(ref PropertyKey key, out Guid clsid); - - [PreserveSig] - int GetFileTime(ref PropertyKey key, out FileTime pft); - - [PreserveSig] - int GetInt32(ref PropertyKey key, out int pi); - - [PreserveSig] - int GetString(ref PropertyKey key, [MarshalAs(UnmanagedType.LPWStr)] string ppsz); - - [PreserveSig] - int GetUint32(ref PropertyKey key, out uint pui); - - [PreserveSig] - int GetUint64(ref PropertyKey key, out uint pull); - - [PreserveSig] - int GetBool(ref PropertyKey key, bool pf); - } - - [StructLayout(LayoutKind.Sequential)] - public struct FileTime - { - public int DWHighDateTime; - public int DWLowDateTime; - } - - [ComImport] - [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IPropertyStore - { - void GetCount(out uint propertyCount); - - void GetAt(uint iProp, out PropertyKey pkey); - - void GetValue(ref PropertyKey key, out PropVariant pv); - - void SetValue(ref PropertyKey key, ref PropVariant pv); - - void Commit(); - } - - public enum PropertyStoreFlags - { - DEFAULT = 0x00000000, - HANDLERPROPERTIESONLY = 0x00000001, - READWRITE = 0x00000002, - TEMPORARY = 0x00000004, - FASTPROPERTIESONLY = 0x00000008, - OPENSLOWITEM = 0x00000010, - DELAYCREATION = 0x00000020, - BESTEFFORT = 0x00000040, - NO_OPLOCK = 0x00000080, - PREFERQUERYPROPERTIES = 0x00000100, - EXTRINSICPROPERTIES = 0x00000200, - EXTRINSICPROPERTIESONLY = 0x00000400, - MASK_VALID = 0x000007ff, - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct PropertyKey - { - public Guid FormatId; - public int PropertyId; - - public PropertyKey(Guid guid, int propertyId) - { - this.FormatId = guid; - this.PropertyId = propertyId; - } - - public PropertyKey(uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h, uint i, uint j, uint k, int propertyId) - : this(new Guid((uint)a, (ushort)b, (ushort)c, (byte)d, (byte)e, (byte)f, (byte)g, (byte)h, (byte)i, (byte)j, (byte)k), propertyId) - { - } - - public override bool Equals(object? obj) - { - if ((obj == null) || !(obj is PropertyKey)) - { - return false; - } - - PropertyKey pk = (PropertyKey)obj; - - return FormatId.Equals(pk.FormatId) && (PropertyId == pk.PropertyId); - } - - public static bool operator ==(PropertyKey a, PropertyKey b) - { - if (((object)a == null) || ((object)b == null)) - { - return false; - } - - return a.FormatId == b.FormatId && a.PropertyId == b.PropertyId; - } - - public static bool operator !=(PropertyKey a, PropertyKey b) - { - return !(a == b); - } - - public override int GetHashCode() - { - return FormatId.GetHashCode() ^ PropertyId; - } - - // File properties: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wsp/2dbe759c-c955-4770-a545-e46d7f6332ed - public static readonly PropertyKey ImageHorizontalSize = new PropertyKey(new Guid(0x6444048F, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 3); - public static readonly PropertyKey ImageVerticalSize = new PropertyKey(new Guid(0x6444048F, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 4); - public static readonly PropertyKey FileSizeBytes = new PropertyKey(new Guid(0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac), 12); - public static readonly PropertyKey FileType = new PropertyKey(new Guid(0xd5cdd502, 0x2e9c, 0x101b, 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae), 26); - } - - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - private static extern int StrRetToBuf(ref Strret pstr, IntPtr pidl, StringBuilder pszBuf, int cchBuf); - - /// - /// Extracts a specified string from a specified resource via an indirect string - /// - /// An indirect string representing the desired string from a specified resource file - /// An output string, which receives the native function's outputted resource string - /// The buffer size to hold the output string, in characters - /// A reserved pointer (void**) - /// Returns an HRESULT representing the success/failure of the native function - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - internal static extern uint SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); - - [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out string pszPath); - - [DllImport("Shell32.dll", CharSet = CharSet.Unicode)] - private static extern int SHGetKnownFolderItem([In] ref Guid rfid, [In] int dwFlags, [In] IntPtr hToken, [In] ref Guid riid, [Out] out IntPtr ppv); - - [DllImport("shell32.dll", CharSet = CharSet.Unicode)] - private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags); - - [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern int SHGetPropertyStoreFromParsingName( - string pszPath, - IntPtr zeroWorks, - PropertyStoreFlags flags, - ref Guid iIdPropStore, - [Out] out IPropertyStore propertyStore); - - [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] - private static extern void SHCreateItemFromParsingName( - [In][MarshalAs(UnmanagedType.LPWStr)] string pszPath, - [In] IntPtr pbc, - [In][MarshalAs(UnmanagedType.LPStruct)] Guid riid, - [Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem2 ppv); - } -} diff --git a/src/modules/peek/Peek.Common/Models/SHELLDETAILS.cs b/src/modules/peek/Peek.Common/Models/SHELLDETAILS.cs new file mode 100644 index 0000000000..d423e556ac --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/SHELLDETAILS.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; +using static Peek.Common.Helpers.PropertyStoreHelper; + +namespace Peek.Common.Models +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct SHELLDETAILS + { + public int Fmt; + public int CxChar; + public Strret Str; + } +} diff --git a/src/modules/peek/Peek.Common/Models/SHFILEINFO.cs b/src/modules/peek/Peek.Common/Models/SHFILEINFO.cs new file mode 100644 index 0000000000..69db5cc865 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/SHFILEINFO.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SHFILEINFO + { + public IntPtr HIcon; + public int IIcon; + public uint DwAttributes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string SzDisplayName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] + public string SzTypeName; + } +} diff --git a/src/modules/peek/Peek.Common/Models/Strret.cs b/src/modules/peek/Peek.Common/Models/Strret.cs new file mode 100644 index 0000000000..dcb3796917 --- /dev/null +++ b/src/modules/peek/Peek.Common/Models/Strret.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace Peek.Common.Models +{ + [SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", Justification = "Targeting Windows (X86/AMD64/ARM) only")] + [StructLayout(LayoutKind.Explicit)] + public struct Strret + { + [FieldOffset(0)] + public int UType; + + [FieldOffset(4)] + public IntPtr POleStr; + + [FieldOffset(4)] + public IntPtr PStr; + + [FieldOffset(4)] + public int UOffset; + + [FieldOffset(4)] + public IntPtr CStr; + } +} diff --git a/src/modules/peek/Peek.Common/NativeMethods.json b/src/modules/peek/Peek.Common/NativeMethods.json new file mode 100644 index 0000000000..357c140355 --- /dev/null +++ b/src/modules/peek/Peek.Common/NativeMethods.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "public": true +} \ No newline at end of file diff --git a/src/modules/peek/Peek.Common/NativeMethods.txt b/src/modules/peek/Peek.Common/NativeMethods.txt new file mode 100644 index 0000000000..c5ac7c0399 --- /dev/null +++ b/src/modules/peek/Peek.Common/NativeMethods.txt @@ -0,0 +1,5 @@ +GETPROPERTYSTOREFLAGS +_SHCONTF +SIGDN +SHGDNF +SIATTRIBFLAGS \ No newline at end of file diff --git a/src/modules/peek/Peek.Common/Peek.Common.csproj b/src/modules/peek/Peek.Common/Peek.Common.csproj index 0acb5a9d9e..de413ec0e7 100644 --- a/src/modules/peek/Peek.Common/Peek.Common.csproj +++ b/src/modules/peek/Peek.Common/Peek.Common.csproj @@ -10,6 +10,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml index cc50556bfd..c2f665d249 100644 --- a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml +++ b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml @@ -27,8 +27,8 @@ diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/PropertyHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/PropertyHelper.cs index 1b8fb74235..182836f09f 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/PropertyHelper.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ImagePreviewer/Helpers/PropertyHelper.cs @@ -2,11 +2,13 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Runtime.InteropServices; using System.Threading.Tasks; +using Peek.Common.Extensions; +using Peek.Common.Helpers; using Peek.Common.Models; using Windows.Foundation; +using Windows.Win32.UI.Shell.PropertiesSystem; namespace Peek.FilePreviewer.Previewers { @@ -16,11 +18,11 @@ namespace Peek.FilePreviewer.Previewers { return Task.Run(() => { - var propertyStore = PropertyStoreShellApi.GetPropertyStoreFromPath(filePath, PropertyStoreShellApi.PropertyStoreFlags.OPENSLOWITEM); + var propertyStore = PropertyStoreHelper.GetPropertyStoreFromPath(filePath, GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM); if (propertyStore != null) { - var width = (int)PropertyStoreShellApi.GetUIntFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.ImageHorizontalSize); - var height = (int)PropertyStoreShellApi.GetUIntFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.ImageVerticalSize); + var width = (int)propertyStore.GetUInt(PropertyKey.ImageHorizontalSize); + var height = (int)propertyStore.GetUInt(PropertyKey.ImageVerticalSize); Marshal.ReleaseComObject(propertyStore); return new Size(width, height); @@ -33,10 +35,10 @@ namespace Peek.FilePreviewer.Previewers public static Task GetFileSizeInBytes(string filePath) { ulong bytes = 0; - var propertyStore = PropertyStoreShellApi.GetPropertyStoreFromPath(filePath, PropertyStoreShellApi.PropertyStoreFlags.OPENSLOWITEM); + var propertyStore = PropertyStoreHelper.GetPropertyStoreFromPath(filePath, GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM); if (propertyStore != null) { - bytes = PropertyStoreShellApi.GetULongFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.FileSizeBytes); + bytes = propertyStore.GetULong(PropertyKey.FileSizeBytes); Marshal.ReleaseComObject(propertyStore); } @@ -47,11 +49,11 @@ namespace Peek.FilePreviewer.Previewers public static Task GetFileType(string filePath) { var type = string.Empty; - var propertyStore = PropertyStoreShellApi.GetPropertyStoreFromPath(filePath, PropertyStoreShellApi.PropertyStoreFlags.OPENSLOWITEM); + var propertyStore = PropertyStoreHelper.GetPropertyStoreFromPath(filePath, GETPROPERTYSTOREFLAGS.GPS_OPENSLOWITEM); if (propertyStore != null) { // TODO: find a way to get user friendly description - type = PropertyStoreShellApi.GetStringFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.FileType); + type = propertyStore.GetString(PropertyKey.FileType); Marshal.ReleaseComObject(propertyStore); } diff --git a/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs b/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs new file mode 100644 index 0000000000..8f2e1a12b7 --- /dev/null +++ b/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Peek.UI.Native; +using Windows.Win32; +using Windows.Win32.Foundation; + +namespace Peek.UI.Extensions +{ + public static class HWNDExtensions + { + internal static HWND GetActiveTab(this HWND windowHandle) + { + var activeTab = windowHandle.FindChildWindow("ShellTabWindowClass"); + if (activeTab == HWND.Null) + { + activeTab = windowHandle.FindChildWindow("TabWindowClass"); + } + + return activeTab; + } + + internal static bool IsDesktopWindow(this HWND windowHandle) + { + StringBuilder strClassName = new StringBuilder(256); + NativeMethods.GetClassName(windowHandle, strClassName, 256); + + var className = strClassName.ToString(); + + if (className != "Progman" && className != "WorkerW") + { + return false; + } + + return windowHandle.FindChildWindow("SHELLDLL_DefView") != HWND.Null; + } + + internal static HWND FindChildWindow(this HWND windowHandle, string className) + { + return PInvoke.FindWindowEx(windowHandle, HWND.Null, className, null); + } + } +} diff --git a/src/modules/peek/Peek.UI/FolderItemsQuery.cs b/src/modules/peek/Peek.UI/FolderItemsQuery.cs index 81f2456cce..61fa3ea225 100644 --- a/src/modules/peek/Peek.UI/FolderItemsQuery.cs +++ b/src/modules/peek/Peek.UI/FolderItemsQuery.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; @@ -36,18 +37,6 @@ namespace Peek.UI private Task? InitializeFilesTask { get; set; } = null; - public static File? GetFileExplorerSelectedFile() - { - var shellItems = FileExplorerHelper.GetSelectedItems(); - var firstSelectedItem = shellItems?.Item(0); - if (shellItems == null || firstSelectedItem == null) - { - return null; - } - - return new File(firstSelectedItem.Path); - } - public void Clear() { CurrentFile = null; @@ -94,25 +83,21 @@ namespace Peek.UI public void Start() { - var folderView = FileExplorerHelper.GetCurrentFolderView(); - if (folderView == null) + var selectedItems = FileExplorerHelper.GetSelectedItems(); + if (!selectedItems.Any()) { return; } - Shell32.FolderItems selectedItems = folderView.SelectedItems(); - if (selectedItems == null || selectedItems.Count == 0) - { - return; - } - - IsMultiSelection = selectedItems.Count > 1; + bool hasMoreThanOneItem = selectedItems.Skip(1).Any(); + IsMultiSelection = hasMoreThanOneItem; // Prioritize setting CurrentFile, which notifies UI - var firstSelectedItem = selectedItems.Item(0); - CurrentFile = new File(firstSelectedItem.Path); + var firstSelectedItem = selectedItems.First(); + CurrentFile = firstSelectedItem; - var items = selectedItems.Count > 1 ? selectedItems : folderView.Folder?.Items(); + // TODO: we shouldn't get all files from the SHell API, we should query them + var items = hasMoreThanOneItem ? selectedItems : FileExplorerHelper.GetItems(); if (items == null) { return; @@ -148,32 +133,14 @@ namespace Peek.UI // the entire folder. We can then avoid iterating through all items here, and maintain a dynamic window of // loaded items around the current item index. private void InitializeFiles( - Shell32.FolderItems items, - Shell32.FolderItem firstSelectedItem, + IEnumerable items, + File firstSelectedItem, CancellationToken cancellationToken) { - var tempFiles = new List(items.Count); - var tempCurIndex = UninitializedItemIndex; + var listOfItems = items.ToList(); + var currentItemIndex = listOfItems.FindIndex(item => item.Path == firstSelectedItem.Path); - for (int i = 0; i < items.Count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - var item = items.Item(i); - if (item == null) - { - continue; - } - - if (item.Name == firstSelectedItem.Name) - { - tempCurIndex = i; - } - - tempFiles.Add(new File(item.Path)); - } - - if (tempCurIndex == UninitializedItemIndex) + if (currentItemIndex < 0) { Debug.WriteLine("File query initialization: selectedItem index not found. Navigation remains disabled."); return; @@ -187,8 +154,8 @@ namespace Peek.UI _dispatcherQueue.TryEnqueue(() => { - Files = tempFiles; - CurrentItemIndex = tempCurIndex; + Files = listOfItems; + CurrentItemIndex = currentItemIndex; }); } } diff --git a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs index d009c975cc..825a89c8dc 100644 --- a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs +++ b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs @@ -2,58 +2,111 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.IO; -using System.Text; -using Peek.UI.Native; +using System; +using System.Collections.Generic; +using Peek.Common.Models; +using Peek.UI.Extensions; +using SHDocVw; +using Shell32; using Windows.Win32; using Windows.Win32.Foundation; +using Windows.Win32.UI.Shell; +using IServiceProvider = Peek.Common.Models.IServiceProvider; namespace Peek.UI.Helpers { public static class FileExplorerHelper { - public static Shell32.FolderItems? GetSelectedItems() + public static IEnumerable GetSelectedItems() { - var folderView = GetCurrentFolderView(); - if (folderView == null) - { - return null; - } - - Shell32.FolderItems selectedItems = folderView.SelectedItems(); - if (selectedItems == null || selectedItems.Count == 0) - { - return null; - } - - return selectedItems; + return GetItemsInternal(onlySelectedFiles: true); } - public static Shell32.IShellFolderViewDual2? GetCurrentFolderView() + public static IEnumerable GetItems() { - var foregroundWindowHandle = NativeMethods.GetForegroundWindow(); + return GetItemsInternal(onlySelectedFiles: false); + } - int capacity = PInvoke.GetWindowTextLength(new HWND(foregroundWindowHandle)) * 2; - StringBuilder foregroundWindowTitleBuffer = new StringBuilder(capacity); - NativeMethods.GetWindowText(new HWND(foregroundWindowHandle), foregroundWindowTitleBuffer, foregroundWindowTitleBuffer.Capacity); - - string foregroundWindowTitle = foregroundWindowTitleBuffer.ToString(); - var directoryInfo = new DirectoryInfo(foregroundWindowTitle); - string windowFolderName = directoryInfo.Name; - - var shell = new Shell32.Shell(); - foreach (SHDocVw.InternetExplorer window in shell.Windows()) + private static IEnumerable GetItemsInternal(bool onlySelectedFiles) + { + var foregroundWindowHandle = PInvoke.GetForegroundWindow(); + if (foregroundWindowHandle.IsDesktopWindow()) { - var shellFolderView = (Shell32.IShellFolderViewDual2)window.Document; + return GetItemsFromDesktop(foregroundWindowHandle, onlySelectedFiles); + } + else + { + return GetItemsFromFileExplorer(foregroundWindowHandle, onlySelectedFiles); + } + } + + private static IEnumerable GetItemsFromDesktop(HWND foregroundWindowHandle, bool onlySelectedFiles) + { + const int SWC_DESKTOP = 8; + const int SWFO_NEEDDISPATCH = 1; + + var shell = new Shell(); + ShellWindows shellWindows = shell.Windows(); + object? oNull1 = null; + object? oNull2 = null; + var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref oNull1, ref oNull2, SWC_DESKTOP, out int pHWND, SWFO_NEEDDISPATCH); + var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke.SID_STopLevelBrowser, typeof(IShellBrowser).GUID); + var shellView = (IFolderView)shellBrowser.QueryActiveShellView(); + + var selectionFlag = onlySelectedFiles ? (uint)_SVGIO.SVGIO_SELECTION : (uint)_SVGIO.SVGIO_ALLVIEW; + shellView.Items(selectionFlag, typeof(IShellItemArray).GUID, out var items); + if (items is IShellItemArray array) + { + return array.ToFilesEnumerable(); + } + + return new List(); + } + + private static IEnumerable GetItemsFromFileExplorer(HWND foregroundWindowHandle, bool onlySelectedFiles) + { + var activeTab = foregroundWindowHandle.GetActiveTab(); + + var shell = new Shell(); + ShellWindows shellWindows = shell.Windows(); + foreach (IWebBrowserApp webBrowserApp in shell.Windows()) + { + var shellFolderView = (IShellFolderViewDual2)webBrowserApp.Document; var folderTitle = shellFolderView.Folder.Title; - if (window.HWND == (int)foregroundWindowHandle && folderTitle == windowFolderName) + if (webBrowserApp.HWND == foregroundWindowHandle) { - return shellFolderView; + var serviceProvider = (IServiceProvider)webBrowserApp; + var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke.SID_STopLevelBrowser, typeof(IShellBrowser).GUID); + shellBrowser.GetWindow(out IntPtr shellBrowserHandle); + + if (activeTab == shellBrowserHandle) + { + var items = onlySelectedFiles ? shellFolderView.SelectedItems() : shellFolderView.Folder?.Items(); + return items != null ? items.ToFilesEnumerable() : new List(); + } } } - return null; + return new List(); + } + + private static IEnumerable ToFilesEnumerable(this IShellItemArray array) + { + for (var i = 0; i < array.GetCount(); i++) + { + var item = array.GetItemAt(i); + var path = item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH); + yield return new File(path); + } + } + + private static IEnumerable ToFilesEnumerable(this FolderItems folderItems) + { + foreach (FolderItem item in folderItems) + { + yield return new File(item.Path); + } } } } diff --git a/src/modules/peek/Peek.UI/MainWindow.xaml.cs b/src/modules/peek/Peek.UI/MainWindow.xaml.cs index ba91321b83..091173b47c 100644 --- a/src/modules/peek/Peek.UI/MainWindow.xaml.cs +++ b/src/modules/peek/Peek.UI/MainWindow.xaml.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Linq; using interop; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml.Input; @@ -152,19 +153,13 @@ namespace Peek.UI private bool IsNewSingleSelectedItem() { - var folderView = FileExplorerHelper.GetCurrentFolderView(); - if (folderView == null) + var selectedItems = FileExplorerHelper.GetSelectedItems(); + if (!selectedItems.Any() || selectedItems.Skip(1).Any()) { return false; } - Shell32.FolderItems selectedItems = folderView.SelectedItems(); - if (selectedItems.Count > 1) - { - return false; - } - - var fileExplorerSelectedItemPath = selectedItems.Item(0)?.Path; + var fileExplorerSelectedItemPath = selectedItems.First().Path; var currentFilePath = ViewModel.FolderItemsQuery.CurrentFile?.Path; if (fileExplorerSelectedItemPath == null || currentFilePath == null || fileExplorerSelectedItemPath == currentFilePath) { diff --git a/src/modules/peek/Peek.UI/Native/NativeMethods.cs b/src/modules/peek/Peek.UI/Native/NativeMethods.cs index daa15432aa..0bbd79b6f1 100644 --- a/src/modules/peek/Peek.UI/Native/NativeMethods.cs +++ b/src/modules/peek/Peek.UI/Native/NativeMethods.cs @@ -43,13 +43,13 @@ namespace Peek.UI.Native DDETopic, } - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - internal static extern IntPtr GetForegroundWindow(); - [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] internal static extern HResult AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string? pszExtra, [Out] StringBuilder? pszOut, [In][Out] ref uint pcchOut); [DllImport("user32.dll")] internal static extern int GetWindowText(Windows.Win32.Foundation.HWND hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + public static extern int GetClassName(IntPtr hWnd, StringBuilder buf, int nMaxCount); } } diff --git a/src/modules/peek/Peek.UI/NativeMethods.txt b/src/modules/peek/Peek.UI/NativeMethods.txt index 30f37506e8..e60c0fb4ab 100644 --- a/src/modules/peek/Peek.UI/NativeMethods.txt +++ b/src/modules/peek/Peek.UI/NativeMethods.txt @@ -7,4 +7,8 @@ GetCurrentThreadId AttachThreadInput BringWindowToTop ShowWindow -GetWindowTextLength \ No newline at end of file +GetWindowTextLength +FindWindowEx +SID_STopLevelBrowser +GetClassName +_SVGIO \ No newline at end of file