[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
This commit is contained in:
Samuel Chapleau 2023-01-24 11:02:01 -08:00 committed by GitHub
parent 7105802831
commit 4fe3e27fa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 972 additions and 768 deletions

View File

@ -126,7 +126,7 @@
<?define FileLocksmithAssetsFiles=Icon.ico?>
<?define PeekFiles=CommunityToolkit.Mvvm.dll;Microsoft.InteractiveExperiences.Projection.dll;WinRT.Runtime.dll;Ijwhost.dll;Microsoft.Win32.SystemEvents.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;Powertoys.Interop.dll;Peek.Common.dll;Peek.FilePreviewer.dll;Powertoys.Peek.UI.dll;Powertoys.Peek.UI.exe;Powertoys.Peek.UI.deps.json;Powertoys.Peek.UI.runtimeconfig.json;resources.pri;System.CodeDom.dll;System.Drawing.Common.dll;System.Management.dll;WIC.dll;WinUIEx.dll;System.IO.Abstractions.dll;System.Text.Json.dll;WindowsBase.dll;PowerToys.Settings.UI.Lib.dll;Microsoft.Graphics.Display.dll;Microsoft.Windows.AppNotifications.Builder.Projection.dll;Microsoft.Windows.Security.AccessControl.Projection.dll;Microsoft.Windows.Widgets.dll;Microsoft.Windows.Widgets.Projection.dll?>
<?define PeekFiles=CommunityToolkit.Mvvm.dll;Microsoft.InteractiveExperiences.Projection.dll;WinRT.Runtime.dll;Ijwhost.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;Powertoys.Interop.dll;Peek.Common.dll;Peek.FilePreviewer.dll;Powertoys.Peek.UI.dll;Powertoys.Peek.UI.exe;Powertoys.Peek.UI.deps.json;Powertoys.Peek.UI.runtimeconfig.json;resources.pri;System.CodeDom.dll;System.Drawing.Common.dll;System.Management.dll;WIC.dll;WinUIEx.dll;System.IO.Abstractions.dll;PowerToys.Settings.UI.Lib.dll?>
<?define PeekAssetsFiles=Icon.ico?>
@ -148,6 +148,8 @@
<?define HostsMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
<?define PeekMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
<?define PowerAccentFiles=ControlzEx.dll;Ijwhost.dll;Microsoft.Xaml.Behaviors.dll;PowerAccent.Core.dll;PowerToys.PowerAccentModuleInterface.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.PowerAccent.deps.json;PowerToys.PowerAccent.dll;PowerToys.PowerAccent.exe;PowerToys.PowerAccent.runtimeconfig.json;PowerToys.Common.UI.dll;PowerToys.Settings.UI.Lib.dll;System.IO.Abstractions.dll;Vanara.Core.dll;Vanara.PInvoke.ComCtl32.dll;Vanara.PInvoke.Cryptography.dll;Vanara.PInvoke.Gdi32.dll;Vanara.PInvoke.Kernel32.dll;Vanara.PInvoke.Ole.dll;Vanara.PInvoke.Rpc.dll;Vanara.PInvoke.Security.dll;Vanara.PInvoke.Shared.dll;Vanara.PInvoke.Shell32.dll;Vanara.PInvoke.ShlwApi.dll;Vanara.PInvoke.User32.dll;PowerToys.PowerAccentKeyboardService.dll;Microsoft.Windows.SDK.NET.dll;WinRT.Runtime.dll;PowerToys.GPOWrapper.dll;UnicodeInformation.dll;ModernWpf.dll;ModernWpf.Controls.dll?>
<?define DotnetRuntimeFiles= ?>

View File

@ -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
{
/// <summary>
/// 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).
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param>
/// <returns>The uint value</returns>
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;
}
}
/// <summary>
/// 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).
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">the pkey</param>
/// <returns>the ulong value</returns>
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;
}
}
/// <summary>
/// Helper method that retrieves a string value from the given property store.
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param>
/// <returns>The string value</returns>
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;
}
}
/// <summary>
/// Helper method that retrieves an array of string values from the given property store.
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param>
/// <returns>The array of string values</returns>
public static string[] GetStringArray(this IPropertyStore propertyStore, PropertyKey key)
{
PropVariant propVar;
propertyStore.GetValue(ref key, out propVar);
List<string> values = new List<string>();
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();
}
}
}

View File

@ -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
{
/// <summary>
/// Gets a IPropertyStore interface from the given path.
/// </summary>
/// <param name="path">The file/folder path</param>
/// <param name="flags">The property store flags</param>
/// <returns>an IPropertyStroe interface</returns>
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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
}
}

View File

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

View File

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

View File

@ -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
{
}
}

View File

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

View File

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

View File

@ -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
{
/// <summary>
/// Gets the path to the given known folder.
/// </summary>
/// <param name="knownFolderId">Guid for given known folder</param>
/// <returns>The path to the known folder.</returns>
public static string GetKnownFolderPath(Guid knownFolderId)
{
string path;
int hResult = SHGetKnownFolderPath(knownFolderId, 0, IntPtr.Zero, out path);
Marshal.ThrowExceptionForHR(hResult);
return path;
}
/// <summary>
/// Gets a IPropertyStore interface from the given path.
/// </summary>
/// <param name="path">The file/folder path</param>
/// <param name="flags">The property store flags</param>
/// <returns>an IPropertyStroe interface</returns>
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);
}
}
}
/// <summary>
/// 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).
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param>
/// <returns>The uint value</returns>
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;
}
}
/// <summary>
/// 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).
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">the pkey</param>
/// <returns>the ulong value</returns>
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;
}
}
/// <summary>
/// Helper method that retrieves a string value from the given property store.
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param>
/// <returns>The string value</returns>
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;
}
}
/// <summary>
/// Helper method that retrieves an array of string values from the given property store.
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param>
/// <returns>The array of string values</returns>
public static string[] GetStringArrayFromPropertyStore(IPropertyStore propertyStore, PropertyKey key)
{
PropVariant propVar;
propertyStore.GetValue(ref key, out propVar);
List<string> values = new List<string>();
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);
/// <summary>
/// Extracts a specified string from a specified resource via an indirect string
/// </summary>
/// <param name="pszSource">An indirect string representing the desired string from a specified resource file</param>
/// <param name="pszOutBuf">An output string, which receives the native function's outputted resource string</param>
/// <param name="cchOutBuf">The buffer size to hold the output string, in characters</param>
/// <param name="ppvReserved">A reserved pointer (void**)</param>
/// <returns>Returns an HRESULT representing the success/failure of the native function</returns>
[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);
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": true
}

View File

@ -0,0 +1,5 @@
GETPROPERTYSTOREFLAGS
_SHCONTF
SIGDN
SHGDNF
SIATTRIBFLAGS

View File

@ -10,6 +10,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />
</ItemGroup>

View File

@ -27,8 +27,8 @@
<controls:BrowserControl
x:Name="BrowserPreview"
x:Load="True"
NavigationCompleted="PreviewBrowser_NavigationCompleted"
DOMContentLoaded="BrowserPreview_DOMContentLoaded"
NavigationCompleted="PreviewBrowser_NavigationCompleted"
Source="{x:Bind BrowserPreviewer.Preview, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(BrowserPreviewer, Previewer.State), Mode=OneWay, FallbackValue=Collapsed}" />

View File

@ -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<ulong> 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<string> 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);
}

View File

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

View File

@ -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<File> items,
File firstSelectedItem,
CancellationToken cancellationToken)
{
var tempFiles = new List<File>(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;
});
}
}

View File

@ -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<File> GetSelectedItems()
{
var folderView = GetCurrentFolderView();
if (folderView == null)
{
return null;
return GetItemsInternal(onlySelectedFiles: true);
}
Shell32.FolderItems selectedItems = folderView.SelectedItems();
if (selectedItems == null || selectedItems.Count == 0)
public static IEnumerable<File> GetItems()
{
return null;
return GetItemsInternal(onlySelectedFiles: false);
}
return selectedItems;
private static IEnumerable<File> GetItemsInternal(bool onlySelectedFiles)
{
var foregroundWindowHandle = PInvoke.GetForegroundWindow();
if (foregroundWindowHandle.IsDesktopWindow())
{
return GetItemsFromDesktop(foregroundWindowHandle, onlySelectedFiles);
}
else
{
return GetItemsFromFileExplorer(foregroundWindowHandle, onlySelectedFiles);
}
}
public static Shell32.IShellFolderViewDual2? GetCurrentFolderView()
private static IEnumerable<File> GetItemsFromDesktop(HWND foregroundWindowHandle, bool onlySelectedFiles)
{
var foregroundWindowHandle = NativeMethods.GetForegroundWindow();
const int SWC_DESKTOP = 8;
const int SWFO_NEEDDISPATCH = 1;
int capacity = PInvoke.GetWindowTextLength(new HWND(foregroundWindowHandle)) * 2;
StringBuilder foregroundWindowTitleBuffer = new StringBuilder(capacity);
NativeMethods.GetWindowText(new HWND(foregroundWindowHandle), foregroundWindowTitleBuffer, foregroundWindowTitleBuffer.Capacity);
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();
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())
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)
{
var shellFolderView = (Shell32.IShellFolderViewDual2)window.Document;
return array.ToFilesEnumerable();
}
return new List<File>();
}
private static IEnumerable<File> 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<File>();
}
}
}
return null;
return new List<File>();
}
private static IEnumerable<File> 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<File> ToFilesEnumerable(this FolderItems folderItems)
{
foreach (FolderItem item in folderItems)
{
yield return new File(item.Path);
}
}
}
}

View File

@ -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)
{

View File

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

View File

@ -8,3 +8,7 @@ AttachThreadInput
BringWindowToTop
ShowWindow
GetWindowTextLength
FindWindowEx
SID_STopLevelBrowser
GetClassName
_SVGIO