Skip to content

Commit dd12008

Browse files
authored
Implemented shell file operations (#5342)
1 parent 79f00b8 commit dd12008

35 files changed

+2999
-1790
lines changed

Files.Launcher/ContextMenu.cs

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
using Files.Common;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Drawing;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Runtime.InteropServices;
9+
using Vanara.InteropServices;
10+
using Vanara.PInvoke;
11+
using Vanara.Windows.Shell;
12+
13+
namespace FilesFullTrust
14+
{
15+
public class ContextMenu : Win32ContextMenu, IDisposable
16+
{
17+
private Shell32.IContextMenu cMenu;
18+
private User32.SafeHMENU hMenu;
19+
public List<string> ItemsPath { get; }
20+
21+
public ContextMenu(Shell32.IContextMenu cMenu, User32.SafeHMENU hMenu, IEnumerable<string> itemsPath)
22+
{
23+
this.cMenu = cMenu;
24+
this.hMenu = hMenu;
25+
this.ItemsPath = itemsPath.ToList();
26+
this.Items = new List<Win32ContextMenuItem>();
27+
}
28+
29+
public bool InvokeVerb(string verb)
30+
{
31+
if (string.IsNullOrEmpty(verb))
32+
{
33+
return false;
34+
}
35+
36+
try
37+
{
38+
var currentWindows = Win32API.GetDesktopWindows();
39+
var pici = new Shell32.CMINVOKECOMMANDINFOEX();
40+
pici.lpVerb = new SafeResourceId(verb, CharSet.Ansi);
41+
pici.nShow = ShowWindowCommand.SW_SHOWNORMAL;
42+
pici.cbSize = (uint)Marshal.SizeOf(pici);
43+
cMenu.InvokeCommand(pici);
44+
Win32API.BringToForeground(currentWindows);
45+
return true;
46+
}
47+
catch (Exception ex) when (
48+
ex is COMException
49+
|| ex is UnauthorizedAccessException)
50+
{
51+
Debug.WriteLine(ex);
52+
}
53+
return false;
54+
}
55+
56+
public void InvokeItem(int itemID)
57+
{
58+
if (itemID < 0)
59+
{
60+
return;
61+
}
62+
63+
try
64+
{
65+
var currentWindows = Win32API.GetDesktopWindows();
66+
var pici = new Shell32.CMINVOKECOMMANDINFOEX();
67+
pici.lpVerb = Macros.MAKEINTRESOURCE(itemID);
68+
pici.nShow = ShowWindowCommand.SW_SHOWNORMAL;
69+
pici.cbSize = (uint)Marshal.SizeOf(pici);
70+
cMenu.InvokeCommand(pici);
71+
Win32API.BringToForeground(currentWindows);
72+
}
73+
catch (Exception ex) when (
74+
ex is COMException
75+
|| ex is UnauthorizedAccessException)
76+
{
77+
Debug.WriteLine(ex);
78+
}
79+
}
80+
81+
#region FactoryMethods
82+
83+
public static ContextMenu GetContextMenuForFiles(string[] filePathList, Shell32.CMF flags, Func<string, bool> itemFilter = null)
84+
{
85+
List<ShellItem> shellItems = new List<ShellItem>();
86+
try
87+
{
88+
foreach (var fp in filePathList.Where(x => !string.IsNullOrEmpty(x)))
89+
{
90+
shellItems.Add(new ShellItem(fp));
91+
}
92+
93+
return GetContextMenuForFiles(shellItems.ToArray(), flags, itemFilter);
94+
}
95+
catch (Exception ex) when (ex is ArgumentException || ex is FileNotFoundException)
96+
{
97+
// Return empty context menu
98+
return null;
99+
}
100+
finally
101+
{
102+
foreach (var si in shellItems)
103+
{
104+
si.Dispose();
105+
}
106+
}
107+
}
108+
109+
private static ContextMenu GetContextMenuForFiles(ShellItem[] shellItems, Shell32.CMF flags, Func<string, bool> itemFilter = null)
110+
{
111+
if (shellItems == null || !shellItems.Any())
112+
{
113+
return null;
114+
}
115+
116+
using var sf = shellItems.First().Parent; // HP: the items are all in the same folder
117+
Shell32.IContextMenu menu = sf.GetChildrenUIObjects<Shell32.IContextMenu>(null, shellItems);
118+
var hMenu = User32.CreatePopupMenu();
119+
menu.QueryContextMenu(hMenu, 0, 1, 0x7FFF, flags);
120+
var contextMenu = new ContextMenu(menu, hMenu, shellItems.Select(x => x.ParsingName));
121+
ContextMenu.EnumMenuItems(menu, hMenu, contextMenu.Items, itemFilter);
122+
return contextMenu;
123+
}
124+
125+
#endregion FactoryMethods
126+
127+
private static void EnumMenuItems(
128+
Shell32.IContextMenu cMenu,
129+
HMENU hMenu,
130+
List<Win32ContextMenuItem> menuItemsResult,
131+
Func<string, bool> itemFilter = null)
132+
{
133+
var itemCount = User32.GetMenuItemCount(hMenu);
134+
var mii = new User32.MENUITEMINFO();
135+
mii.cbSize = (uint)Marshal.SizeOf(mii);
136+
mii.fMask = User32.MenuItemInfoMask.MIIM_BITMAP
137+
| User32.MenuItemInfoMask.MIIM_FTYPE
138+
| User32.MenuItemInfoMask.MIIM_STRING
139+
| User32.MenuItemInfoMask.MIIM_ID
140+
| User32.MenuItemInfoMask.MIIM_SUBMENU;
141+
for (uint ii = 0; ii < itemCount; ii++)
142+
{
143+
var menuItem = new ContextMenuItem();
144+
var container = new SafeCoTaskMemString(512);
145+
mii.dwTypeData = (IntPtr)container;
146+
mii.cch = (uint)container.Capacity - 1; // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723
147+
var retval = User32.GetMenuItemInfo(hMenu, ii, true, ref mii);
148+
if (!retval)
149+
{
150+
container.Dispose();
151+
continue;
152+
}
153+
menuItem.Type = (MenuItemType)mii.fType;
154+
menuItem.ID = (int)(mii.wID - 1); // wID - idCmdFirst
155+
if (menuItem.Type == MenuItemType.MFT_STRING)
156+
{
157+
Debug.WriteLine("Item {0} ({1}): {2}", ii, mii.wID, mii.dwTypeData);
158+
menuItem.Label = mii.dwTypeData;
159+
menuItem.CommandString = GetCommandString(cMenu, mii.wID - 1);
160+
if (itemFilter != null && (itemFilter(menuItem.CommandString) || itemFilter(menuItem.Label)))
161+
{
162+
// Skip items implemented in UWP
163+
container.Dispose();
164+
continue;
165+
}
166+
if (mii.hbmpItem != HBITMAP.NULL && !Enum.IsDefined(typeof(HBITMAP_HMENU), ((IntPtr)mii.hbmpItem).ToInt64()))
167+
{
168+
using var bitmap = Win32API.GetBitmapFromHBitmap(mii.hbmpItem);
169+
if (bitmap != null)
170+
{
171+
byte[] bitmapData = (byte[])new ImageConverter().ConvertTo(bitmap, typeof(byte[]));
172+
menuItem.IconBase64 = Convert.ToBase64String(bitmapData, 0, bitmapData.Length);
173+
}
174+
}
175+
if (mii.hSubMenu != HMENU.NULL)
176+
{
177+
Debug.WriteLine("Item {0}: has submenu", ii);
178+
var subItems = new List<Win32ContextMenuItem>();
179+
try
180+
{
181+
(cMenu as Shell32.IContextMenu2)?.HandleMenuMsg((uint)User32.WindowMessage.WM_INITMENUPOPUP, (IntPtr)mii.hSubMenu, new IntPtr(ii));
182+
}
183+
catch (NotImplementedException)
184+
{
185+
// Only for dynamic/owner drawn? (open with, etc)
186+
}
187+
EnumMenuItems(cMenu, mii.hSubMenu, subItems, itemFilter);
188+
menuItem.SubItems = subItems;
189+
Debug.WriteLine("Item {0}: done submenu", ii);
190+
}
191+
}
192+
else
193+
{
194+
Debug.WriteLine("Item {0}: {1}", ii, mii.fType.ToString());
195+
}
196+
container.Dispose();
197+
menuItemsResult.Add(menuItem);
198+
}
199+
}
200+
201+
private static string GetCommandString(Shell32.IContextMenu cMenu, uint offset, Shell32.GCS flags = Shell32.GCS.GCS_VERBW)
202+
{
203+
if (offset > 5000)
204+
{
205+
// Hackish workaround to avoid an AccessViolationException on some items,
206+
// notably the "Run with graphic processor" menu item of NVidia cards
207+
return null;
208+
}
209+
SafeCoTaskMemString commandString = null;
210+
try
211+
{
212+
commandString = new SafeCoTaskMemString(512);
213+
cMenu.GetCommandString(new IntPtr(offset), flags, IntPtr.Zero, commandString, (uint)commandString.Capacity - 1);
214+
Debug.WriteLine("Verb {0}: {1}", offset, commandString);
215+
return commandString.ToString();
216+
}
217+
catch (Exception ex) when (ex is InvalidCastException || ex is ArgumentException)
218+
{
219+
// TODO: investigate this..
220+
Debug.WriteLine(ex);
221+
return null;
222+
}
223+
catch (Exception ex) when (ex is COMException || ex is NotImplementedException)
224+
{
225+
// Not every item has an associated verb
226+
return null;
227+
}
228+
finally
229+
{
230+
commandString?.Dispose();
231+
}
232+
}
233+
234+
#region IDisposable Support
235+
236+
private bool disposedValue = false; // To detect redundant calls
237+
238+
protected virtual void Dispose(bool disposing)
239+
{
240+
if (!disposedValue)
241+
{
242+
if (disposing)
243+
{
244+
// TODO: dispose managed state (managed objects).
245+
if (Items != null)
246+
{
247+
foreach (var si in Items)
248+
{
249+
(si as IDisposable)?.Dispose();
250+
}
251+
252+
Items = null;
253+
}
254+
}
255+
256+
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
257+
if (hMenu != null)
258+
{
259+
User32.DestroyMenu(hMenu);
260+
hMenu = null;
261+
}
262+
if (cMenu != null)
263+
{
264+
Marshal.ReleaseComObject(cMenu);
265+
cMenu = null;
266+
}
267+
268+
disposedValue = true;
269+
}
270+
}
271+
272+
~ContextMenu()
273+
{
274+
Dispose(false);
275+
}
276+
277+
public void Dispose()
278+
{
279+
Dispose(true);
280+
GC.SuppressFinalize(this);
281+
}
282+
283+
#endregion IDisposable Support
284+
285+
public enum HBITMAP_HMENU : long
286+
{
287+
HBMMENU_CALLBACK = -1,
288+
HBMMENU_MBAR_CLOSE = 5,
289+
HBMMENU_MBAR_CLOSE_D = 6,
290+
HBMMENU_MBAR_MINIMIZE = 3,
291+
HBMMENU_MBAR_MINIMIZE_D = 7,
292+
HBMMENU_MBAR_RESTORE = 2,
293+
HBMMENU_POPUP_CLOSE = 8,
294+
HBMMENU_POPUP_MAXIMIZE = 10,
295+
HBMMENU_POPUP_MINIMIZE = 11,
296+
HBMMENU_POPUP_RESTORE = 9,
297+
HBMMENU_SYSTEM = 1
298+
}
299+
}
300+
301+
public class ContextMenuItem : Win32ContextMenuItem, IDisposable
302+
{
303+
public ContextMenuItem()
304+
{
305+
this.SubItems = new List<Win32ContextMenuItem>();
306+
}
307+
308+
public void Dispose()
309+
{
310+
if (SubItems != null)
311+
{
312+
foreach (var si in SubItems)
313+
{
314+
(si as IDisposable)?.Dispose();
315+
}
316+
317+
SubItems = null;
318+
}
319+
}
320+
}
321+
}

Files.Launcher/FilePermissions.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,49 @@ private IEnumerable<string> GetGroupsForUser(string sid)
248248
return null;
249249
}
250250
}
251+
252+
public bool HasPermission(FileSystemRights perm)
253+
{
254+
return GetEffectiveRights().HasFlag(perm);
255+
}
256+
257+
public FileSystemRights GetEffectiveRights()
258+
{
259+
using var user = WindowsIdentity.GetCurrent();
260+
var userSids = new List<string> { user.User.Value };
261+
userSids.AddRange(user.Groups.Select(x => x.Value));
262+
263+
FileSystemRights inheritedDenyRights = 0, denyRights = 0;
264+
FileSystemRights inheritedAllowRights = 0, allowRights = 0;
265+
266+
foreach (var Rule in AccessRules.Where(x => userSids.Contains(x.IdentityReference)))
267+
{
268+
if (Rule.AccessControlType == AccessControlType.Deny)
269+
{
270+
if (Rule.IsInherited)
271+
{
272+
inheritedDenyRights |= Rule.FileSystemRights;
273+
}
274+
else
275+
{
276+
denyRights |= Rule.FileSystemRights;
277+
}
278+
}
279+
else if (Rule.AccessControlType == AccessControlType.Allow)
280+
{
281+
if (Rule.IsInherited)
282+
{
283+
inheritedAllowRights |= Rule.FileSystemRights;
284+
}
285+
else
286+
{
287+
allowRights |= Rule.FileSystemRights;
288+
}
289+
}
290+
}
291+
292+
return (inheritedAllowRights & ~inheritedDenyRights) | (allowRights & ~denyRights);
293+
}
251294
}
252295

253296
public class FileSystemAccessRule2

0 commit comments

Comments
 (0)