diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt index 9d7c337d7c4dd..a64db000acfd7 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -133,3 +133,8 @@ IFileOperation IShellItem2 PSGetPropertyKeyFromName ShellExecuteEx +BHID_EnumItems +CoTaskMemFree +FOLDERID_NetHood +IShellLinkW +SLGP_FLAGS diff --git a/src/Files.App/Services/Storage/StorageNetworkService.cs b/src/Files.App/Services/Storage/StorageNetworkService.cs index d784303d76115..0df5a924fff5b 100644 --- a/src/Files.App/Services/Storage/StorageNetworkService.cs +++ b/src/Files.App/Services/Storage/StorageNetworkService.cs @@ -1,14 +1,13 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using System.Runtime.InteropServices; using System.Text; -using Vanara.PInvoke; -using Vanara.Windows.Shell; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.NetworkManagement.WNet; using Windows.Win32.Security.Credentials; +using Windows.Win32.System.SystemServices; +using Windows.Win32.UI.Shell; namespace Files.App.Services { @@ -16,9 +15,6 @@ public sealed class NetworkService : ObservableObject, INetworkService { private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); - private readonly static string guid = "::{f02c1a0d-be21-4350-88b0-7367fc96ef3c}"; - - private ObservableCollection _Computers = []; /// public ObservableCollection Computers @@ -40,102 +36,164 @@ public ObservableCollection Shortcuts /// public NetworkService() { - var networkItem = new DriveItem() + var item = new DriveItem() { DeviceID = "network-folder", Text = "Network".GetLocalizedResource(), Path = Constants.UserEnvironmentPaths.NetworkFolderPath, Type = DriveType.Network, ItemType = NavigationControlItemType.Drive, + MenuOptions = new ContextMenuOptions() + { + IsLocationItem = true, + ShowShellItems = true, + ShowProperties = true, + }, }; - networkItem.MenuOptions = new ContextMenuOptions() - { - IsLocationItem = true, - ShowEjectDevice = networkItem.IsRemovable, - ShowShellItems = true, - ShowProperties = true, - }; + item.MenuOptions.ShowEjectDevice = item.IsRemovable; + lock (_Computers) - _Computers.Add(networkItem); + _Computers.Add(item); } /// public async Task> GetComputersAsync() { - var result = await Win32Helper.GetShellFolderAsync(guid, false, true, 0, int.MaxValue); + return await Task.Run(GetComputers); - return result.Enumerate.Where(item => item.IsFolder).Select(item => + unsafe IEnumerable GetComputers() { - var networkItem = new DriveItem() + HRESULT hr = default; + + // Get IShellItem of the shell folder + var shellItemIid = typeof(IShellItem).GUID; + using ComPtr pFolderShellItem = default; + fixed (char* pszFolderShellPath = "Shell:::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}") + hr = PInvoke.SHCreateItemFromParsingName(pszFolderShellPath, null, &shellItemIid, (void**)pFolderShellItem.GetAddressOf()); + + // Get IEnumShellItems of the shell folder + var enumItemsBHID = PInvoke.BHID_EnumItems; + Guid enumShellItemIid = typeof(IEnumShellItems).GUID; + using ComPtr pEnumShellItems = default; + hr = pFolderShellItem.Get()->BindToHandler(null, &enumItemsBHID, &enumShellItemIid, (void**)pEnumShellItems.GetAddressOf()); + + // Enumerate items and populate the list + List items = []; + using ComPtr pShellItem = default; + while (pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()) == HRESULT.S_OK) { - Text = item.FileName, - Path = item.FilePath, - DeviceID = item.FilePath, - Type = DriveType.Network, - ItemType = NavigationControlItemType.Drive, - }; - - networkItem.MenuOptions = new ContextMenuOptions() - { - IsLocationItem = true, - ShowEjectDevice = networkItem.IsRemovable, - ShowShellItems = true, - ShowProperties = true, - }; + // Get only folders + if (pShellItem.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var attribute) == HRESULT.S_OK && + (attribute & SFGAO_FLAGS.SFGAO_FOLDER) is not SFGAO_FLAGS.SFGAO_FOLDER) + continue; + + // Get the target path + using ComPtr pShellLink = default; + var shellLinkIid = typeof(IShellLinkW).GUID; + pShellItem.Get()->QueryInterface(&shellLinkIid, (void**)pShellLink.GetAddressOf()); + string targetPath = string.Empty; + fixed (char* pszTargetPath = new char[1024]) + { + hr = pShellLink.Get()->GetPath(pszTargetPath, 1024, null, (uint)SLGP_FLAGS.SLGP_RAWPATH); + targetPath = Environment.ExpandEnvironmentVariables(new PWSTR(pszTargetPath).ToString()); + } - return networkItem; - }); + // Get the display name + pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY, out var szDisplayName); + var fileName = szDisplayName.ToString(); + PInvoke.CoTaskMemFree(szDisplayName.Value); + + var item = new DriveItem() + { + Text = fileName, + Path = targetPath, + DeviceID = targetPath, + Type = DriveType.Network, + ItemType = NavigationControlItemType.Drive, + MenuOptions = new() + { + IsLocationItem = true, + ShowShellItems = true, + ShowProperties = true, + }, + }; + + item.MenuOptions.ShowEjectDevice = item.IsRemovable; + + items.Add(item); + } + + return items; + } } /// public async Task> GetShortcutsAsync() { - var networkLocations = await Win32Helper.StartSTATask(() => + return await Task.Run(GetShortcuts); + + unsafe IEnumerable GetShortcuts() { - var locations = new List(); - using (var netHood = new ShellFolder(Shell32.KNOWNFOLDERID.FOLDERID_NetHood)) + // Get IShellItem of the known folder + using ComPtr pShellFolder = default; + var folderId = PInvoke.FOLDERID_NetHood; + var shellItemIid = typeof(IShellItem).GUID; + HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderId, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, &shellItemIid, (void**)pShellFolder.GetAddressOf()); + + // Get IEnumShellItems for Recycle Bin folder + using ComPtr pEnumShellItems = default; + Guid enumShellItemGuid = typeof(IEnumShellItems).GUID; + var enumItemsBHID = BHID.BHID_EnumItems; + hr = pShellFolder.Get()->BindToHandler(null, &enumItemsBHID, &enumShellItemGuid, (void**)pEnumShellItems.GetAddressOf()); + + List items = []; + using ComPtr pShellItem = default; + while (pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()) == HRESULT.S_OK) { - foreach (var item in netHood) + // Get the target path + using ComPtr pShellLink = default; + var shellLinkIid = typeof(IShellLinkW).GUID; + pShellItem.Get()->QueryInterface(&shellLinkIid, (void**)pShellLink.GetAddressOf()); + string targetPath = string.Empty; + fixed (char* pszTargetPath = new char[1024]) { - if (item is ShellLink link) - { - locations.Add(ShellFolderExtensions.GetShellLinkItem(link)); - } - else - { - var linkPath = (string?)item?.Properties["System.Link.TargetParsingPath"]; - if (linkPath is not null) - { - var linkItem = ShellFolderExtensions.GetShellFileItem(item); - locations.Add(new(linkItem) { TargetPath = linkPath }); - } - } + hr = pShellLink.Get()->GetPath(pszTargetPath, 1024, null, (uint)SLGP_FLAGS.SLGP_RAWPATH); + targetPath = Environment.ExpandEnvironmentVariables(new PWSTR(pszTargetPath).ToString()); } + + // Get the display name + pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY, out var szDisplayName); + var fileName = szDisplayName.ToString(); + PInvoke.CoTaskMemFree(szDisplayName.Value); + + // Get the file system path on disk + pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out szDisplayName); + var filePath = szDisplayName.ToString(); + PInvoke.CoTaskMemFree(szDisplayName.Value); + + var item = new DriveItem() + { + Text = fileName, + Path = targetPath, + DeviceID = filePath, + Type = DriveType.Network, + ItemType = NavigationControlItemType.Drive, + MenuOptions = new() + { + IsLocationItem = true, + ShowShellItems = true, + ShowProperties = true, + }, + }; + + item.MenuOptions.ShowEjectDevice = item.IsRemovable; + + items.Add(item); } - return locations; - }); - return (networkLocations ?? Enumerable.Empty()).Select(item => - { - var networkItem = new DriveItem() - { - Text = item.FileName, - Path = item.TargetPath, - DeviceID = item.FilePath, - Type = DriveType.Network, - ItemType = NavigationControlItemType.Drive, - }; - - networkItem.MenuOptions = new ContextMenuOptions() - { - IsLocationItem = true, - ShowEjectDevice = networkItem.IsRemovable, - ShowShellItems = true, - ShowProperties = true, - }; - return networkItem; - }); + return items; + } } ///