diff --git a/src/Files.App/Filesystem/LibraryManager.cs b/src/Files.App/Filesystem/LibraryManager.cs index 610dd5defcc1..08fef6d0a55f 100644 --- a/src/Files.App/Filesystem/LibraryManager.cs +++ b/src/Files.App/Filesystem/LibraryManager.cs @@ -105,8 +105,8 @@ public static async Task> ListUserLibraries() var libFiles = Directory.EnumerateFiles(ShellLibraryItem.LibrariesPath, "*" + ShellLibraryItem.EXTENSION); foreach (var libFile in libFiles) { - using var shellItem = new ShellLibrary2(Shell32.ShellUtil.GetShellItemForPath(libFile), true); - if (shellItem is ShellLibrary2 library) + using var shellItem = new ShellLibraryEx(Shell32.ShellUtil.GetShellItemForPath(libFile), true); + if (shellItem is ShellLibraryEx library) { libraryItems.Add(ShellFolderExtensions.GetShellLibraryItem(library, libFile)); } @@ -167,7 +167,7 @@ public async Task CreateNewLibrary(string name) { try { - using var library = new ShellLibrary2(name, Shell32.KNOWNFOLDERID.FOLDERID_Libraries, false); + using var library = new ShellLibraryEx(name, Shell32.KNOWNFOLDERID.FOLDERID_Libraries, false); library.Folders.Add(ShellItem.Open(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments))); // Add default folder so it's not empty library.Commit(); library.Reload(); @@ -212,7 +212,7 @@ public async Task UpdateLibrary(string libraryPath, string try { bool updated = false; - using var library = new ShellLibrary2(Shell32.ShellUtil.GetShellItemForPath(libraryPath), false); + using var library = new ShellLibraryEx(Shell32.ShellUtil.GetShellItemForPath(libraryPath), false); if (folders is not null) { if (folders.Length > 0) @@ -404,7 +404,7 @@ private void OnLibraryChanged(WatcherChangeTypes changeType, string oldPath, str if (!changeType.HasFlag(WatcherChangeTypes.Deleted)) { - var library = SafetyExtensions.IgnoreExceptions(() => new ShellLibrary2(Shell32.ShellUtil.GetShellItemForPath(newPath), true)); + var library = SafetyExtensions.IgnoreExceptions(() => new ShellLibraryEx(Shell32.ShellUtil.GetShellItemForPath(newPath), true)); if (library is null) { App.Logger.LogWarning($"Failed to open library after {changeType}: {newPath}"); diff --git a/src/Files.App/Filesystem/StorageItems/ShellStorageFile.cs b/src/Files.App/Filesystem/StorageItems/ShellStorageFile.cs index 811637c5ed0a..2fd47d9318b5 100644 --- a/src/Files.App/Filesystem/StorageItems/ShellStorageFile.cs +++ b/src/Files.App/Filesystem/StorageItems/ShellStorageFile.cs @@ -92,7 +92,7 @@ public static IAsyncOperation FromPathAsync(string path) { try { - using var shellItem = ShellFolderExtensions.GetShellItemFromPathOrPidl(path); + using var shellItem = ShellFolderExtensions.GetShellItemFromPathOrPIDL(path); return ShellFolderExtensions.GetShellFileItem(shellItem); } catch diff --git a/src/Files.App/Shell/ContextMenu.cs b/src/Files.App/Shell/ContextMenu.cs index 496e727af9e3..f4af96b43aad 100644 --- a/src/Files.App/Shell/ContextMenu.cs +++ b/src/Files.App/Shell/ContextMenu.cs @@ -4,37 +4,42 @@ using System.Drawing; using System.IO; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using Vanara.InteropServices; using Vanara.PInvoke; using Vanara.Windows.Shell; namespace Files.App.Shell { - [SupportedOSPlatform("Windows10.0.10240")] + /// + /// Provides a helper for Win32 context menu. + /// public class ContextMenu : Win32ContextMenu, IDisposable { - private Shell32.IContextMenu cMenu; + private Shell32.IContextMenu _cMenu; - private User32.SafeHMENU hMenu; + private User32.SafeHMENU _hMenu; - private ThreadWithMessageQueue owningThread; + private readonly ThreadWithMessageQueue _owningThread; - Func? itemFilter; + private readonly Func? _itemFilter; - private Dictionary, Action> loadSubMenuActions; + private readonly Dictionary, Action> _loadSubMenuActions; + + // To detect redundant calls + private bool disposedValue = false; public List ItemsPath { get; } private ContextMenu(Shell32.IContextMenu cMenu, User32.SafeHMENU hMenu, IEnumerable itemsPath, ThreadWithMessageQueue owningThread, Func? itemFilter) { - this.cMenu = cMenu; - this.hMenu = hMenu; + _cMenu = cMenu; + _hMenu = hMenu; + _owningThread = owningThread; + _itemFilter = itemFilter; + _loadSubMenuActions = new(); + ItemsPath = itemsPath.ToList(); - Items = new List(); - this.owningThread = owningThread; - this.itemFilter = itemFilter; - loadSubMenuActions = new(); + Items = new(); } public async static Task InvokeVerb(string verb, params string[] filePaths) @@ -66,7 +71,7 @@ public async Task InvokeVerb(string? verb) pici.cbSize = (uint)Marshal.SizeOf(pici); - await owningThread.PostMethod(() => cMenu.InvokeCommand(pici)); + await _owningThread.PostMethod(() => _cMenu.InvokeCommand(pici)); Win32API.BringToForeground(currentWindows); return true; @@ -95,7 +100,7 @@ public async Task InvokeItem(int itemID) pici.cbSize = (uint)Marshal.SizeOf(pici); - await owningThread.PostMethod(() => cMenu.InvokeCommand(pici)); + await _owningThread.PostMethod(() => _cMenu.InvokeCommand(pici)); Win32API.BringToForeground(currentWindows); return true; @@ -108,18 +113,19 @@ public async Task InvokeItem(int itemID) return false; } - #region FactoryMethods - public async static Task GetContextMenuForFiles(string[] filePathList, Shell32.CMF flags, Func? itemFilter = null) { var owningThread = new ThreadWithMessageQueue(); + return await owningThread.PostMethod(() => { var shellItems = new List(); + try { - foreach (var fp in filePathList.Where(x => !string.IsNullOrEmpty(x))) - shellItems.Add(ShellFolderExtensions.GetShellItemFromPathOrPidl(fp)); + foreach (var filePathItem in filePathList.Where(x => !string.IsNullOrEmpty(x))) + shellItems.Add(ShellFolderExtensions.GetShellItemFromPathOrPIDL(filePathItem)); + return GetContextMenuForFiles(shellItems.ToArray(), flags, owningThread, itemFilter); } catch (Exception ex) when (ex is ArgumentException or FileNotFoundException) @@ -129,10 +135,8 @@ public async Task InvokeItem(int itemID) } finally { - foreach (var si in shellItems) - { - si.Dispose(); - } + foreach (var item in shellItems) + item.Dispose(); } }); } @@ -140,8 +144,8 @@ public async Task InvokeItem(int itemID) public async static Task GetContextMenuForFiles(ShellItem[] shellItems, Shell32.CMF flags, Func? itemFilter = null) { var owningThread = new ThreadWithMessageQueue(); - return await owningThread.PostMethod(() - => GetContextMenuForFiles(shellItems, flags, owningThread, itemFilter)); + + return await owningThread.PostMethod(() => GetContextMenuForFiles(shellItems, flags, owningThread, itemFilter)); } private static ContextMenu? GetContextMenuForFiles(ShellItem[] shellItems, Shell32.CMF flags, ThreadWithMessageQueue owningThread, Func? itemFilter = null) @@ -151,7 +155,7 @@ public async Task InvokeItem(int itemID) try { - // HP: The items are all in the same folder + // NOTE: The items are all in the same folder using var sf = shellItems[0].Parent; Shell32.IContextMenu menu = sf.GetChildrenUIObjects(default, shellItems); @@ -174,76 +178,70 @@ public static async Task WarmUpQueryContextMenuAsync() using var cMenu = await GetContextMenuForFiles(new string[] { "C:\\" }, Shell32.CMF.CMF_NORMAL); } - #endregion FactoryMethods - - private void EnumMenuItems( - HMENU hMenu, - List menuItemsResult, - bool loadSubenus = false) + private void EnumMenuItems(HMENU hMenu, List menuItemsResult, bool loadSubenus = false) { var itemCount = User32.GetMenuItemCount(hMenu); - var mii = new User32.MENUITEMINFO() + var menuItemInfo = new User32.MENUITEMINFO() { - fMask = User32.MenuItemInfoMask.MIIM_BITMAP | + fMask = + User32.MenuItemInfoMask.MIIM_BITMAP | User32.MenuItemInfoMask.MIIM_FTYPE | User32.MenuItemInfoMask.MIIM_STRING | User32.MenuItemInfoMask.MIIM_ID | User32.MenuItemInfoMask.MIIM_SUBMENU, }; - mii.cbSize = (uint)Marshal.SizeOf(mii); + menuItemInfo.cbSize = (uint)Marshal.SizeOf(menuItemInfo); - for (uint ii = 0; ii < itemCount; ii++) + for (uint index = 0; index < itemCount; index++) { var menuItem = new ContextMenuItem(); var container = new SafeCoTaskMemString(512); - var cMenu2 = cMenu as Shell32.IContextMenu2; + var cMenu2 = _cMenu as Shell32.IContextMenu2; - mii.dwTypeData = (IntPtr)container; + menuItemInfo.dwTypeData = (IntPtr)container; - // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 - mii.cch = (uint)container.Capacity - 1; + // See also, https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 + menuItemInfo.cch = (uint)container.Capacity - 1; - var retval = User32.GetMenuItemInfo(hMenu, ii, true, ref mii); - if (!retval) + var result = User32.GetMenuItemInfo(hMenu, index, true, ref menuItemInfo); + if (!result) { container.Dispose(); continue; } - menuItem.Type = (MenuItemType)mii.fType; + menuItem.Type = (MenuItemType)menuItemInfo.fType; // wID - idCmdFirst - menuItem.ID = (int)(mii.wID - 1); + menuItem.ID = (int)(menuItemInfo.wID - 1); if (menuItem.Type == MenuItemType.MFT_STRING) { - Debug.WriteLine("Item {0} ({1}): {2}", ii, mii.wID, mii.dwTypeData); + Debug.WriteLine("Item {0} ({1}): {2}", index, menuItemInfo.wID, menuItemInfo.dwTypeData); - // Hackish workaround to avoid an AccessViolationException on some items, - // notably the "Run with graphic processor" menu item of NVidia cards - if (mii.wID - 1 > 5000) + // A workaround to avoid an AccessViolationException on some items, + // notably the "Run with graphic processor" menu item of NVIDIA cards + if (menuItemInfo.wID - 1 > 5000) { container.Dispose(); - continue; } - menuItem.Label = mii.dwTypeData; - menuItem.CommandString = GetCommandString(cMenu, mii.wID - 1); + menuItem.Label = menuItemInfo.dwTypeData; + menuItem.CommandString = GetCommandString(_cMenu, menuItemInfo.wID - 1); - if (itemFilter is not null && (itemFilter(menuItem.CommandString) || itemFilter(menuItem.Label))) + if (_itemFilter is not null && (_itemFilter(menuItem.CommandString) || _itemFilter(menuItem.Label))) { // Skip items implemented in UWP container.Dispose(); - continue; } - if (mii.hbmpItem != HBITMAP.NULL && !Enum.IsDefined(typeof(HBITMAP_HMENU), ((IntPtr)mii.hbmpItem).ToInt64())) + if (menuItemInfo.hbmpItem != HBITMAP.NULL && !Enum.IsDefined(typeof(HBITMAP_HMENU), ((IntPtr)menuItemInfo.hbmpItem).ToInt64())) { - using var bitmap = Win32API.GetBitmapFromHBitmap(mii.hbmpItem); + using var bitmap = Win32API.GetBitmapFromHBitmap(menuItemInfo.hbmpItem); if (bitmap is not null) { @@ -252,30 +250,26 @@ private void EnumMenuItems( } } - if (mii.hSubMenu != HMENU.NULL) + if (menuItemInfo.hSubMenu != HMENU.NULL) { - Debug.WriteLine("Item {0}: has submenu", ii); + Debug.WriteLine("Item {0}: has submenu", index); var subItems = new List(); - var hSubMenu = mii.hSubMenu; + var hSubMenu = menuItemInfo.hSubMenu; if (loadSubenus) - { LoadSubMenu(); - } else - { - loadSubMenuActions.Add(subItems, LoadSubMenu); - } + _loadSubMenuActions.Add(subItems, LoadSubMenu); menuItem.SubItems = subItems; - Debug.WriteLine("Item {0}: done submenu", ii); + Debug.WriteLine("Item {0}: done submenu", index); void LoadSubMenu() { try { - cMenu2?.HandleMenuMsg((uint)User32.WindowMessage.WM_INITMENUPOPUP, (IntPtr)hSubMenu, new IntPtr(ii)); + cMenu2?.HandleMenuMsg((uint)User32.WindowMessage.WM_INITMENUPOPUP, (IntPtr)hSubMenu, new IntPtr(index)); } catch (Exception ex) when (ex is COMException or NotImplementedException) { @@ -288,7 +282,7 @@ void LoadSubMenu() } else { - Debug.WriteLine("Item {0}: {1}", ii, mii.fType.ToString()); + Debug.WriteLine("Item {0}: {1}", index, menuItemInfo.fType.ToString()); } container.Dispose(); @@ -298,9 +292,9 @@ void LoadSubMenu() public Task LoadSubMenu(List subItems) { - if (loadSubMenuActions.Remove(subItems, out var loadSubMenuAction)) + if (_loadSubMenuActions.Remove(subItems, out var loadSubMenuAction)) { - return owningThread.PostMethod(() => + return _owningThread.PostMethod(() => { try { @@ -333,7 +327,7 @@ public Task LoadSubMenu(List subItems) } catch (Exception ex) when (ex is InvalidCastException or ArgumentException) { - // TODO: Investigate this... + // TODO: Investigate why this exception happen Debug.WriteLine(ex); return null; @@ -350,11 +344,6 @@ public Task LoadSubMenu(List subItems) } } - #region IDisposable Support - - // To detect redundant calls - private bool disposedValue = false; - protected virtual void Dispose(bool disposing) { if (!disposedValue) @@ -374,49 +363,32 @@ protected virtual void Dispose(bool disposing) } // TODO: Free unmanaged resources (unmanaged objects) and override a finalizer below - if (hMenu is not null) + if (_hMenu is not null) { - User32.DestroyMenu(hMenu); - hMenu = null; + User32.DestroyMenu(_hMenu); + _hMenu = null; } - if (cMenu is not null) + if (_cMenu is not null) { - Marshal.ReleaseComObject(cMenu); - cMenu = null; + Marshal.ReleaseComObject(_cMenu); + _cMenu = null; } - owningThread.Dispose(); + _owningThread.Dispose(); disposedValue = true; } } - ~ContextMenu() - { - Dispose(false); - } - public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - #endregion IDisposable Support - - public enum HBITMAP_HMENU : long + ~ContextMenu() { - HBMMENU_CALLBACK = -1, - HBMMENU_MBAR_CLOSE = 5, - HBMMENU_MBAR_CLOSE_D = 6, - HBMMENU_MBAR_MINIMIZE = 3, - HBMMENU_MBAR_MINIMIZE_D = 7, - HBMMENU_MBAR_RESTORE = 2, - HBMMENU_POPUP_CLOSE = 8, - HBMMENU_POPUP_MAXIMIZE = 10, - HBMMENU_POPUP_MINIMIZE = 11, - HBMMENU_POPUP_RESTORE = 9, - HBMMENU_SYSTEM = 1 + Dispose(false); } } } diff --git a/src/Files.App/Shell/ContextMenuItem.cs b/src/Files.App/Shell/ContextMenuItem.cs index f7718f0151f9..93185d646fdf 100644 --- a/src/Files.App/Shell/ContextMenuItem.cs +++ b/src/Files.App/Shell/ContextMenuItem.cs @@ -3,6 +3,9 @@ namespace Files.App.Shell { + /// + /// Represents an item for Win32 context menu. + /// public class ContextMenuItem : Win32ContextMenuItem, IDisposable { public void Dispose() diff --git a/src/Files.App/Shell/Disposable.cs b/src/Files.App/Shell/Disposable.cs index be69d4d6dab3..344d3f58a44e 100644 --- a/src/Files.App/Shell/Disposable.cs +++ b/src/Files.App/Shell/Disposable.cs @@ -3,6 +3,9 @@ namespace Files.App.Shell { + /// + /// Represents an abstracted implementation for IDisposable + /// public abstract class Disposable : IDisposable { public void Dispose() diff --git a/src/Files.App/Shell/LaunchHelper.cs b/src/Files.App/Shell/LaunchHelper.cs index 25d24250ea11..a09a425e0ea5 100644 --- a/src/Files.App/Shell/LaunchHelper.cs +++ b/src/Files.App/Shell/LaunchHelper.cs @@ -10,23 +10,33 @@ namespace Files.App.Shell { + /// + /// Provides static helper for launching external executable files. + /// public static class LaunchHelper { public static void LaunchSettings(string page) { var appActiveManager = new Shell32.IApplicationActivationManager(); - appActiveManager.ActivateApplication("windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel", - page, Shell32.ACTIVATEOPTIONS.AO_NONE, out _); + + appActiveManager.ActivateApplication( + "windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel", + page, + Shell32.ACTIVATEOPTIONS.AO_NONE, + out _); } public static Task LaunchAppAsync(string application, string arguments, string workingDirectory) - => HandleApplicationLaunch(application, arguments, workingDirectory); + { + return HandleApplicationLaunch(application, arguments, workingDirectory); + } public static Task RunCompatibilityTroubleshooterAsync(string filePath) { - var afPath = Path.Combine(Path.GetTempPath(), "CompatibilityTroubleshooterAnswerFile.xml"); - File.WriteAllText(afPath, string.Format("CompatTab{0}", filePath)); - return HandleApplicationLaunch("msdt.exe", $"/id PCWDiagnostic /af \"{afPath}\"", ""); + var compatibilityTroubleshooterAnswerFile = Path.Combine(Path.GetTempPath(), "CompatibilityTroubleshooterAnswerFile.xml"); + File.WriteAllText(compatibilityTroubleshooterAnswerFile, string.Format("CompatTab{0}", filePath)); + + return HandleApplicationLaunch("MSDT.exe", $"/id PCWDiagnostic /af \"{compatibilityTroubleshooterAnswerFile}\"", ""); } private static async Task HandleApplicationLaunch(string application, string arguments, string workingDirectory) @@ -35,7 +45,7 @@ private static async Task HandleApplicationLaunch(string application, stri if (FileExtensionHelpers.IsVhdFile(application)) { - // Use powershell to mount vhds as this requires admin rights + // Use PowerShell to mount Vhd Disk as this requires admin rights return Win32API.MountVhdDisk(application); } @@ -48,25 +58,25 @@ private static async Task HandleApplicationLaunch(string application, stri // Show window if workingDirectory (opening terminal) process.StartInfo.CreateNoWindow = string.IsNullOrEmpty(workingDirectory); - if (arguments == "runas") + if (arguments == "RunAs") { process.StartInfo.UseShellExecute = true; - process.StartInfo.Verb = "runas"; + process.StartInfo.Verb = "RunAs"; if (FileExtensionHelpers.IsMsiFile(application)) { - process.StartInfo.FileName = "msiexec.exe"; + process.StartInfo.FileName = "MSIEXEC.exe"; process.StartInfo.Arguments = $"/a \"{application}\""; } } - else if (arguments == "runasuser") + else if (arguments == "RunAsUser") { process.StartInfo.UseShellExecute = true; - process.StartInfo.Verb = "runasuser"; + process.StartInfo.Verb = "RunAsUser"; if (FileExtensionHelpers.IsMsiFile(application)) { - process.StartInfo.FileName = "msiexec.exe"; + process.StartInfo.FileName = "MSIEXEC.exe"; process.StartInfo.Arguments = $"/i \"{application}\""; } } diff --git a/src/Files.App/Shell/RecycleBinManager.cs b/src/Files.App/Shell/RecycleBinManager.cs index 00dd05c17964..f37f96c2006d 100644 --- a/src/Files.App/Shell/RecycleBinManager.cs +++ b/src/Files.App/Shell/RecycleBinManager.cs @@ -5,20 +5,25 @@ namespace Files.App.Shell { + /// + /// Provides a utility to handle Windows Recycle Bin. + /// public sealed class RecycleBinManager { private static readonly Lazy lazy = new(() => new RecycleBinManager()); + private IList? binWatchers; public event SystemIO.FileSystemEventHandler? RecycleBinItemCreated; + public event SystemIO.FileSystemEventHandler? RecycleBinItemDeleted; + public event SystemIO.FileSystemEventHandler? RecycleBinItemRenamed; + public event SystemIO.FileSystemEventHandler? RecycleBinRefreshRequested; public static RecycleBinManager Default - { - get => lazy.Value; - } + => lazy.Value; private RecycleBinManager() { @@ -33,9 +38,10 @@ private void Initialize() private void StartRecycleBinWatcher() { - // Create filesystem watcher to monitor recycle bin folder(s) - // SHChangeNotifyRegister only works if recycle bin is open in explorer :( + // NOTE: SHChangeNotifyRegister only works if recycle bin is open in explorer + // Create file system watcher to monitor recycle bin folder(s) binWatchers = new List(); + var sid = WindowsIdentity.GetCurrent().User.ToString(); foreach (var drive in SystemIO.DriveInfo.GetDrives()) @@ -62,7 +68,8 @@ private void StartRecycleBinWatcher() private void RecycleBinWatcher_Changed(object sender, SystemIO.FileSystemEventArgs e) { - System.Diagnostics.Debug.WriteLine($"Recycle bin event: {e.ChangeType}, {e.FullPath}"); + Debug.WriteLine($"Recycle bin event: {e.ChangeType}, {e.FullPath}"); + if (e.Name.StartsWith("$I", StringComparison.Ordinal)) { // Recycle bin also stores a file starting with $I for each item @@ -91,9 +98,7 @@ private void Unregister() if (binWatchers is not null) { foreach (var watcher in binWatchers) - { watcher.Dispose(); - } } } diff --git a/src/Files.App/Shell/ShellFolderExtensions.cs b/src/Files.App/Shell/ShellFolderExtensions.cs index d784cffe1979..2341bb2a7139 100644 --- a/src/Files.App/Shell/ShellFolderExtensions.cs +++ b/src/Files.App/Shell/ShellFolderExtensions.cs @@ -7,9 +7,12 @@ namespace Files.App.Shell { + /// + /// Provides static extension for shell folders. + /// public static class ShellFolderExtensions { - public static ShellLibraryItem GetShellLibraryItem(ShellLibrary2 library, string filePath) + public static ShellLibraryItem GetShellLibraryItem(ShellLibraryEx library, string filePath) { var libraryItem = new ShellLibraryItem { @@ -33,6 +36,7 @@ public static ShellLibraryItem GetShellLibraryItem(ShellLibrary2 library, string private static T TryGetProperty(this ShellItemPropertyStore sip, Ole32.PROPERTYKEY key) { T value = default; + SafetyExtensions.IgnoreExceptions(() => sip.TryGetValue(key, out value)); return value; @@ -43,8 +47,9 @@ public static ShellFileItem GetShellFileItem(ShellItem folderItem) if (folderItem is null) return null; + // NOTE: Do not use folderItem's Attributes property, throws unimplemented for some shell folders + // Zip archives are also shell folders, check for STREAM attribute - // Do not use folderItem's Attributes property, throws unimplemented for some shell folders bool isFolder = folderItem.IsFolder && folderItem.IShellItem?.GetAttributes(Shell32.SFGAO.SFGAO_STREAM) is 0; var parsingPath = folderItem.GetDisplayName(ShellItemDisplayString.DesktopAbsoluteParsing); @@ -105,7 +110,7 @@ public static ShellFileItem GetShellFileItem(ShellItem folderItem) string fileSize = fileSizeBytes is not null ? folderItem.Properties.GetPropertyString(Ole32.PROPERTYKEY.System.Size) : null; var fileType = folderItem.Properties.TryGetProperty(Ole32.PROPERTYKEY.System.ItemTypeText); - return new ShellFileItem(isFolder, parsingPath, fileName, filePath, recycleDate, modifiedDate, createdDate, fileSize, fileSizeBytes ?? 0, fileType, folderItem.PIDL.GetBytes()); + return new(isFolder, parsingPath, fileName, filePath, recycleDate, modifiedDate, createdDate, fileSize, fileSizeBytes ?? 0, fileType, folderItem.PIDL.GetBytes()); } public static ShellLinkItem GetShellLinkItem(ShellLink linkItem) @@ -117,12 +122,14 @@ public static ShellLinkItem GetShellLinkItem(ShellLink linkItem) if (baseItem is null) return null; - var link = new ShellLinkItem(baseItem); - link.IsFolder = !string.IsNullOrEmpty(linkItem.TargetPath) && linkItem.Target.IsFolder; - link.RunAsAdmin = linkItem.RunAsAdministrator; - link.Arguments = linkItem.Arguments; - link.WorkingDirectory = linkItem.WorkingDirectory; - link.TargetPath = linkItem.TargetPath; + var link = new ShellLinkItem(baseItem) + { + IsFolder = !string.IsNullOrEmpty(linkItem.TargetPath) && linkItem.Target.IsFolder, + RunAsAdmin = linkItem.RunAsAdministrator, + Arguments = linkItem.Arguments, + WorkingDirectory = linkItem.WorkingDirectory, + TargetPath = linkItem.TargetPath + }; return link; } @@ -135,30 +142,30 @@ public static string GetParsingPath(this ShellItem item) return item.IsFileSystem ? item.FileSystemPath : item.ParsingName; } - public static bool GetStringAsPidl(string pathOrPidl, out Shell32.PIDL pidl) + public static bool GetStringAsPIDL(string pathOrPIDL, out Shell32.PIDL pPIDL) { - if (pathOrPidl.StartsWith(@"\\SHELL\", StringComparison.Ordinal)) + if (pathOrPIDL.StartsWith(@"\\SHELL\", StringComparison.Ordinal)) { - pidl = pathOrPidl.Replace(@"\\SHELL\", "", StringComparison.Ordinal) + pPIDL = pathOrPIDL.Replace(@"\\SHELL\", "", StringComparison.Ordinal) // Avoid confusion with path separator .Replace("_", "/") .Split('\\', StringSplitOptions.RemoveEmptyEntries) .Select(pathSegment => new Shell32.PIDL(Convert.FromBase64String(pathSegment))) - .Aggregate((x, y) => Shell32.PIDL.Combine(x, y)); + .Aggregate(Shell32.PIDL.Combine); return true; } else { - pidl = Shell32.PIDL.Null; + pPIDL = Shell32.PIDL.Null; return false; } } - public static ShellItem GetShellItemFromPathOrPidl(string pathOrPidl) + public static ShellItem GetShellItemFromPathOrPIDL(string pathOrPIDL) { - return GetStringAsPidl(pathOrPidl, out var pidl) ? ShellItem.Open(pidl) : ShellItem.Open(pathOrPidl); + return GetStringAsPIDL(pathOrPIDL, out var pPIDL) ? ShellItem.Open(pPIDL) : ShellItem.Open(pathOrPIDL); } } } diff --git a/src/Files.App/Shell/ShellLibrary2.cs b/src/Files.App/Shell/ShellLibrary2.cs deleted file mode 100644 index 2bdf2a04750a..000000000000 --- a/src/Files.App/Shell/ShellLibrary2.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -using System.Windows.Forms; -using Vanara.Extensions; -using Vanara.PInvoke; -using Vanara.Windows.Shell; -using static Vanara.PInvoke.Shell32; - -namespace Files.App.Shell -{ - /// Shell library encapsulation. - public class ShellLibrary2 : ShellFolder - { - //private const string ext = ".library-ms"; - internal IShellLibrary lib; - - private ShellLibraryFolders folders; - private string name; - - /// Initializes a new instance of the class. - /// The known folder identifier. - /// if set to true [read only]. - public ShellLibrary2(KNOWNFOLDERID knownFolderId, bool readOnly = false) - { - lib = new IShellLibrary(); - lib.LoadLibraryFromKnownFolder(knownFolderId.Guid(), readOnly ? STGM.STGM_READ : STGM.STGM_READWRITE); - - Init(knownFolderId.GetIShellItem()); - } - - /// Initializes a new instance of the class. - /// Name of the library. - /// The known folder identifier. - /// if set to true [overwrite]. - public ShellLibrary2(string libraryName, KNOWNFOLDERID kf = KNOWNFOLDERID.FOLDERID_Libraries, bool overwrite = false) - { - lib = new IShellLibrary(); - name = libraryName; - var item = lib.SaveInKnownFolder(kf.Guid(), libraryName, overwrite ? LIBRARYSAVEFLAGS.LSF_OVERRIDEEXISTING : LIBRARYSAVEFLAGS.LSF_FAILIFTHERE); - - Init(item); - } - - /// Initializes a new instance of the class. - /// Name of the library. - /// The parent. - /// if set to true [overwrite]. - public ShellLibrary2(string libraryName, ShellFolder parent, bool overwrite = false) - { - lib = new IShellLibrary(); - name = libraryName; - var item = lib.Save(parent.IShellItem, libraryName, overwrite ? LIBRARYSAVEFLAGS.LSF_OVERRIDEEXISTING : LIBRARYSAVEFLAGS.LSF_FAILIFTHERE); - - Init(item); - } - - /// Initializes a new instance of the class. - /// The i item. - /// if set to true [read only]. - public ShellLibrary2(IShellItem iItem, bool readOnly = false) - { - lib = new IShellLibrary(); - lib.LoadLibraryFromItem(iItem, readOnly ? STGM.STGM_READ : STGM.STGM_READWRITE); - - Init(iItem); - } - - /// Gets or sets the default target folder the library uses for save operations. - /// The default save folder. - public ShellItem DefaultSaveFolder - { - get => Open(lib.GetDefaultSaveFolder(DEFAULTSAVEFOLDERTYPE.DSFT_DETECT)); - set => lib.SetDefaultSaveFolder(DEFAULTSAVEFOLDERTYPE.DSFT_DETECT, value.IShellItem); - } - - /// Gets the set of child folders that are contained in the library. - /// A containing the child folders. - public ShellLibraryFolders Folders => folders ??= GetFilteredFolders(); - - public void Reload() - { - folders = GetFilteredFolders(); - } - - /// - /// Gets or sets a string that describes the location of the default icon. The string must be formatted as - /// ModuleFileName,ResourceIndex or ModuleFileName,-ResourceID. - /// - /// The default icon location. - public IconLocation IconLocation - { - get - { - _ = IconLocation.TryParse(lib.GetIcon(), out var l); - return l; - } - set => lib.SetIcon(value.ToString()); - } - - /// Gets the name relative to the parent for the item. - public override string Name { get => name; protected set => name = value; } - - /// Gets or sets a value indicating whether to pin the library to the navigation pane. - /// true if pinned to the navigation pane; otherwise, false. - public bool PinnedToNavigationPane - { - get => lib.GetOptions().IsFlagSet(LIBRARYOPTIONFLAGS.LOF_PINNEDTONAVPANE); - set => lib.SetOptions(LIBRARYOPTIONFLAGS.LOF_PINNEDTONAVPANE, value ? LIBRARYOPTIONFLAGS.LOF_PINNEDTONAVPANE : 0); - } - - /// Gets or sets the library's View Template. - /// The View Template. - public LibraryViewTemplate ViewTemplate - { - get => (LibraryViewTemplate)ShlGuidExt.Lookup(ViewTemplateId); - set - { - if (value != LibraryViewTemplate.Custom) - ViewTemplateId = ((FOLDERTYPEID)value).Guid(); - } - } - - /// Gets or sets the library's View Template identifier. - /// The View Template identifier. - public Guid ViewTemplateId - { - get => lib.GetFolderType(); - set => lib.SetFolderType(value); - } - - /// Commits library updates. - public void Commit() => lib.Commit(); - - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - public override void Dispose() - { - lib = null; - - folders?.Dispose(); - folders = null; - - base.Dispose(); - GC.SuppressFinalize(this); - } - - /// Gets the set of child folders that are contained in the library. - /// A value that determines the folders to get. - /// A containing the child folders. - public ShellLibraryFolders GetFilteredFolders(LibraryFolderFilter filter = LibraryFolderFilter.AllItems) - => new(lib, lib.GetFolders((LIBRARYFOLDERFILTER)filter)); - - /// Resolves the target location of a library folder, even if the folder has been moved or renamed. - /// A ShellItem object that represents the library folder to locate. - /// - /// The maximum time the method will attempt to locate the folder before returning. If the folder could not be located before the - /// specified time elapses, an error is returned. - /// - /// The resulting target location. - public ShellItem ResolveFolder(ShellItem item, TimeSpan timeout) - => Open(lib.ResolveFolder(item.IShellItem, Convert.ToUInt32(timeout.TotalMilliseconds))); - - /// Shows the library management dialog box, which enables users to manage the library folders and default save location. - /// - /// The handle for the window that owns the library management dialog box. The value of this parameter can be NULL. - /// - /// - /// The title for the library management dialog. To display the generic title string, set the value of this parameter to NULL. - /// - /// - /// The help string to display below the title string in the library management dialog box. To display the generic help string, set - /// the value of this parameter to NULL. - /// - /// - /// if set to true do not display a warning dialog to the user in collisions that concern network locations that cannot be indexed. - /// - public void ShowLibraryManagementDialog(IWin32Window parentWindow = null, string title = null, string instruction = null, bool allowUnindexableLocations = false) - { - SHShowManageLibraryUI(IShellItem, parentWindow?.Handle ?? IntPtr.Zero, title, instruction, - allowUnindexableLocations ? LIBRARYMANAGEDIALOGOPTIONS.LMD_ALLOWUNINDEXABLENETWORKLOCATIONS : LIBRARYMANAGEDIALOGOPTIONS.LMD_DEFAULT).ThrowIfFailed(); - } - - /// Folders of a . - /// - /// - public class ShellLibraryFolders : ShellItemArray, ICollection - { - private IShellLibrary lib; - - /// Initializes a new instance of the class. - /// The library. - /// The shell item array. - internal ShellLibraryFolders(IShellLibrary lib, IShellItemArray shellItemArray) : base(shellItemArray) - => this.lib = lib; - - /// Gets a value indicating whether the is read-only. - bool ICollection.IsReadOnly - => false; - - /// Adds the specified location. - /// The location. - /// location - public void Add(ShellItem location) - { - if (location is null) throw new ArgumentNullException(nameof(location)); - lib.AddFolder(location.IShellItem); - } - - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - public override void Dispose() - { - lib = null; - base.Dispose(); - GC.SuppressFinalize(this); - } - - /// Removes the specified location. - /// The location. - /// true on success. - /// location - public bool Remove(ShellItem location) - { - if (location is null) - throw new ArgumentNullException(nameof(location)); - - try - { - lib.RemoveFolder(location.IShellItem); - return true; - } - catch - { - return false; - } - } - - /// Removes all items from the . - /// - void ICollection.Clear() - => throw new NotImplementedException(); - } - } -} diff --git a/src/Files.App/Shell/ShellLibraryEx.cs b/src/Files.App/Shell/ShellLibraryEx.cs new file mode 100644 index 000000000000..822e0d38a59b --- /dev/null +++ b/src/Files.App/Shell/ShellLibraryEx.cs @@ -0,0 +1,242 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using System.Windows.Forms; +using Vanara.Extensions; +using Vanara.PInvoke; +using Vanara.Windows.Shell; + +namespace Files.App.Shell +{ + /// + /// Represents an encapsulated item for shell library. + /// + public class ShellLibraryEx : ShellFolder + { + //private const string ext = ".library-ms"; + + internal Shell32.IShellLibrary _lib; + + private ShellLibraryFolders _folders; + + private string _name; + + /// + /// Initializes a new instance of the Ex class. + /// + /// The known folder identifier. + /// If set to true [read only]. + public ShellLibraryEx(Shell32.KNOWNFOLDERID knownFolderId, bool readOnly = false) + { + _lib = new Shell32.IShellLibrary(); + _lib.LoadLibraryFromKnownFolder(knownFolderId.Guid(), readOnly ? STGM.STGM_READ : STGM.STGM_READWRITE); + + Init(knownFolderId.GetIShellItem()); + } + + /// + /// Initializes a new instance of the Ex class. + /// + /// Name of the library. + /// The known folder identifier. + /// If set to true [overwrite]. + public ShellLibraryEx(string libraryName, Shell32.KNOWNFOLDERID kf = Shell32.KNOWNFOLDERID.FOLDERID_Libraries, bool overwrite = false) + { + _lib = new Shell32.IShellLibrary(); + _name = libraryName; + var item = _lib.SaveInKnownFolder(kf.Guid(), libraryName, overwrite ? Shell32.LIBRARYSAVEFLAGS.LSF_OVERRIDEEXISTING : Shell32.LIBRARYSAVEFLAGS.LSF_FAILIFTHERE); + + Init(item); + } + + /// + /// Initializes a new instance of the Ex class. + /// + /// Name of the library. + /// The parent. + /// If set to true [overwrite]. + public ShellLibraryEx(string libraryName, ShellFolder parent, bool overwrite = false) + { + _lib = new Shell32.IShellLibrary(); + _name = libraryName; + var item = _lib.Save(parent.IShellItem, libraryName, overwrite ? Shell32.LIBRARYSAVEFLAGS.LSF_OVERRIDEEXISTING : Shell32.LIBRARYSAVEFLAGS.LSF_FAILIFTHERE); + + Init(item); + } + + /// + /// Initializes a new instance of the class. + /// + /// The library item. + /// If set to true [read only]. + public ShellLibraryEx(Shell32.IShellItem libraryItem, bool readOnly = false) + { + _lib = new Shell32.IShellLibrary(); + _lib.LoadLibraryFromItem(libraryItem, readOnly ? STGM.STGM_READ : STGM.STGM_READWRITE); + + Init(libraryItem); + } + + /// + /// Gets or sets the default target folder the library uses for save operations. + /// + /// The default save folder. + public ShellItem DefaultSaveFolder + { + get => Open(_lib.GetDefaultSaveFolder(Shell32.DEFAULTSAVEFOLDERTYPE.DSFT_DETECT)); + set => _lib.SetDefaultSaveFolder(Shell32.DEFAULTSAVEFOLDERTYPE.DSFT_DETECT, value.IShellItem); + } + + /// Gets the set of child folders that are contained in the library. + /// A containing the child folders. + public ShellLibraryFolders Folders + => _folders ??= GetFilteredFolders(); + + /// + /// Gets or sets a string that describes the location of the default icon. + /// The string must be formatted as + /// ModuleFileName,ResourceIndex or ModuleFileName,-ResourceID. + /// + /// + /// The default icon location. + /// + public IconLocation IconLocation + { + get + { + _ = IconLocation.TryParse(_lib.GetIcon(), out var l); + return l; + } + set => _lib.SetIcon(value.ToString()); + } + + /// + /// Gets the name relative to the parent for the item. + /// + public override string Name + { + get => _name; + protected set => _name = value; + } + + /// + /// Gets or sets a value indicating whether to pin the library to the navigation pane. + /// + /// + /// true if pinned to the navigation pane; otherwise, false. + /// + public bool PinnedToNavigationPane + { + get => _lib.GetOptions().IsFlagSet(Shell32.LIBRARYOPTIONFLAGS.LOF_PINNEDTONAVPANE); + set => _lib.SetOptions(Shell32.LIBRARYOPTIONFLAGS.LOF_PINNEDTONAVPANE, value ? Shell32.LIBRARYOPTIONFLAGS.LOF_PINNEDTONAVPANE : 0); + } + + /// + /// Gets or sets the library's View Template. + /// + /// + /// The View Template. + /// + public LibraryViewTemplate ViewTemplate + { + get => (LibraryViewTemplate)ShlGuidExt.Lookup(ViewTemplateId); + set + { + if (value != LibraryViewTemplate.Custom) + ViewTemplateId = ((Shell32.FOLDERTYPEID)value).Guid(); + } + } + + /// + /// Gets or sets the library's View Template identifier. + /// + /// + /// The View Template identifier. + /// + public Guid ViewTemplateId + { + get => _lib.GetFolderType(); + set => _lib.SetFolderType(value); + } + + /// + /// Reload library folders. + /// + public void Reload() + { + _folders = GetFilteredFolders(); + } + + /// + /// Commits library updates. + /// + public void Commit() + { + _lib.Commit(); + } + + /// + /// Gets the set of child folders that are contained in the library. + /// + /// A value that determines the folders to get. + /// A containing the child folders. + public ShellLibraryFolders GetFilteredFolders(LibraryFolderFilter filter = LibraryFolderFilter.AllItems) + { + return new(_lib, _lib.GetFolders((Shell32.LIBRARYFOLDERFILTER)filter)); + } + + /// + /// Resolves the target location of a library folder, even if the folder has been moved or renamed. + /// + /// A ShellItem object that represents the library folder to locate. + /// + /// The maximum time the method will attempt to locate the folder before returning. If the folder could not be located before the + /// specified time elapses, an error is returned. + /// + /// The resulting target location. + public ShellItem ResolveFolder(ShellItem item, TimeSpan timeout) + { + return Open(_lib.ResolveFolder(item.IShellItem, Convert.ToUInt32(timeout.TotalMilliseconds))); + } + + /// + /// Shows the library management dialog box, which enables users to manage the library folders and default save location. + /// + /// + /// The handle for the window that owns the library management dialog box. The value of this parameter can be NULL. + /// + /// + /// The title for the library management dialog. To display the generic title string, set the value of this parameter to NULL. + /// + /// + /// The help string to display below the title string in the library management dialog box. To display the generic help string, set + /// the value of this parameter to NULL. + /// + /// + /// If set to true do not display a warning dialog to the user in collisions that concern network locations that cannot be indexed. + /// + public void ShowLibraryManagementDialog(IWin32Window parentWindow = null, string title = null, string instruction = null, bool allowUnindexableLocations = false) + { + Shell32.SHShowManageLibraryUI( + IShellItem, + parentWindow?.Handle ?? IntPtr.Zero, + title, instruction, + allowUnindexableLocations ? Shell32.LIBRARYMANAGEDIALOGOPTIONS.LMD_ALLOWUNINDEXABLENETWORKLOCATIONS : Shell32.LIBRARYMANAGEDIALOGOPTIONS.LMD_DEFAULT + ).ThrowIfFailed(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public override void Dispose() + { + _lib = null; + + _folders?.Dispose(); + _folders = null; + + base.Dispose(); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Files.App/Shell/ShellLibraryFolders.cs b/src/Files.App/Shell/ShellLibraryFolders.cs new file mode 100644 index 000000000000..8433641f2acb --- /dev/null +++ b/src/Files.App/Shell/ShellLibraryFolders.cs @@ -0,0 +1,91 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Vanara.PInvoke; +using Vanara.Windows.Shell; + +namespace Files.App.Shell +{ + /// + /// Folders of a . + /// + /// + /// + public class ShellLibraryFolders : ShellItemArray, ICollection + { + private Shell32.IShellLibrary _lib; + + /// + /// Initializes a new instance of the class. + /// + /// The library. + /// The shell item array. + internal ShellLibraryFolders(Shell32.IShellLibrary lib, Shell32.IShellItemArray shellItemArray) : base(shellItemArray) + { + _lib = lib; + } + + /// + /// Gets a value indicating whether the is read-only. + /// + bool ICollection.IsReadOnly + => false; + + /// + /// Adds the specified location. + /// + /// The location. + /// location + public void Add(ShellItem location) + { + if (location is null) + throw new ArgumentNullException(nameof(location)); + + _lib.AddFolder(location.IShellItem); + } + + /// + /// Removes the specified location. + /// + /// The location. + /// true on success. + /// location + public bool Remove(ShellItem location) + { + if (location is null) + throw new ArgumentNullException(nameof(location)); + + try + { + _lib.RemoveFolder(location.IShellItem); + + return true; + } + catch + { + return false; + } + } + + /// + /// Removes all items from the . + /// + /// + void ICollection.Clear() + { + throw new NotImplementedException(); + } + + /// + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + /// + public override void Dispose() + { + _lib = null; + + base.Dispose(); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Files.App/Shell/ShellNewMenuHelper.cs b/src/Files.App/Shell/ShellNewMenuHelper.cs index 4ee3c166a577..ec6a2c0fc244 100644 --- a/src/Files.App/Shell/ShellNewMenuHelper.cs +++ b/src/Files.App/Shell/ShellNewMenuHelper.cs @@ -1,22 +1,23 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. - using Microsoft.Win32; using System.IO; -using System.Runtime.Versioning; using System.Security; using System.Text; using Windows.Storage; namespace Files.App.Shell { - [SupportedOSPlatform("Windows10.0.10240")] + /// + /// Provides static helper to get extension-specific shell context menu from Windows Registry. + /// public static class ShellNewMenuHelper { public static async Task> GetNewContextMenuEntries() { var newMenuItems = new List(); + var shortcutExtensions = new string[] { ShellLibraryItem.EXTENSION, ".url", ".lnk" }; foreach (var keyName in Registry.ClassesRoot.GetSubKeyNames().Where(x => x.StartsWith('.') && !shortcutExtensions.Contains(x, StringComparer.OrdinalIgnoreCase))) @@ -79,7 +80,9 @@ private static Task ParseShellNewRegistryEntry(RegistryKey key, R !valueNames.Contains("Command", StringComparer.OrdinalIgnoreCase) && !valueNames.Contains("ItemName", StringComparer.OrdinalIgnoreCase) && !valueNames.Contains("Data", StringComparer.OrdinalIgnoreCase)) + { return Task.FromResult(null); + } var extension = root.Name.Substring(root.Name.LastIndexOf('\\') + 1); var fileName = (string)key.GetValue("FileName"); @@ -90,16 +93,12 @@ private static Task ParseShellNewRegistryEntry(RegistryKey key, R if (dataObj is not null) { - switch (key.GetValueKind("Data")) + data = key.GetValueKind("Data") switch { - case RegistryValueKind.Binary: - data = (byte[])dataObj; - break; - - case RegistryValueKind.String: - data = UTF8Encoding.UTF8.GetBytes((string)dataObj); - break; - } + RegistryValueKind.Binary => (byte[])dataObj, + RegistryValueKind.String => Encoding.UTF8.GetBytes((string)dataObj), + _ => (byte[])dataObj, + }; } return CreateShellNewEntry(extension, fileName, command, data); diff --git a/src/Files.App/Shell/ThreadWithMessageQueue.cs b/src/Files.App/Shell/ThreadWithMessageQueue.cs index 426cdeeba222..47feb3fd697c 100644 --- a/src/Files.App/Shell/ThreadWithMessageQueue.cs +++ b/src/Files.App/Shell/ThreadWithMessageQueue.cs @@ -2,11 +2,9 @@ // Licensed under the MIT License. See the LICENSE. using System.Collections.Concurrent; -using System.Runtime.Versioning; namespace Files.App.Shell { - [SupportedOSPlatform("Windows")] public class ThreadWithMessageQueue : Disposable { private readonly BlockingCollection messageQueue; diff --git a/src/Files.App/Shell/Win32API.cs b/src/Files.App/Shell/Win32API.cs index 030f41ca4338..eda05bcdc1f3 100644 --- a/src/Files.App/Shell/Win32API.cs +++ b/src/Files.App/Shell/Win32API.cs @@ -7,7 +7,6 @@ using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Text; using System.Windows.Forms; using Vanara.PInvoke; @@ -15,7 +14,9 @@ namespace Files.App.Shell { - [SupportedOSPlatform("Windows10.0.10240")] + /// + /// Provides static helper for general Win32API. + /// internal class Win32API { public static Task StartSTATask(Func func) @@ -250,7 +251,7 @@ public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path, if (!onlyGetOverlay) { using var shellItem = SafetyExtensions.IgnoreExceptions(() - => ShellFolderExtensions.GetShellItemFromPathOrPidl(path)); + => ShellFolderExtensions.GetShellItemFromPathOrPIDL(path)); if (shellItem is not null && shellItem.IShellItem is Shell32.IShellItemImageFactory fctry) { @@ -277,7 +278,7 @@ public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path, // Cannot access file, use file attributes var useFileAttibutes = !onlyGetOverlay && iconData is null; - var ret = ShellFolderExtensions.GetStringAsPidl(path, out var pidl) ? + var ret = ShellFolderExtensions.GetStringAsPIDL(path, out var pidl) ? Shell32.SHGetFileInfo(pidl, 0, ref shfi, Shell32.SHFILEINFO.Size, Shell32.SHGFI.SHGFI_PIDL | flags) : Shell32.SHGetFileInfo(path, isFolder ? FileAttributes.Directory : 0, ref shfi, Shell32.SHFILEINFO.Size, flags | (useFileAttibutes ? Shell32.SHGFI.SHGFI_USEFILEATTRIBUTES : 0)); if (ret == IntPtr.Zero) diff --git a/src/Files.App/Shell/Win32Shell.cs b/src/Files.App/Shell/Win32Shell.cs index c7cd6e35f541..9d838b8d0336 100644 --- a/src/Files.App/Shell/Win32Shell.cs +++ b/src/Files.App/Shell/Win32Shell.cs @@ -8,17 +8,20 @@ namespace Files.App.Shell { + /// + /// Provides a utility to manage shell folders. + /// public class Win32Shell { - private static ShellFolder controlPanel; + private readonly static ShellFolder _controlPanel; - private static ShellFolder controlPanelCategoryView; + private readonly static ShellFolder _controlPanelCategoryView; static Win32Shell() { - controlPanel = new ShellFolder(Shell32.KNOWNFOLDERID.FOLDERID_ControlPanelFolder); + _controlPanel = new ShellFolder(Shell32.KNOWNFOLDERID.FOLDERID_ControlPanelFolder); - controlPanelCategoryView = new ShellFolder("::{26EE0668-A00A-44D7-9371-BEB064C98683}"); + _controlPanelCategoryView = new ShellFolder("::{26EE0668-A00A-44D7-9371-BEB064C98683}"); } public static async Task<(ShellFileItem Folder, List Enumerate)> GetShellFolderAsync(string path, string action, int from, int count, params string[] properties) @@ -35,11 +38,11 @@ static Win32Shell() try { - using var shellFolder = ShellFolderExtensions.GetShellItemFromPathOrPidl(path) as ShellFolder; + using var shellFolder = ShellFolderExtensions.GetShellItemFromPathOrPIDL(path) as ShellFolder; if (shellFolder is null || - (controlPanel.PIDL.IsParentOf(shellFolder.PIDL, false) || - controlPanelCategoryView.PIDL.IsParentOf(shellFolder.PIDL, false)) && + (_controlPanel.PIDL.IsParentOf(shellFolder.PIDL, false) || + _controlPanelCategoryView.PIDL.IsParentOf(shellFolder.PIDL, false)) && !shellFolder.Any()) { // Return null to force open unsupported items in explorer diff --git a/src/Files.Shared/ContextMenu.cs b/src/Files.Shared/ContextMenu.cs index 9f2743578faf..1042b916f6f4 100644 --- a/src/Files.Shared/ContextMenu.cs +++ b/src/Files.Shared/ContextMenu.cs @@ -19,6 +19,21 @@ public enum MenuItemType : uint MFT_RIGHTJUSTIFY = 16384 } + public enum HBITMAP_HMENU : long + { + HBMMENU_CALLBACK = -1, + HBMMENU_MBAR_CLOSE = 5, + HBMMENU_MBAR_CLOSE_D = 6, + HBMMENU_MBAR_MINIMIZE = 3, + HBMMENU_MBAR_MINIMIZE_D = 7, + HBMMENU_MBAR_RESTORE = 2, + HBMMENU_POPUP_CLOSE = 8, + HBMMENU_POPUP_MAXIMIZE = 10, + HBMMENU_POPUP_MINIMIZE = 11, + HBMMENU_POPUP_RESTORE = 9, + HBMMENU_SYSTEM = 1 + } + public class Win32ContextMenu { public List Items { get; set; }