Skip to content

Commit c420f19

Browse files
committed
Init
1 parent 56df483 commit c420f19

File tree

2 files changed

+150
-71
lines changed

2 files changed

+150
-71
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,8 @@ IFileOperation
133133
IShellItem2
134134
PSGetPropertyKeyFromName
135135
ShellExecuteEx
136+
BHID_EnumItems
137+
CoTaskMemFree
138+
FOLDERID_NetHood
139+
IShellLinkW
140+
SLGP_FLAGS

src/Files.App/Services/Storage/StorageNetworkService.cs

Lines changed: 145 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
// Copyright (c) 2024 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4-
using System.Runtime.InteropServices;
54
using System.Text;
6-
using Vanara.PInvoke;
7-
using Vanara.Windows.Shell;
85
using Windows.Win32;
96
using Windows.Win32.Foundation;
107
using Windows.Win32.NetworkManagement.WNet;
118
using Windows.Win32.Security.Credentials;
9+
using Windows.Win32.System.SystemServices;
10+
using Windows.Win32.UI.Shell;
1211

1312
namespace Files.App.Services
1413
{
1514
public sealed class NetworkService : ObservableObject, INetworkService
1615
{
1716
private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService<ICommonDialogService>();
1817

19-
private readonly static string guid = "::{f02c1a0d-be21-4350-88b0-7367fc96ef3c}";
20-
21-
2218
private ObservableCollection<ILocatableFolder> _Computers = [];
2319
/// <inheritdoc/>
2420
public ObservableCollection<ILocatableFolder> Computers
@@ -40,102 +36,180 @@ public ObservableCollection<ILocatableFolder> Shortcuts
4036
/// </summary>
4137
public NetworkService()
4238
{
43-
var networkItem = new DriveItem()
39+
var item = new DriveItem()
4440
{
4541
DeviceID = "network-folder",
4642
Text = "Network".GetLocalizedResource(),
4743
Path = Constants.UserEnvironmentPaths.NetworkFolderPath,
4844
Type = DriveType.Network,
4945
ItemType = NavigationControlItemType.Drive,
46+
MenuOptions = new ContextMenuOptions()
47+
{
48+
IsLocationItem = true,
49+
ShowShellItems = true,
50+
ShowProperties = true,
51+
},
5052
};
5153

52-
networkItem.MenuOptions = new ContextMenuOptions()
53-
{
54-
IsLocationItem = true,
55-
ShowEjectDevice = networkItem.IsRemovable,
56-
ShowShellItems = true,
57-
ShowProperties = true,
58-
};
54+
item.MenuOptions.ShowEjectDevice = item.IsRemovable;
55+
5956
lock (_Computers)
60-
_Computers.Add(networkItem);
57+
_Computers.Add(item);
6158
}
6259

6360
/// <inheritdoc/>
6461
public async Task<IEnumerable<ILocatableFolder>> GetComputersAsync()
6562
{
66-
var result = await Win32Helper.GetShellFolderAsync(guid, false, true, 0, int.MaxValue);
63+
return await Task.Run(GetComputers);
6764

68-
return result.Enumerate.Where(item => item.IsFolder).Select(item =>
65+
unsafe IEnumerable<ILocatableFolder> GetComputers()
6966
{
70-
var networkItem = new DriveItem()
67+
HRESULT hr = default;
68+
69+
// Get IShellItem of the shell folder
70+
var shellItemIid = typeof(IShellItem).GUID;
71+
using ComPtr<IShellItem> pFolderShellItem = default;
72+
fixed (char* pszFolderShellPath = "Shell:::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}")
73+
hr = PInvoke.SHCreateItemFromParsingName(pszFolderShellPath, null, &shellItemIid, (void**)pFolderShellItem.GetAddressOf());
74+
75+
// Get IEnumShellItems of the shell folder
76+
var enumItemsBHID = PInvoke.BHID_EnumItems;
77+
Guid enumShellItemIid = typeof(IEnumShellItems).GUID;
78+
using ComPtr<IEnumShellItems> pEnumShellItems = default;
79+
hr = pFolderShellItem.Get()->BindToHandler(null, &enumItemsBHID, &enumShellItemIid, (void**)pEnumShellItems.GetAddressOf());
80+
81+
// Enumerate items and populate the list
82+
List<ILocatableFolder> items = [];
83+
using ComPtr<IShellItem> pShellItem = default;
84+
while (pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()) == HRESULT.S_OK)
7185
{
72-
Text = item.FileName,
73-
Path = item.FilePath,
74-
DeviceID = item.FilePath,
75-
Type = DriveType.Network,
76-
ItemType = NavigationControlItemType.Drive,
77-
};
78-
79-
networkItem.MenuOptions = new ContextMenuOptions()
80-
{
81-
IsLocationItem = true,
82-
ShowEjectDevice = networkItem.IsRemovable,
83-
ShowShellItems = true,
84-
ShowProperties = true,
85-
};
86+
// Get only folders
87+
if (pShellItem.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var attribute) == HRESULT.S_OK &&
88+
(attribute & SFGAO_FLAGS.SFGAO_FOLDER) is not SFGAO_FLAGS.SFGAO_FOLDER)
89+
continue;
90+
91+
// Get the display name
92+
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY, out var szDisplayName);
93+
var fileName = szDisplayName.ToString();
94+
PInvoke.CoTaskMemFree(szDisplayName.Value);
95+
96+
// Get the file system path on disk
97+
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out szDisplayName);
98+
var filePath = szDisplayName.ToString();
99+
PInvoke.CoTaskMemFree(szDisplayName.Value);
100+
101+
var item = new DriveItem()
102+
{
103+
Text = fileName,
104+
Path = filePath,
105+
DeviceID = filePath,
106+
Type = DriveType.Network,
107+
ItemType = NavigationControlItemType.Drive,
108+
MenuOptions = new()
109+
{
110+
IsLocationItem = true,
111+
ShowShellItems = true,
112+
ShowProperties = true,
113+
},
114+
};
86115

87-
return networkItem;
88-
});
116+
item.MenuOptions.ShowEjectDevice = item.IsRemovable;
117+
118+
items.Add(item);
119+
}
120+
121+
return items;
122+
}
89123
}
90124

91125
/// <inheritdoc/>
92126
public async Task<IEnumerable<ILocatableFolder>> GetShortcutsAsync()
93127
{
94-
var networkLocations = await Win32Helper.StartSTATask(() =>
128+
return await Task.Run(GetShortcuts);
129+
130+
unsafe IEnumerable<ILocatableFolder> GetShortcuts()
95131
{
96-
var locations = new List<ShellLinkItem>();
97-
using (var netHood = new ShellFolder(Shell32.KNOWNFOLDERID.FOLDERID_NetHood))
132+
// Get IShellItem of the known folder
133+
using ComPtr<IShellItem> pShellFolder = default;
134+
var folderId = PInvoke.FOLDERID_NetHood;
135+
var shellItemIid = typeof(IShellItem).GUID;
136+
HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderId, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, &shellItemIid, (void**)pShellFolder.GetAddressOf());
137+
138+
// Get IEnumShellItems for Recycle Bin folder
139+
using ComPtr<IEnumShellItems> pEnumShellItems = default;
140+
Guid enumShellItemGuid = typeof(IEnumShellItems).GUID;
141+
var enumItemsBHID = BHID.BHID_EnumItems;
142+
hr = pShellFolder.Get()->BindToHandler(null, &enumItemsBHID, &enumShellItemGuid, (void**)pEnumShellItems.GetAddressOf());
143+
144+
List<ILocatableFolder> items = [];
145+
using ComPtr<IShellItem> pShellItem = default;
146+
while (pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()) == HRESULT.S_OK)
98147
{
99-
foreach (var item in netHood)
148+
// Get the target path
149+
using ComPtr<IShellLinkW> pShellLink = default;
150+
var shellLinkIid = typeof(IShellLinkW).GUID;
151+
pShellItem.Get()->QueryInterface(&shellLinkIid, (void**)pShellLink.GetAddressOf());
152+
string targetPath = string.Empty;
153+
if (pShellLink.IsNull)
100154
{
101-
if (item is ShellLink link)
102-
{
103-
locations.Add(ShellFolderExtensions.GetShellLinkItem(link));
104-
}
105-
else
155+
using ComPtr<IShellItem2> pShellItem2 = default;
156+
var shellItem2Iid = typeof(IShellItem2).GUID;
157+
pShellItem.Get()->QueryInterface(&shellItem2Iid, (void**)pShellItem2.GetAddressOf());
158+
PInvoke.PSGetPropertyKeyFromName("System.Link.TargetParsingPath", out var propertyKey);
159+
pShellItem2.Get()->GetString(propertyKey, out var pszTargetPath);
160+
targetPath = Environment.ExpandEnvironmentVariables(pszTargetPath.ToString());
161+
162+
// Test 1
163+
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var szDisplayNameTest);
164+
var filePathTest = szDisplayNameTest.ToString();
165+
PInvoke.CoTaskMemFree(szDisplayNameTest.Value); // break here
166+
167+
// Test 2
168+
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_PARENTRELATIVEPARSING, out szDisplayNameTest);
169+
filePathTest = szDisplayNameTest.ToString();
170+
PInvoke.CoTaskMemFree(szDisplayNameTest.Value); // break here
171+
}
172+
else
173+
{
174+
fixed (char* pszTargetPath = new char[1024])
106175
{
107-
var linkPath = (string?)item?.Properties["System.Link.TargetParsingPath"];
108-
if (linkPath is not null)
109-
{
110-
var linkItem = ShellFolderExtensions.GetShellFileItem(item);
111-
locations.Add(new(linkItem) { TargetPath = linkPath });
112-
}
176+
hr = pShellLink.Get()->GetPath(pszTargetPath, 1024, null, (uint)SLGP_FLAGS.SLGP_RAWPATH);
177+
targetPath = Environment.ExpandEnvironmentVariables(new PWSTR(pszTargetPath).ToString());
113178
}
114179
}
180+
181+
// Get the display name
182+
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY, out var szDisplayName);
183+
var fileName = szDisplayName.ToString();
184+
PInvoke.CoTaskMemFree(szDisplayName.Value);
185+
186+
// Get the file system path on disk
187+
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out szDisplayName);
188+
var filePath = szDisplayName.ToString();
189+
PInvoke.CoTaskMemFree(szDisplayName.Value);
190+
191+
var item = new DriveItem()
192+
{
193+
Text = fileName,
194+
Path = targetPath,
195+
DeviceID = filePath,
196+
Type = DriveType.Network,
197+
ItemType = NavigationControlItemType.Drive,
198+
MenuOptions = new()
199+
{
200+
IsLocationItem = true,
201+
ShowShellItems = true,
202+
ShowProperties = true,
203+
},
204+
};
205+
206+
item.MenuOptions.ShowEjectDevice = item.IsRemovable;
207+
208+
items.Add(item);
115209
}
116-
return locations;
117-
});
118210

119-
return (networkLocations ?? Enumerable.Empty<ShellLinkItem>()).Select(item =>
120-
{
121-
var networkItem = new DriveItem()
122-
{
123-
Text = item.FileName,
124-
Path = item.TargetPath,
125-
DeviceID = item.FilePath,
126-
Type = DriveType.Network,
127-
ItemType = NavigationControlItemType.Drive,
128-
};
129-
130-
networkItem.MenuOptions = new ContextMenuOptions()
131-
{
132-
IsLocationItem = true,
133-
ShowEjectDevice = networkItem.IsRemovable,
134-
ShowShellItems = true,
135-
ShowProperties = true,
136-
};
137-
return networkItem;
138-
});
211+
return items;
212+
}
139213
}
140214

141215
/// <inheritdoc/>

0 commit comments

Comments
 (0)