From a8e2927f2ab8b079dfe930cbbaa3e357c3f7d10d Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Thu, 30 May 2024 18:55:35 +0600 Subject: [PATCH 01/30] Integrate native Windows context menu directly into the Explorer plugin --- .../ContextMenu.cs | 19 + .../Helper/ShellContextMenuDisplayHelper.cs | 425 ++++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 2297e5f9633..47521cdabd7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -9,6 +9,7 @@ using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using System.Linq; +using Flow.Launcher.Plugin.Explorer.Helper; using MessageBox = System.Windows.Forms.MessageBox; using MessageBoxIcon = System.Windows.Forms.MessageBoxIcon; using MessageBoxButton = System.Windows.Forms.MessageBoxButtons; @@ -166,6 +167,24 @@ public List LoadContextMenus(Result selectedResult) Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b") }); + if (record.Type is ResultType.File or ResultType.Folder) + { + var menuItems = ShellContextMenuDisplayHelper.GetContextMenuWithIcons(record.FullPath); + foreach (var menuItem in menuItems) + { + contextMenus.Add(new Result + { + Title = $"{menuItem.Label}", + Icon = () => menuItem.Icon, + Action = _ => + { + ShellContextMenuDisplayHelper.ExecuteContextMenuItem(record.FullPath, menuItem.CommandId); + return true; + } + }); + } + } + if (record.Type is ResultType.File or ResultType.Folder) contextMenus.Add(new Result diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs new file mode 100644 index 00000000000..15c7cb6f056 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Flow.Launcher.Plugin.Explorer.Helper; + +public static class ShellContextMenuDisplayHelper +{ + #region DllImport + + [DllImport("shell32.dll")] + private static extern Int32 SHGetMalloc(out IntPtr hObject); + + [DllImport("shell32.dll")] + private static extern Int32 SHParseDisplayName( + [MarshalAs(UnmanagedType.LPWStr)] string pszName, + IntPtr pbc, + out IntPtr ppidl, + UInt32 sfgaoIn, + out UInt32 psfgaoOut + ); + + [DllImport("shell32.dll")] + private static extern Int32 SHBindToParent( + IntPtr pidl, + [MarshalAs(UnmanagedType.LPStruct)] Guid riid, + out IntPtr ppv, + ref IntPtr ppidlLast + ); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern IntPtr CreatePopupMenu(); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool DestroyMenu(IntPtr hMenu); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern uint GetMenuItemCount(IntPtr hMenu); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern uint GetMenuString( + IntPtr hMenu, uint uIDItem, StringBuilder lpString, int nMaxCount, uint uFlag + ); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool GetMenuItemInfo(IntPtr hMenu, uint uItem, bool fByPosition, ref MENUITEMINFO lpmii); + + [DllImport("gdi32.dll")] + private static extern bool DeleteObject(IntPtr hObject); + + #endregion + + #region Constants + + private const uint ContextMenuStartId = 0x0001; + private const uint ContextMenuEndId = 0x7FFF; + + #endregion + + #region Enums + + [Flags] + enum ContextMenuFlags : uint + { + Normal = 0x00000000, + DefaultOnly = 0x00000001, + VerbsOnly = 0x00000002, + Explore = 0x00000004, + NoVerbs = 0x00000008, + CanRename = 0x00000010, + NoDefault = 0x00000020, + IncludeStatic = 0x00000040, + ItemMenu = 0x00000080, + ExtendedVerbs = 0x00000100, + DisabledVerbs = 0x00000200, + AsyncVerbState = 0x00000400, + OptimizeForInvoke = 0x00000800, + SyncCascadeMenu = 0x00001000, + DoNotPickDefault = 0x00002000, + Reserved = 0xffff0000 + } + + [Flags] + enum ContextMenuInvokeCommandFlags : uint + { + Icon = 0x00000010, + Hotkey = 0x00000020, + FlagNoUi = 0x00000400, + Unicode = 0x00004000, + NoConsole = 0x00008000, + AsyncOk = 0x00100000, + NoZoneChecks = 0x00800000, + ShiftDown = 0x10000000, + ControlDown = 0x40000000, + FlagLogUsage = 0x04000000, + PointInvoke = 0x20000000 + } + + [Flags] + enum MenuItemInformationMask : uint + { + Bitmap = 0x00000080, + Checkmarks = 0x00000008, + Data = 0x00000020, + Ftype = 0x00000100, + Id = 0x00000002, + State = 0x00000001, + String = 0x00000040, + Submenu = 0x00000004, + Type = 0x00000010 + } + + enum MenuItemFtype : uint + { + Bitmap = 0x00000004, + MenuBarBreak = 0x00000020, + MenuBreak = 0x00000040, + OwnerDraw = 0x00000100, + RadioCheck = 0x00000200, + RightJustify = 0x00004000, + RightOrder = 0x00002000, + Separator = 0x00000800, + String = 0x00000000, + } + + #endregion + + private static IMalloc GetMalloc() + { + SHGetMalloc(out var pMalloc); + return (IMalloc)Marshal.GetTypedObjectForIUnknown(pMalloc, typeof(IMalloc)); + } + + public static void ExecuteContextMenuItem(string fileName, uint menuItemId) + { + var malloc = GetMalloc(); + var hr = SHParseDisplayName(fileName, IntPtr.Zero, out var pidl, 0, out _); + if (hr != 0) throw new Exception("SHParseDisplayName failed"); + + var originalPidl = pidl; + + var guid = typeof(IShellFolder).GUID; + hr = SHBindToParent(pidl, guid, out var pShellFolder, ref pidl); + if (hr != 0) throw new Exception("SHBindToParent failed"); + + var shellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pShellFolder, typeof(IShellFolder)); + hr = shellFolder.GetUIObjectOf( + IntPtr.Zero, 1, new[] { pidl }, typeof(IContextMenu).GUID, IntPtr.Zero, out var pContextMenu + ); + if (hr != 0) throw new Exception("GetUIObjectOf failed"); + + var contextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(pContextMenu, typeof(IContextMenu)); + + var hMenu = CreatePopupMenu(); + contextMenu.QueryContextMenu(hMenu, 0, ContextMenuStartId, ContextMenuEndId, (uint)ContextMenuFlags.Normal); + + var directory = Path.GetDirectoryName(fileName); + var invokeCommandInfo = new CMINVOKECOMMANDINFO + { + cbSize = (uint)Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO)), + fMask = (uint)ContextMenuInvokeCommandFlags.Unicode, + hwnd = IntPtr.Zero, + lpVerb = (IntPtr)(menuItemId - ContextMenuStartId), + lpParameters = null, + lpDirectory = directory ?? "", + nShow = 1, + hIcon = IntPtr.Zero, + }; + + hr = contextMenu.InvokeCommand(ref invokeCommandInfo); + if (hr != 0) + { + throw new Exception($"InvokeCommand failed with code {hr:X}"); + } + + DestroyMenu(hMenu); + Marshal.ReleaseComObject(contextMenu); + Marshal.ReleaseComObject(shellFolder); + + if (originalPidl != IntPtr.Zero) + malloc.Free(originalPidl); + + Marshal.ReleaseComObject(malloc); + } + + public static List GetContextMenuWithIcons(string filePath) + { + var malloc = GetMalloc(); + var hr = SHParseDisplayName(filePath, IntPtr.Zero, out var pidl, 0, out _); + if (hr != 0) throw new Exception("SHParseDisplayName failed"); + + var originalPidl = pidl; + + var guid = typeof(IShellFolder).GUID; + hr = SHBindToParent(pidl, guid, out var pShellFolder, ref pidl); + if (hr != 0) throw new Exception("SHBindToParent failed"); + + var shellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pShellFolder, typeof(IShellFolder)); + hr = shellFolder.GetUIObjectOf( + IntPtr.Zero, 1, new[] { pidl }, typeof(IContextMenu).GUID, IntPtr.Zero, out var pContextMenu + ); + if (hr != 0) throw new Exception("GetUIObjectOf failed"); + + var contextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(pContextMenu, typeof(IContextMenu)); + + var hMenu = CreatePopupMenu(); + contextMenu.QueryContextMenu(hMenu, 0, ContextMenuStartId, ContextMenuEndId, (uint)ContextMenuFlags.Normal); + + var menuItems = new List(); + ProcessMenuWithIcons(hMenu, menuItems); + + DestroyMenu(hMenu); + Marshal.ReleaseComObject(contextMenu); + Marshal.ReleaseComObject(shellFolder); + + if (originalPidl != IntPtr.Zero) + malloc.Free(originalPidl); + + Marshal.ReleaseComObject(malloc); + + return menuItems; + } + + + private static void ProcessMenuWithIcons(IntPtr hMenu, List menuItems, string prefix = "") + { + uint menuCount = GetMenuItemCount(hMenu); + + for (uint i = 0; i < menuCount; i++) + { + var menuText = new StringBuilder(256); + uint result = GetMenuString(hMenu, i, menuText, menuText.Capacity, 0x400); + + if (result == 0 || string.IsNullOrWhiteSpace(menuText.ToString())) + { + continue; + } + + menuText.Replace("&", ""); + + var mii = new MENUITEMINFO + { + cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO)), + fMask = (uint)(MenuItemInformationMask.Bitmap | MenuItemInformationMask.Ftype | + MenuItemInformationMask.Submenu | MenuItemInformationMask.Id) + }; + + GetMenuItemInfo(hMenu, i, true, ref mii); + + if ((mii.fType & (uint)MenuItemFtype.Separator) != 0) + { + continue; + } + + IntPtr hSubMenu = GetSubMenu(hMenu, (int)i); + if (hSubMenu != IntPtr.Zero) + { + ProcessMenuWithIcons(hSubMenu, menuItems, prefix + menuText + " > "); + } + else if (!string.IsNullOrWhiteSpace(menuText.ToString())) + { + ImageSource icon = null; + if (mii.hbmpItem != IntPtr.Zero) + { + icon = GetBitmapSourceFromHBitmap(mii.hbmpItem); + } + else if (mii.hbmpChecked != IntPtr.Zero) + { + icon = GetBitmapSourceFromHBitmap(mii.hbmpChecked); + } + + menuItems.Add(new ContextMenuItem(prefix + menuText, icon, mii.wID)); + } + } + } + + private static BitmapSource GetBitmapSourceFromHBitmap(IntPtr hBitmap) + { + var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap( + hBitmap, + IntPtr.Zero, + Int32Rect.Empty, + BitmapSizeOptions.FromWidthAndHeight(32, 32) + ); + + if (!DeleteObject(hBitmap)) + { + throw new Exception("Failed to delete HBitmap."); + } + + return bitmapSource; + } +} + +#region Data Structures + +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +[Guid("000214E6-0000-0000-C000-000000000046")] +public interface IShellFolder +{ + [PreserveSig] + int ParseDisplayName( + IntPtr hwnd, IntPtr pbc, [In, MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, out uint pchEaten, + out IntPtr ppidl, ref uint pdwAttributes + ); + + [PreserveSig] + int EnumObjects(IntPtr hwnd, uint grfFlags, out IntPtr ppenumIDList); + + [PreserveSig] + int BindToObject(IntPtr pidl, IntPtr pbc, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); + + [PreserveSig] + int BindToStorage(IntPtr pidl, IntPtr pbc, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); + + [PreserveSig] + int CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2); + + [PreserveSig] + int CreateViewObject(IntPtr hwndOwner, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); + + [PreserveSig] + int GetAttributesOf( + uint cidl, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] apidl, ref uint rgfInOut + ); + + [PreserveSig] + int GetUIObjectOf( + IntPtr hwndOwner, uint cidl, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] apidl, + [In, MarshalAs(UnmanagedType.LPStruct)] + Guid riid, IntPtr rgfReserved, out IntPtr ppv + ); + + [PreserveSig] + int GetDisplayNameOf(IntPtr pidl, uint uFlags, IntPtr pName); + + [PreserveSig] + int SetNameOf( + IntPtr hwnd, IntPtr pidl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszName, uint uFlags, out IntPtr ppidlOut + ); +} + +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +[Guid("00000002-0000-0000-C000-000000000046")] +public interface IMalloc +{ + [PreserveSig] + IntPtr Alloc(UInt32 cb); + + [PreserveSig] + IntPtr Realloc(IntPtr pv, UInt32 cb); + + [PreserveSig] + void Free(IntPtr pv); + + [PreserveSig] + UInt32 GetSize(IntPtr pv); + + [PreserveSig] + Int16 DidAlloc(IntPtr pv); + + [PreserveSig] + void HeapMinimize(); +} + +[StructLayout(LayoutKind.Sequential)] +public struct CMINVOKECOMMANDINFO +{ + public uint cbSize; + public uint fMask; + public IntPtr hwnd; + public IntPtr lpVerb; + [MarshalAs(UnmanagedType.LPStr)] public string lpParameters; + [MarshalAs(UnmanagedType.LPStr)] public string lpDirectory; + public int nShow; + public uint dwHotKey; + public IntPtr hIcon; +} + +[StructLayout(LayoutKind.Sequential)] +public struct MENUITEMINFO +{ + public uint cbSize; + public uint fMask; + public uint fType; + public uint fState; + public uint wID; + public IntPtr hSubMenu; + public IntPtr hbmpChecked; + public IntPtr hbmpUnchecked; + public IntPtr dwItemData; + public IntPtr dwTypeData; + public uint cch; + public IntPtr hbmpItem; +} + +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +[Guid("000214E4-0000-0000-C000-000000000046")] +public interface IContextMenu +{ + [PreserveSig] + int QueryContextMenu(IntPtr hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags); + + [PreserveSig] + int InvokeCommand(ref CMINVOKECOMMANDINFO pici); + + [PreserveSig] + int GetCommandString(uint idcmd, uint uflags, IntPtr reserved, StringBuilder commandstring, int cch); +} + +public record ContextMenuItem(string Label, ImageSource Icon, uint CommandId); + +#endregion From 0969fc12cbe3e1850135273497ef65c33be9038d Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Thu, 30 May 2024 22:44:52 +0600 Subject: [PATCH 02/30] Fix bitmap creation error in Explorer plugin shell integration --- .../Helper/ShellContextMenuDisplayHelper.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs index 15c7cb6f056..19414fc71f1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -285,19 +285,28 @@ private static void ProcessMenuWithIcons(IntPtr hMenu, List men private static BitmapSource GetBitmapSourceFromHBitmap(IntPtr hBitmap) { - var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap( - hBitmap, - IntPtr.Zero, - Int32Rect.Empty, - BitmapSizeOptions.FromWidthAndHeight(32, 32) - ); + try + { + var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap( + hBitmap, + IntPtr.Zero, + Int32Rect.Empty, + BitmapSizeOptions.FromWidthAndHeight(32, 32) + ); + + if (!DeleteObject(hBitmap)) + { + throw new Exception("Failed to delete HBitmap."); + } - if (!DeleteObject(hBitmap)) + return bitmapSource; + } + catch (COMException) { - throw new Exception("Failed to delete HBitmap."); + // ignore } - return bitmapSource; + return null; } } From 0706c5ec8be45878c697f5af8713c7e012efd9a7 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 11:16:30 +0600 Subject: [PATCH 03/30] Explorer plugin: ignore native context menu items that don't work --- .../Helper/ShellContextMenuDisplayHelper.cs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs index 19414fc71f1..fb4c2388d40 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Windows; @@ -64,6 +65,14 @@ private static extern uint GetMenuString( private const uint ContextMenuStartId = 0x0001; private const uint ContextMenuEndId = 0x7FFF; + // We haven't managed to make these work, so we don't display them in the context menu. + private static readonly string[] IgnoredContextMenuOptions = + { + "share", + "Windows.ModernShare", + "PinToStartScreen" + }; + #endregion #region Enums @@ -216,7 +225,7 @@ public static List GetContextMenuWithIcons(string filePath) contextMenu.QueryContextMenu(hMenu, 0, ContextMenuStartId, ContextMenuEndId, (uint)ContextMenuFlags.Normal); var menuItems = new List(); - ProcessMenuWithIcons(hMenu, menuItems); + ProcessMenuWithIcons(hMenu, contextMenu, menuItems); DestroyMenu(hMenu); Marshal.ReleaseComObject(contextMenu); @@ -231,7 +240,7 @@ public static List GetContextMenuWithIcons(string filePath) } - private static void ProcessMenuWithIcons(IntPtr hMenu, List menuItems, string prefix = "") + private static void ProcessMenuWithIcons(IntPtr hMenu, IContextMenu contextMenu, List menuItems, string prefix = "") { uint menuCount = GetMenuItemCount(hMenu); @@ -264,10 +273,23 @@ private static void ProcessMenuWithIcons(IntPtr hMenu, List men IntPtr hSubMenu = GetSubMenu(hMenu, (int)i); if (hSubMenu != IntPtr.Zero) { - ProcessMenuWithIcons(hSubMenu, menuItems, prefix + menuText + " > "); + ProcessMenuWithIcons(hSubMenu, contextMenu, menuItems, prefix + menuText + " > "); } else if (!string.IsNullOrWhiteSpace(menuText.ToString())) { + var commandBuilder = new StringBuilder(256); + contextMenu.GetCommandString( + mii.wID - ContextMenuStartId, + (uint)ContextMenuFlags.Explore, + IntPtr.Zero, + commandBuilder, + commandBuilder.Capacity + ); + if (IgnoredContextMenuOptions.Contains(commandBuilder.ToString(), StringComparer.OrdinalIgnoreCase)) + { + continue; + } + ImageSource icon = null; if (mii.hbmpItem != IntPtr.Zero) { From 2c445b3cbb4e26a42677805de321396342dec9cd Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 12:30:45 +0600 Subject: [PATCH 04/30] Explorer plugin native context menu: remove unnecessary string interpolation --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 47521cdabd7..519ff991b02 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -174,7 +174,7 @@ public List LoadContextMenus(Result selectedResult) { contextMenus.Add(new Result { - Title = $"{menuItem.Label}", + Title = menuItem.Label, Icon = () => menuItem.Icon, Action = _ => { From f49e952e9b5eb95a954e268d9de593d957a8c343 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 12:32:08 +0600 Subject: [PATCH 05/30] Explorer plugin native context menu: add ContextData=native-context-menu-item to differentiate them in markup --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 519ff991b02..95fc9c1d13f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -180,7 +180,8 @@ public List LoadContextMenus(Result selectedResult) { ShellContextMenuDisplayHelper.ExecuteContextMenuItem(record.FullPath, menuItem.CommandId); return true; - } + }, + ContextData = "native-context-menu-item" }); } } From 0c29948b12001217142565e4650b5ea404983f80 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 12:34:07 +0600 Subject: [PATCH 06/30] Revert "Explorer plugin native context menu: add ContextData=native-context-menu-item to differentiate them in markup" This reverts commit f49e952e9b5eb95a954e268d9de593d957a8c343. --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 95fc9c1d13f..519ff991b02 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -180,8 +180,7 @@ public List LoadContextMenus(Result selectedResult) { ShellContextMenuDisplayHelper.ExecuteContextMenuItem(record.FullPath, menuItem.CommandId); return true; - }, - ContextData = "native-context-menu-item" + } }); } } From 0987922cf87ef5a9ec5c3fffa9117b50c44f2944 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 12:38:37 +0600 Subject: [PATCH 07/30] Explorer plugin native context menu: add CopyAsPath to the list of ignored commands --- .../Helper/ShellContextMenuDisplayHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs index fb4c2388d40..b01413285a2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -70,7 +70,8 @@ private static extern uint GetMenuString( { "share", "Windows.ModernShare", - "PinToStartScreen" + "PinToStartScreen", + "CopyAsPath" }; #endregion From 3ee870b249ba85ee48eecd036c3ee896295ece12 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 12:39:26 +0600 Subject: [PATCH 08/30] Explorer plugin native context menu: rename the list of ignored commands to better describe it --- .../Helper/ShellContextMenuDisplayHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs index b01413285a2..860b6b33c8c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -66,7 +66,7 @@ private static extern uint GetMenuString( private const uint ContextMenuEndId = 0x7FFF; // We haven't managed to make these work, so we don't display them in the context menu. - private static readonly string[] IgnoredContextMenuOptions = + private static readonly string[] IgnoredContextMenuCommands = { "share", "Windows.ModernShare", @@ -286,7 +286,7 @@ private static void ProcessMenuWithIcons(IntPtr hMenu, IContextMenu contextMenu, commandBuilder, commandBuilder.Capacity ); - if (IgnoredContextMenuOptions.Contains(commandBuilder.ToString(), StringComparer.OrdinalIgnoreCase)) + if (IgnoredContextMenuCommands.Contains(commandBuilder.ToString(), StringComparer.OrdinalIgnoreCase)) { continue; } From d97382f9fbeb8e969cc7163b1302d7e1373e9e84 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 12:52:24 +0600 Subject: [PATCH 09/30] Explorer plugin native context menu: fix icons being drawn at 32x32 --- Flow.Launcher/ResultListBox.xaml | 1 + .../Helper/ShellContextMenuDisplayHelper.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 38202fcf316..3337b9c93b6 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -108,6 +108,7 @@ RenderOptions.BitmapScalingMode="Fant" Source="{Binding Image, TargetNullValue={x:Null}}" Stretch="Uniform" + StretchDirection="DownOnly" Style="{DynamicResource ImageIconStyle}" Visibility="{Binding ShowIcon}"> diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs index 860b6b33c8c..eedc4498a0e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -314,7 +314,7 @@ private static BitmapSource GetBitmapSourceFromHBitmap(IntPtr hBitmap) hBitmap, IntPtr.Zero, Int32Rect.Empty, - BitmapSizeOptions.FromWidthAndHeight(32, 32) + BitmapSizeOptions.FromWidthAndHeight(16, 16) ); if (!DeleteObject(hBitmap)) From adebabd82401938a42a40371b9a0765dc7132ea4 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 13:33:06 +0600 Subject: [PATCH 10/30] Explorer plugin native context menu: hide duplicate functionality in the native context menu --- .../Helper/ShellContextMenuDisplayHelper.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs index eedc4498a0e..6edc3bd3efa 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -65,13 +65,17 @@ private static extern uint GetMenuString( private const uint ContextMenuStartId = 0x0001; private const uint ContextMenuEndId = 0x7FFF; - // We haven't managed to make these work, so we don't display them in the context menu. private static readonly string[] IgnoredContextMenuCommands = { - "share", + // We haven't managed to make these work, so we don't display them in the context menu. + "Share", "Windows.ModernShare", "PinToStartScreen", - "CopyAsPath" + "CopyAsPath", + + // Hide functionality provided by the Explorer plugin itself + "Copy", + "Delete" }; #endregion From 1747a64d6bea9a8a0ee13e870e03c7ee5892f9e3 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 13:34:48 +0600 Subject: [PATCH 11/30] Explorer plugin native context menu: move native context menu below all other items --- .../ContextMenu.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 519ff991b02..b0d392f1b6e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -167,24 +167,6 @@ public List LoadContextMenus(Result selectedResult) Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b") }); - if (record.Type is ResultType.File or ResultType.Folder) - { - var menuItems = ShellContextMenuDisplayHelper.GetContextMenuWithIcons(record.FullPath); - foreach (var menuItem in menuItems) - { - contextMenus.Add(new Result - { - Title = menuItem.Label, - Icon = () => menuItem.Icon, - Action = _ => - { - ShellContextMenuDisplayHelper.ExecuteContextMenuItem(record.FullPath, menuItem.CommandId); - return true; - } - }); - } - } - if (record.Type is ResultType.File or ResultType.Folder) contextMenus.Add(new Result @@ -297,6 +279,24 @@ public List LoadContextMenus(Result selectedResult) }, IcoPath = Constants.DifferentUserIconImagePath }); + + if (record.Type is ResultType.File or ResultType.Folder) + { + var menuItems = ShellContextMenuDisplayHelper.GetContextMenuWithIcons(record.FullPath); + foreach (var menuItem in menuItems) + { + contextMenus.Add(new Result + { + Title = menuItem.Label, + Icon = () => menuItem.Icon, + Action = _ => + { + ShellContextMenuDisplayHelper.ExecuteContextMenuItem(record.FullPath, menuItem.CommandId); + return true; + } + }); + } + } } return contextMenus; From d07cb1c1664f0eaaa1caa800ad8856c27fe596fb Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 14:30:19 +0600 Subject: [PATCH 12/30] Explorer plugin native context menu: implement settings --- .../ContextMenu.cs | 14 ++++++++++- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 4 +++- .../ViewModels/SettingsViewModel.cs | 24 +++++++++++++++++++ .../Views/ExplorerSettings.xaml | 22 +++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index b0d392f1b6e..fd0e6419ad3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -282,7 +282,19 @@ public List LoadContextMenus(Result selectedResult) if (record.Type is ResultType.File or ResultType.Folder) { - var menuItems = ShellContextMenuDisplayHelper.GetContextMenuWithIcons(record.FullPath); + var filters = Settings + .WindowsContextMenuIgnoredItems + .Replace("\r", "") + .Split("\n") + .Where(v => !string.IsNullOrWhiteSpace(v)) + .ToArray(); + var menuItems = ShellContextMenuDisplayHelper + .GetContextMenuWithIcons(record.FullPath) + .Where(contextMenuItem => + filters.Length == 0 || !filters.Any(filter => + contextMenuItem.Label.Contains(filter, StringComparison.OrdinalIgnoreCase) + ) + ); foreach (var menuItem in menuItems) { contextMenus.Add(new Result diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 088bcf5d60b..81a8332da0f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -30,6 +30,8 @@ public class Settings public bool ShowWindowsContextMenu { get; set; } = true; + public string WindowsContextMenuIgnoredItems { get; set; } = string.Empty; + public bool DefaultOpenFolderInFileManager { get; set; } = false; public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; @@ -62,7 +64,7 @@ public class Settings public bool ShowModifiedDateInPreviewPanel { get; set; } = true; public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd"; - + public string PreviewPanelTimeFormat { get; set; } = "HH:mm"; private EverythingSearchManager _everythingManagerInstance; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 064795b430a..f592629af9d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -102,6 +102,30 @@ private void InitializeEngineSelection() #endregion + #region Native Context Menu + + public bool ShowWindowsContextMenu + { + get => Settings.ShowWindowsContextMenu; + set + { + Settings.ShowWindowsContextMenu = value; + OnPropertyChanged(); + } + } + + public string WindowsContextMenuIgnoredItems + { + get => Settings.WindowsContextMenuIgnoredItems; + set + { + Settings.WindowsContextMenuIgnoredItems = value; + OnPropertyChanged(); + } + } + + #endregion + #region Preview Panel public bool ShowFileSizeInPreviewPanel diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index 5c92fc271d8..0a51598d5e3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -348,6 +348,28 @@ + + + + + + + + + Date: Fri, 7 Jun 2024 14:35:15 +0600 Subject: [PATCH 13/30] Explorer plugin native context menu: only show it when it's enabled in settings --- Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index fd0e6419ad3..9cf0a185781 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -280,7 +280,7 @@ public List LoadContextMenus(Result selectedResult) IcoPath = Constants.DifferentUserIconImagePath }); - if (record.Type is ResultType.File or ResultType.Folder) + if (record.Type is ResultType.File or ResultType.Folder && Settings.ShowWindowsContextMenu) { var filters = Settings .WindowsContextMenuIgnoredItems From ee64c259fcbb07d27abc5daf597dca8883efdb9e Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Fri, 7 Jun 2024 21:29:04 +0600 Subject: [PATCH 14/30] Explorer plugin native context menu: free all allocated unmanaged memory --- .../Helper/ShellContextMenuDisplayHelper.cs | 174 +++++++++++------- 1 file changed, 112 insertions(+), 62 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs index 6edc3bd3efa..6becb7793a4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenuDisplayHelper.cs @@ -156,92 +156,142 @@ private static IMalloc GetMalloc() public static void ExecuteContextMenuItem(string fileName, uint menuItemId) { - var malloc = GetMalloc(); - var hr = SHParseDisplayName(fileName, IntPtr.Zero, out var pidl, 0, out _); - if (hr != 0) throw new Exception("SHParseDisplayName failed"); + IMalloc malloc = null; + IntPtr originalPidl = IntPtr.Zero; + IntPtr pShellFolder = IntPtr.Zero; + IntPtr pContextMenu = IntPtr.Zero; + IntPtr hMenu = IntPtr.Zero; + IContextMenu contextMenu = null; + IShellFolder shellFolder = null; - var originalPidl = pidl; + try + { + malloc = GetMalloc(); + var hr = SHParseDisplayName(fileName, IntPtr.Zero, out var pidl, 0, out _); + if (hr != 0) throw new Exception("SHParseDisplayName failed"); - var guid = typeof(IShellFolder).GUID; - hr = SHBindToParent(pidl, guid, out var pShellFolder, ref pidl); - if (hr != 0) throw new Exception("SHBindToParent failed"); + originalPidl = pidl; - var shellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pShellFolder, typeof(IShellFolder)); - hr = shellFolder.GetUIObjectOf( - IntPtr.Zero, 1, new[] { pidl }, typeof(IContextMenu).GUID, IntPtr.Zero, out var pContextMenu - ); - if (hr != 0) throw new Exception("GetUIObjectOf failed"); + var guid = typeof(IShellFolder).GUID; + hr = SHBindToParent(pidl, guid, out pShellFolder, ref pidl); + if (hr != 0) throw new Exception("SHBindToParent failed"); - var contextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(pContextMenu, typeof(IContextMenu)); + shellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pShellFolder, typeof(IShellFolder)); + hr = shellFolder.GetUIObjectOf( + IntPtr.Zero, 1, new[] { pidl }, typeof(IContextMenu).GUID, IntPtr.Zero, out pContextMenu + ); + if (hr != 0) throw new Exception("GetUIObjectOf failed"); - var hMenu = CreatePopupMenu(); - contextMenu.QueryContextMenu(hMenu, 0, ContextMenuStartId, ContextMenuEndId, (uint)ContextMenuFlags.Normal); + contextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(pContextMenu, typeof(IContextMenu)); - var directory = Path.GetDirectoryName(fileName); - var invokeCommandInfo = new CMINVOKECOMMANDINFO - { - cbSize = (uint)Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO)), - fMask = (uint)ContextMenuInvokeCommandFlags.Unicode, - hwnd = IntPtr.Zero, - lpVerb = (IntPtr)(menuItemId - ContextMenuStartId), - lpParameters = null, - lpDirectory = directory ?? "", - nShow = 1, - hIcon = IntPtr.Zero, - }; - - hr = contextMenu.InvokeCommand(ref invokeCommandInfo); - if (hr != 0) - { - throw new Exception($"InvokeCommand failed with code {hr:X}"); + hMenu = CreatePopupMenu(); + contextMenu.QueryContextMenu(hMenu, 0, ContextMenuStartId, ContextMenuEndId, (uint)ContextMenuFlags.Normal); + + var directory = Path.GetDirectoryName(fileName); + var invokeCommandInfo = new CMINVOKECOMMANDINFO + { + cbSize = (uint)Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO)), + fMask = (uint)ContextMenuInvokeCommandFlags.Unicode, + hwnd = IntPtr.Zero, + lpVerb = (IntPtr)(menuItemId - ContextMenuStartId), + lpParameters = null, + lpDirectory = directory ?? "", + nShow = 1, + hIcon = IntPtr.Zero, + }; + + hr = contextMenu.InvokeCommand(ref invokeCommandInfo); + if (hr != 0) + { + throw new Exception($"InvokeCommand failed with code {hr:X}"); + } } + finally + { + if (hMenu != IntPtr.Zero) + DestroyMenu(hMenu); + + if (contextMenu != null) + Marshal.ReleaseComObject(contextMenu); - DestroyMenu(hMenu); - Marshal.ReleaseComObject(contextMenu); - Marshal.ReleaseComObject(shellFolder); + if (pContextMenu != IntPtr.Zero) + Marshal.Release(pContextMenu); - if (originalPidl != IntPtr.Zero) - malloc.Free(originalPidl); + if (shellFolder != null) + Marshal.ReleaseComObject(shellFolder); - Marshal.ReleaseComObject(malloc); + if (pShellFolder != IntPtr.Zero) + Marshal.Release(pShellFolder); + + if (originalPidl != IntPtr.Zero) + malloc?.Free(originalPidl); + + if (malloc != null) + Marshal.ReleaseComObject(malloc); + } } public static List GetContextMenuWithIcons(string filePath) { - var malloc = GetMalloc(); - var hr = SHParseDisplayName(filePath, IntPtr.Zero, out var pidl, 0, out _); - if (hr != 0) throw new Exception("SHParseDisplayName failed"); + IMalloc malloc = null; + IntPtr originalPidl = IntPtr.Zero; + IntPtr pShellFolder = IntPtr.Zero; + IShellFolder shellFolder = null; + IntPtr pContextMenu = IntPtr.Zero; + IContextMenu contextMenu = null; + IntPtr hMenu = IntPtr.Zero; - var originalPidl = pidl; + try + { + malloc = GetMalloc(); + var hr = SHParseDisplayName(filePath, IntPtr.Zero, out var pidl, 0, out _); + if (hr != 0) throw new Exception("SHParseDisplayName failed"); - var guid = typeof(IShellFolder).GUID; - hr = SHBindToParent(pidl, guid, out var pShellFolder, ref pidl); - if (hr != 0) throw new Exception("SHBindToParent failed"); + originalPidl = pidl; - var shellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pShellFolder, typeof(IShellFolder)); - hr = shellFolder.GetUIObjectOf( - IntPtr.Zero, 1, new[] { pidl }, typeof(IContextMenu).GUID, IntPtr.Zero, out var pContextMenu - ); - if (hr != 0) throw new Exception("GetUIObjectOf failed"); + var guid = typeof(IShellFolder).GUID; + hr = SHBindToParent(pidl, guid, out pShellFolder, ref pidl); + if (hr != 0) throw new Exception("SHBindToParent failed"); - var contextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(pContextMenu, typeof(IContextMenu)); + shellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pShellFolder, typeof(IShellFolder)); + hr = shellFolder.GetUIObjectOf( + IntPtr.Zero, 1, new[] { pidl }, typeof(IContextMenu).GUID, IntPtr.Zero, out pContextMenu + ); + if (hr != 0) throw new Exception("GetUIObjectOf failed"); - var hMenu = CreatePopupMenu(); - contextMenu.QueryContextMenu(hMenu, 0, ContextMenuStartId, ContextMenuEndId, (uint)ContextMenuFlags.Normal); + contextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(pContextMenu, typeof(IContextMenu)); - var menuItems = new List(); - ProcessMenuWithIcons(hMenu, contextMenu, menuItems); + hMenu = CreatePopupMenu(); + contextMenu.QueryContextMenu(hMenu, 0, ContextMenuStartId, ContextMenuEndId, (uint)ContextMenuFlags.Normal); - DestroyMenu(hMenu); - Marshal.ReleaseComObject(contextMenu); - Marshal.ReleaseComObject(shellFolder); + var menuItems = new List(); + ProcessMenuWithIcons(hMenu, contextMenu, menuItems); - if (originalPidl != IntPtr.Zero) - malloc.Free(originalPidl); + return menuItems; + } + finally + { + if (hMenu != IntPtr.Zero) + DestroyMenu(hMenu); + + if (contextMenu != null) + Marshal.ReleaseComObject(contextMenu); + + if (pContextMenu != IntPtr.Zero) + Marshal.Release(pContextMenu); - Marshal.ReleaseComObject(malloc); + if (shellFolder != null) + Marshal.ReleaseComObject(shellFolder); - return menuItems; + if (pShellFolder != IntPtr.Zero) + Marshal.Release(pShellFolder); + + if (originalPidl != IntPtr.Zero) + malloc?.Free(originalPidl); + + if (malloc != null) + Marshal.ReleaseComObject(malloc); + } } From 4c813301a75b0fa5d9d00ae9d08f4fcffb78095b Mon Sep 17 00:00:00 2001 From: DB p Date: Sat, 8 Jun 2024 04:38:06 +0900 Subject: [PATCH 15/30] Add Type and Auto Size for context menu --- Flow.Launcher.Plugin/Result.cs | 6 +++ Flow.Launcher/ResultListBox.xaml | 42 ++++++++++++++----- Flow.Launcher/ViewModel/ResultViewModel.cs | 5 +++ .../ContextMenu.cs | 1 + 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index ea79386b3dd..5a84d293762 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -246,6 +246,12 @@ public ValueTask ExecuteAsync(ActionContext context) return AsyncAction?.Invoke(context) ?? ValueTask.FromResult(Action?.Invoke(context) ?? false); } + /// + /// Item Height Style. Null, Small(for OS Context), Author + /// + public string ItemType { get; set; } + + /// /// Progress bar display. Providing an int value between 0-100 will trigger the progress bar to be displayed on the result /// diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 3337b9c93b6..340d67e8298 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -62,13 +62,13 @@ @@ -97,12 +97,12 @@ @@ -173,7 +173,7 @@ + \ No newline at end of file diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 5130e7ebafc..f29c718f51e 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -180,6 +180,11 @@ public ImageSource PreviewImage /// public bool UseBigThumbnail => Result.Preview.IsMedia; + public double SmallSize + { + get { return Settings.ItemHeightSize * 0.6; } + } + public GlyphInfo Glyph { get; set; } private async Task LoadImageInternalAsync(string imagePath, Result.IconDelegate icon, bool loadFullImage) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 9cf0a185781..6a3cdf332d0 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -301,6 +301,7 @@ public List LoadContextMenus(Result selectedResult) { Title = menuItem.Label, Icon = () => menuItem.Icon, + ItemType = "Small", Action = _ => { ShellContextMenuDisplayHelper.ExecuteContextMenuItem(record.FullPath, menuItem.CommandId); From fb9515b534aef0b86e296365039da162f36b805b Mon Sep 17 00:00:00 2001 From: DB p Date: Sat, 8 Jun 2024 04:53:43 +0900 Subject: [PATCH 16/30] Add Strings in setting --- .../Languages/en.xaml | 4 + .../Views/ExplorerSettings.xaml | 95 ++++++++++--------- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index cd4a1dc8492..72a0b13aec7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -152,4 +152,8 @@ Do you want to enable content search for Everything? It can be very slow without index (which is only supported in Everything v1.5+) + + Native Context Menu + Display native context menu (experimental) + Below you can specify items you want to exclude from context menu, they can be partial (e.g. 'pen wit') or complete ('Open with') diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index 0a51598d5e3..7ed07d87c01 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -22,7 +22,7 @@ @@ -105,13 +105,13 @@ - + @@ -128,7 +128,7 @@ @@ -160,20 +160,20 @@ Width="Auto" Header="{DynamicResource plugin_explorer_generalsetting_header}" Style="{DynamicResource ExplorerTabItem}"> - + - + @@ -188,7 +188,7 @@