Skip to content

Commit b3ebcfb

Browse files
committed
Init
1 parent 7b4325a commit b3ebcfb

File tree

7 files changed

+379
-15
lines changed

7 files changed

+379
-15
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,12 @@ GetMenuItemCount
236236
GetMenuItemInfo
237237
IsWow64Process2
238238
GetCurrentProcess
239+
SHChangeNotifyRegister
240+
SHChangeNotifyDeregister
241+
SHChangeNotification_Lock
242+
SHChangeNotification_Unlock
243+
CoInitialize
244+
CoUninitialize
245+
PostQuitMessage
246+
HWND_MESSAGE
247+
SHCNE_ID
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using Windows.Foundation;
5+
6+
namespace Files.App.Storage
7+
{
8+
public interface IWindowsFolderWatcher : IFolderWatcher
9+
{
10+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? EventOccurred;
11+
12+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemAssocChanged; // SHCNE_ASSOCCHANGED
13+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemAttributesChanged; // SHCNE_ATTRIBUTES
14+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemImageUpdated; // SHCNE_UPDATEIMAGE
15+
16+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileRenamed; // SHCNE_RENAMEITEM
17+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileCreated; // SHCNE_CREATE
18+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileDeleted; // SHCNE_DELETE
19+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileUpdated; // SHCNE_UPDATEITEM
20+
21+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderRenamed; // SHCNE_RENAMEFOLDER
22+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderCreated; // SHCNE_MKDIR
23+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderDeleted; // SHCNE_RMDIR
24+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderUpdated; // SHCNE_UPDATEDIR
25+
26+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? MediaInserted; // SHCNE_MEDIAINSERTED
27+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? MediaRemoved; // SHCNE_MEDIAREMOVED
28+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveRemoved; // SHCNE_DRIVEREMOVED
29+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveAdded; // SHCNE_DRIVEADD
30+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveAddedViaGUI; // SHCNE_DRIVEADDGUI
31+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FreeSpaceUpdated; // SHCNE_FREESPACE
32+
33+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SharingStarted; // SHCNE_NETSHARE
34+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SharingStopped; // SHCNE_NETUNSHARE
35+
36+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DisconnectedFromServer; // SHCNE_SERVERDISCONNECT
37+
38+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ExtendedEventOccurred; // SHCNE_EXTENDED_EVENT
39+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SystemInterruptOccurred; // SHCNE_INTERRUPT
40+
}
41+
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,11 @@ public override void Dispose()
8181

8282
if (ShellNewMenu is not null) ShellNewMenu->Release();
8383
}
84+
85+
public Task<IFolderWatcher> GetFolderWatcherAsync(CancellationToken cancellationToken = default)
86+
{
87+
IFolderWatcher watcher = new WindowsFolderWatcher(this);
88+
return Task.FromResult(watcher);
89+
}
8490
}
8591
}
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Specialized;
5+
using System.Runtime.InteropServices;
6+
using Windows.Foundation;
7+
using Windows.Win32;
8+
using Windows.Win32.Foundation;
9+
using Windows.Win32.System.Com;
10+
using Windows.Win32.UI.Shell;
11+
using Windows.Win32.UI.Shell.Common;
12+
using Windows.Win32.UI.WindowsAndMessaging;
13+
14+
namespace Files.App.Storage
15+
{
16+
/// <summary>
17+
/// Represents an implementation of <see cref="IFolderWatcher"/> that uses Windows Shell notifications to watch for changes in a folder.
18+
/// </summary>
19+
public unsafe partial class WindowsFolderWatcher : IWindowsFolderWatcher
20+
{
21+
// Fields
22+
23+
private const uint WM_NOTIFYFOLDERCHANGE = PInvoke.WM_APP | 0x0001U;
24+
private readonly WNDPROC _wndProc;
25+
26+
private uint _watcherRegID = 0U;
27+
private ITEMIDLIST* _folderPidl = default;
28+
29+
// Properties
30+
31+
public IMutableFolder Folder { get; private set; }
32+
33+
// Events
34+
35+
public event NotifyCollectionChangedEventHandler? CollectionChanged;
36+
37+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? EventOccurred;
38+
39+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemAssocChanged; // SHCNE_ASSOCCHANGED
40+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemAttributesChanged; // SHCNE_ATTRIBUTES
41+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemImageUpdated; // SHCNE_UPDATEIMAGE
42+
43+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileRenamed; // SHCNE_RENAMEITEM
44+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileCreated; // SHCNE_CREATE
45+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileDeleted; // SHCNE_DELETE
46+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileUpdated; // SHCNE_UPDATEITEM
47+
48+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderRenamed; // SHCNE_RENAMEFOLDER
49+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderCreated; // SHCNE_MKDIR
50+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderDeleted; // SHCNE_RMDIR
51+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderUpdated; // SHCNE_UPDATEDIR
52+
53+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? MediaInserted; // SHCNE_MEDIAINSERTED
54+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? MediaRemoved; // SHCNE_MEDIAREMOVED
55+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveRemoved; // SHCNE_DRIVEREMOVED
56+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveAdded; // SHCNE_DRIVEADD
57+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveAddedViaGUI; // SHCNE_DRIVEADDGUI
58+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FreeSpaceUpdated; // SHCNE_FREESPACE
59+
60+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SharingStarted; // SHCNE_NETSHARE
61+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SharingStopped; // SHCNE_NETUNSHARE
62+
63+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DisconnectedFromServer; // SHCNE_SERVERDISCONNECT
64+
65+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ExtendedEventOccurred; // SHCNE_EXTENDED_EVENT
66+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SystemInterruptOccurred; // SHCNE_INTERRUPT
67+
68+
// Constructor
69+
70+
/// <summary>Initializes a new instance of the <see cref="WindowsFolderWatcher"/> class.</summary>
71+
/// <param name="folder">Specifies the folder to be monitored for changes.</param>
72+
public WindowsFolderWatcher(WindowsFolder folder)
73+
{
74+
Folder = folder;
75+
76+
fixed (char* pszClassName = $"FolderWatcherWindowClass{Guid.NewGuid():B}")
77+
{
78+
_wndProc = new(WndProc);
79+
80+
WNDCLASSEXW wndClass = default;
81+
wndClass.cbSize = (uint)sizeof(WNDCLASSEXW);
82+
wndClass.lpfnWndProc = (delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal.GetFunctionPointerForDelegate(_wndProc);
83+
wndClass.hInstance = PInvoke.GetModuleHandle(default(PWSTR));
84+
wndClass.lpszClassName = pszClassName;
85+
86+
PInvoke.RegisterClassEx(&wndClass);
87+
PInvoke.CreateWindowEx(0, pszClassName, null, 0, 0, 0, 0, 0, HWND.HWND_MESSAGE, default, wndClass.hInstance, null);
88+
}
89+
}
90+
91+
// Methods
92+
93+
private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM lParam)
94+
{
95+
switch (uMessage)
96+
{
97+
case PInvoke.WM_CREATE:
98+
{
99+
PInvoke.CoInitialize();
100+
101+
ITEMIDLIST* pidl = default;
102+
IWindowsFolder folder = (IWindowsFolder)Folder;
103+
PInvoke.SHGetIDListFromObject((IUnknown*)folder.ThisPtr.Get(), &pidl);
104+
_folderPidl = pidl;
105+
106+
SHChangeNotifyEntry changeNotifyEntry = default;
107+
changeNotifyEntry.pidl = pidl;
108+
109+
_watcherRegID = PInvoke.SHChangeNotifyRegister(
110+
hWnd,
111+
SHCNRF_SOURCE.SHCNRF_ShellLevel | SHCNRF_SOURCE.SHCNRF_NewDelivery,
112+
(int)SHCNE_ID.SHCNE_ALLEVENTS,
113+
WM_NOTIFYFOLDERCHANGE,
114+
1,
115+
&changeNotifyEntry);
116+
117+
if (_watcherRegID is 0U)
118+
break;
119+
}
120+
break;
121+
case WM_NOTIFYFOLDERCHANGE:
122+
{
123+
ITEMIDLIST** ppidl;
124+
uint lEvent = 0;
125+
HANDLE hLock = PInvoke.SHChangeNotification_Lock((HANDLE)(nint)wParam.Value, (uint)lParam.Value, &ppidl, (int*)&lEvent);
126+
127+
if (hLock.IsNull)
128+
break;
129+
130+
FireEvent((SHCNE_ID)lEvent, ppidl);
131+
132+
PInvoke.SHChangeNotification_Unlock(hLock);
133+
}
134+
break;
135+
case PInvoke.WM_DESTROY:
136+
{
137+
Dispose();
138+
}
139+
break;
140+
}
141+
142+
return PInvoke.DefWindowProc(hWnd, uMessage, wParam, lParam);
143+
}
144+
145+
private void FireEvent(SHCNE_ID eventType, ITEMIDLIST** ppidl)
146+
{
147+
//ITEMIDLIST* pOldPidl = ppidl[0];
148+
//ITEMIDLIST* pNewPidl = ppidl[1];
149+
150+
EventOccurred?.Invoke(this, new(eventType, null, null)); // WindowsStorable.TryParse(pOldPidl), WindowsStorable.TryParse(pNewPidl)
151+
152+
switch (eventType)
153+
{
154+
case SHCNE_ID.SHCNE_ASSOCCHANGED:
155+
{
156+
ItemAssocChanged?.Invoke(this, new(eventType));
157+
}
158+
break;
159+
case SHCNE_ID.SHCNE_ATTRIBUTES:
160+
{
161+
ItemAttributesChanged?.Invoke(this, new(eventType));
162+
}
163+
break;
164+
case SHCNE_ID.SHCNE_UPDATEIMAGE:
165+
{
166+
ItemImageUpdated?.Invoke(this, new(eventType));
167+
}
168+
break;
169+
case SHCNE_ID.SHCNE_RENAMEITEM:
170+
{
171+
FileRenamed?.Invoke(this, new(eventType));
172+
}
173+
break;
174+
case SHCNE_ID.SHCNE_CREATE:
175+
{
176+
FileCreated?.Invoke(this, new(eventType));
177+
}
178+
break;
179+
case SHCNE_ID.SHCNE_DELETE:
180+
{
181+
FileDeleted?.Invoke(this, new(eventType));
182+
}
183+
break;
184+
case SHCNE_ID.SHCNE_UPDATEITEM:
185+
{
186+
FileUpdated?.Invoke(this, new(eventType));
187+
}
188+
break;
189+
case SHCNE_ID.SHCNE_RENAMEFOLDER:
190+
{
191+
FolderRenamed?.Invoke(this, new(eventType));
192+
}
193+
break;
194+
case SHCNE_ID.SHCNE_MKDIR:
195+
{
196+
FolderCreated?.Invoke(this, new(eventType));
197+
}
198+
break;
199+
case SHCNE_ID.SHCNE_RMDIR:
200+
{
201+
FolderDeleted?.Invoke(this, new(eventType));
202+
}
203+
break;
204+
case SHCNE_ID.SHCNE_UPDATEDIR:
205+
{
206+
FolderUpdated?.Invoke(this, new(eventType));
207+
}
208+
break;
209+
case SHCNE_ID.SHCNE_MEDIAINSERTED:
210+
{
211+
MediaInserted?.Invoke(this, new(eventType));
212+
}
213+
break;
214+
case SHCNE_ID.SHCNE_MEDIAREMOVED:
215+
{
216+
MediaRemoved?.Invoke(this, new(eventType));
217+
}
218+
break;
219+
case SHCNE_ID.SHCNE_DRIVEREMOVED:
220+
{
221+
DriveRemoved?.Invoke(this, new(eventType));
222+
}
223+
break;
224+
case SHCNE_ID.SHCNE_DRIVEADD:
225+
{
226+
DriveAdded?.Invoke(this, new(eventType));
227+
}
228+
break;
229+
case SHCNE_ID.SHCNE_DRIVEADDGUI:
230+
{
231+
DriveAddedViaGUI?.Invoke(this, new(eventType));
232+
}
233+
break;
234+
case SHCNE_ID.SHCNE_FREESPACE:
235+
{
236+
FreeSpaceUpdated?.Invoke(this, new(eventType));
237+
}
238+
break;
239+
case SHCNE_ID.SHCNE_NETSHARE:
240+
{
241+
SharingStarted?.Invoke(this, new(eventType));
242+
}
243+
break;
244+
case SHCNE_ID.SHCNE_NETUNSHARE:
245+
{
246+
SharingStopped?.Invoke(this, new(eventType));
247+
}
248+
break;
249+
case SHCNE_ID.SHCNE_SERVERDISCONNECT:
250+
{
251+
DisconnectedFromServer?.Invoke(this, new(eventType));
252+
}
253+
break;
254+
case SHCNE_ID.SHCNE_EXTENDED_EVENT:
255+
{
256+
ExtendedEventOccurred?.Invoke(this, new(eventType));
257+
}
258+
break;
259+
case SHCNE_ID.SHCNE_INTERRUPT:
260+
{
261+
SystemInterruptOccurred?.Invoke(this, new(eventType));
262+
}
263+
break;
264+
}
265+
}
266+
267+
// Disposers
268+
269+
public void Dispose()
270+
{
271+
PInvoke.SHChangeNotifyDeregister(_watcherRegID);
272+
PInvoke.CoTaskMemFree(_folderPidl);
273+
PInvoke.CoUninitialize();
274+
PInvoke.PostQuitMessage(0);
275+
}
276+
277+
public ValueTask DisposeAsync()
278+
{
279+
Dispose();
280+
281+
return ValueTask.CompletedTask;
282+
}
283+
}
284+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using Windows.Win32.UI.Shell;
5+
6+
namespace Files.App.Storage
7+
{
8+
public class WindowsFolderWatcherEventArgs : EventArgs
9+
{
10+
public SHCNE_ID EventType { get; init; }
11+
12+
public IWindowsStorable? OldItem { get; init; }
13+
14+
public IWindowsStorable? NewItem { get; init; }
15+
16+
public WindowsFolderWatcherEventArgs(SHCNE_ID eventType, IWindowsStorable? _oldItem = null, IWindowsStorable? _newItem = null)
17+
{
18+
EventType = eventType;
19+
OldItem = _oldItem;
20+
NewItem = _newItem;
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)