Skip to content

Commit 6a85db5

Browse files
committed
Init
1 parent 4575b08 commit 6a85db5

File tree

10 files changed

+349
-15
lines changed

10 files changed

+349
-15
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,12 @@ QITIPF_FLAGS
225225
GetKeyboardState
226226
MapVirtualKey
227227
GetKeyboardLayout
228+
SHChangeNotifyRegister
229+
SHChangeNotifyDeregister
230+
SHChangeNotification_Lock
231+
SHChangeNotification_Unlock
232+
CoInitialize
233+
CoUninitialize
234+
PostQuitMessage
235+
HWND_MESSAGE
236+
SHCNE_ID
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
namespace Files.App.Storage
5+
{
6+
public interface IWindowsFile : IWindowsStorable, IChildFile
7+
{
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
namespace Files.App.Storage
5+
{
6+
public interface IWindowsFolder : IWindowsStorable, IChildFolder, IMutableFolder
7+
{
8+
}
9+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Files.App.Storage
88
{
9-
public interface IWindowsStorable : IDisposable
9+
public interface IWindowsStorable : IStorableChild, IEquatable<IWindowsStorable>, IDisposable
1010
{
1111
ComPtr<IShellItem> ThisPtr { get; }
1212
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace Files.App.Storage
99
{
1010
[DebuggerDisplay("{" + nameof(ToString) + "()}")]
11-
public sealed class WindowsFile : WindowsStorable, IChildFile
11+
public sealed class WindowsFile : WindowsStorable, IWindowsFile
1212
{
1313
public WindowsFile(ComPtr<IShellItem> nativeObject)
1414
{

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace Files.App.Storage
1111
{
1212
[DebuggerDisplay("{" + nameof(ToString) + "()}")]
13-
public sealed class WindowsFolder : WindowsStorable, IChildFolder
13+
public sealed class WindowsFolder : WindowsStorable, IWindowsFolder
1414
{
1515
public WindowsFolder(ComPtr<IShellItem> nativeObject)
1616
{
@@ -85,5 +85,11 @@ unsafe bool GetNext()
8585
}
8686
}
8787
}
88+
89+
public Task<IFolderWatcher> GetFolderWatcherAsync(CancellationToken cancellationToken = default)
90+
{
91+
IFolderWatcher watcher = new WindowsFolderWatcher(this);
92+
return Task.FromResult(watcher);
93+
}
8894
}
8995
}
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
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 : IFolderWatcher
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* _targetItemPIDL = 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>? ItemAssocChanged; // SHCNE_ASSOCCHANGED
38+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemAttributesChanged; // SHCNE_ATTRIBUTES
39+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ItemImageUpdated; // SHCNE_UPDATEIMAGE
40+
41+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileRenamed; // SHCNE_RENAMEITEM
42+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileCreated; // SHCNE_CREATE
43+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileDeleted; // SHCNE_DELETE
44+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FileUpdated; // SHCNE_UPDATEITEM
45+
46+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderRenamed; // SHCNE_RENAMEFOLDER
47+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderCreated; // SHCNE_MKDIR
48+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderDeleted; // SHCNE_RMDIR
49+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FolderUpdated; // SHCNE_UPDATEDIR
50+
51+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? MediaInserted; // SHCNE_MEDIAINSERTED
52+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? MediaRemoved; // SHCNE_MEDIAREMOVED
53+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveRemoved; // SHCNE_DRIVEREMOVED
54+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveAdded; // SHCNE_DRIVEADD
55+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DriveAddedViaGUI; // SHCNE_DRIVEADDGUI
56+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? FreeSpaceUpdated; // SHCNE_FREESPACE
57+
58+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SharingStarted; // SHCNE_NETSHARE
59+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SharingStopped; // SHCNE_NETUNSHARE
60+
61+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? DisconnectedFromServer; // SHCNE_SERVERDISCONNECT
62+
63+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? ExtendedEventOccurred; // SHCNE_EXTENDED_EVENT
64+
public event TypedEventHandler<WindowsFolderWatcher, WindowsFolderWatcherEventArgs>? SystemInterruptOccurred; // SHCNE_INTERRUPT
65+
66+
// Constructor
67+
68+
/// <summary>Initializes a new instance of the <see cref="WindowsFolderWatcher"/> class.</summary>
69+
/// <param name="folder">Specifies the folder to be monitored for changes.</param>
70+
public WindowsFolderWatcher(WindowsFolder folder)
71+
{
72+
Folder = folder;
73+
74+
fixed (char* pszClassName = $"FolderWatcherWindowClass{Guid.NewGuid():B}")
75+
{
76+
_wndProc = new(WndProc);
77+
78+
WNDCLASSEXW wndClass = default;
79+
wndClass.cbSize = (uint)sizeof(WNDCLASSEXW);
80+
wndClass.lpfnWndProc = (delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal.GetFunctionPointerForDelegate(_wndProc);
81+
wndClass.hInstance = PInvoke.GetModuleHandle(default(PWSTR));
82+
wndClass.lpszClassName = pszClassName;
83+
84+
PInvoke.RegisterClassEx(&wndClass);
85+
PInvoke.CreateWindowEx(0, pszClassName, null, 0, 0, 0, 0, 0, HWND.HWND_MESSAGE, default, wndClass.hInstance, null);
86+
}
87+
}
88+
89+
private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM lParam)
90+
{
91+
switch (uMessage)
92+
{
93+
case PInvoke.WM_CREATE:
94+
{
95+
PInvoke.CoInitialize();
96+
97+
ITEMIDLIST* pidl = default;
98+
IWindowsFolder folder = (IWindowsFolder)Folder;
99+
PInvoke.SHGetIDListFromObject((IUnknown*)folder.ThisPtr.Get(), &pidl);
100+
_targetItemPIDL = pidl;
101+
102+
SHChangeNotifyEntry changeNotifyEntry = default;
103+
changeNotifyEntry.pidl = pidl;
104+
105+
_watcherRegID = PInvoke.SHChangeNotifyRegister(
106+
hWnd,
107+
SHCNRF_SOURCE.SHCNRF_ShellLevel | SHCNRF_SOURCE.SHCNRF_NewDelivery,
108+
(int)SHCNE_ID.SHCNE_ALLEVENTS,
109+
WM_NOTIFYFOLDERCHANGE,
110+
1,
111+
&changeNotifyEntry);
112+
113+
if (_watcherRegID is 0U)
114+
break;
115+
}
116+
break;
117+
case WM_NOTIFYFOLDERCHANGE:
118+
{
119+
ITEMIDLIST** ppidl;
120+
uint lEvent = 0;
121+
HANDLE hLock = PInvoke.SHChangeNotification_Lock((HANDLE)(nint)wParam.Value, (uint)lParam.Value, &ppidl, (int*)&lEvent);
122+
123+
if (hLock.IsNull)
124+
break;
125+
126+
// TODO: Fire events
127+
128+
PInvoke.SHChangeNotification_Unlock(hLock);
129+
}
130+
break;
131+
case PInvoke.WM_DESTROY:
132+
{
133+
Dispose();
134+
}
135+
break;
136+
}
137+
138+
return PInvoke.DefWindowProc(hWnd, uMessage, wParam, lParam);
139+
}
140+
141+
private void FireEvent(SHCNE_ID eventType, ITEMIDLIST** ppidl)
142+
{
143+
switch (eventType)
144+
{
145+
case SHCNE_ID.SHCNE_ASSOCCHANGED:
146+
{
147+
ItemAssocChanged?.Invoke(this, new(eventType));
148+
}
149+
break;
150+
case SHCNE_ID.SHCNE_ATTRIBUTES:
151+
{
152+
ItemAttributesChanged?.Invoke(this, new(eventType));
153+
}
154+
break;
155+
case SHCNE_ID.SHCNE_UPDATEIMAGE:
156+
{
157+
ItemImageUpdated?.Invoke(this, new(eventType));
158+
}
159+
break;
160+
case SHCNE_ID.SHCNE_RENAMEITEM:
161+
{
162+
FileRenamed?.Invoke(this, new(eventType));
163+
}
164+
break;
165+
case SHCNE_ID.SHCNE_CREATE:
166+
{
167+
FileCreated?.Invoke(this, new(eventType));
168+
}
169+
break;
170+
case SHCNE_ID.SHCNE_DELETE:
171+
{
172+
FileDeleted?.Invoke(this, new(eventType));
173+
}
174+
break;
175+
case SHCNE_ID.SHCNE_UPDATEITEM:
176+
{
177+
FileUpdated?.Invoke(this, new(eventType));
178+
}
179+
break;
180+
case SHCNE_ID.SHCNE_RENAMEFOLDER:
181+
{
182+
FolderRenamed?.Invoke(this, new(eventType));
183+
}
184+
break;
185+
case SHCNE_ID.SHCNE_MKDIR:
186+
{
187+
FolderCreated?.Invoke(this, new(eventType));
188+
}
189+
break;
190+
case SHCNE_ID.SHCNE_RMDIR:
191+
{
192+
FolderDeleted?.Invoke(this, new(eventType));
193+
}
194+
break;
195+
case SHCNE_ID.SHCNE_UPDATEDIR:
196+
{
197+
FolderUpdated?.Invoke(this, new(eventType));
198+
}
199+
break;
200+
case SHCNE_ID.SHCNE_MEDIAINSERTED:
201+
{
202+
MediaInserted?.Invoke(this, new(eventType));
203+
}
204+
break;
205+
case SHCNE_ID.SHCNE_MEDIAREMOVED:
206+
{
207+
MediaRemoved?.Invoke(this, new(eventType));
208+
}
209+
break;
210+
case SHCNE_ID.SHCNE_DRIVEREMOVED:
211+
{
212+
DriveRemoved?.Invoke(this, new(eventType));
213+
}
214+
break;
215+
case SHCNE_ID.SHCNE_DRIVEADD:
216+
{
217+
DriveAdded?.Invoke(this, new(eventType));
218+
}
219+
break;
220+
case SHCNE_ID.SHCNE_DRIVEADDGUI:
221+
{
222+
DriveAddedViaGUI?.Invoke(this, new(eventType));
223+
}
224+
break;
225+
case SHCNE_ID.SHCNE_FREESPACE:
226+
{
227+
FreeSpaceUpdated?.Invoke(this, new(eventType));
228+
}
229+
break;
230+
case SHCNE_ID.SHCNE_NETSHARE:
231+
{
232+
SharingStarted?.Invoke(this, new(eventType));
233+
}
234+
break;
235+
case SHCNE_ID.SHCNE_NETUNSHARE:
236+
{
237+
SharingStopped?.Invoke(this, new(eventType));
238+
}
239+
break;
240+
case SHCNE_ID.SHCNE_SERVERDISCONNECT:
241+
{
242+
DisconnectedFromServer?.Invoke(this, new(eventType));
243+
}
244+
break;
245+
case SHCNE_ID.SHCNE_EXTENDED_EVENT:
246+
{
247+
ExtendedEventOccurred?.Invoke(this, new(eventType));
248+
}
249+
break;
250+
case SHCNE_ID.SHCNE_INTERRUPT:
251+
{
252+
SystemInterruptOccurred?.Invoke(this, new(eventType));
253+
}
254+
break;
255+
}
256+
}
257+
258+
public void Dispose()
259+
{
260+
PInvoke.SHChangeNotifyDeregister(_watcherRegID);
261+
PInvoke.CoTaskMemFree(_targetItemPIDL);
262+
PInvoke.CoUninitialize();
263+
PInvoke.PostQuitMessage(0);
264+
}
265+
266+
public ValueTask DisposeAsync()
267+
{
268+
Dispose();
269+
270+
return ValueTask.CompletedTask;
271+
}
272+
}
273+
}
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+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace Files.App.Storage
1010
{
11-
public abstract class WindowsStorable : IWindowsStorable, IStorableChild, IEquatable<IWindowsStorable>
11+
public abstract class WindowsStorable : IWindowsStorable
1212
{
1313
public ComPtr<IShellItem> ThisPtr { get; protected set; }
1414

0 commit comments

Comments
 (0)