Skip to content

Commit ce31df3

Browse files
committed
Added a watcher for IWindowsFolder
1 parent 3211d47 commit ce31df3

File tree

5 files changed

+158
-12
lines changed

5 files changed

+158
-12
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,12 @@ _SICHINTF
226226
RoGetAgileReference
227227
IQueryInfo
228228
QITIPF_FLAGS
229+
SHChangeNotifyRegister
230+
SHChangeNotifyDeregister
231+
SHChangeNotification_Lock
232+
SHChangeNotification_Unlock
233+
CoInitialize
234+
CoUninitialize
235+
PostQuitMessage
236+
HWND_MESSAGE
237+
SHCNE_ID

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

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

44
namespace Files.App.Storage
55
{
6-
public interface IWindowsFolder : IWindowsStorable, IChildFolder
6+
public interface IWindowsFolder : IWindowsStorable, IChildFolder, IMutableFolder
77
{
88
}
99
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using OwlCore.Storage;
5+
using System.Collections.Concurrent;
6+
using System.Collections.Specialized;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using System.Threading;
10+
using Windows.Win32;
11+
using Windows.Win32.Foundation;
12+
using Windows.Win32.Graphics.Gdi;
13+
using Windows.Win32.System.Com;
14+
using Windows.Win32.UI.Shell;
15+
using Windows.Win32.UI.Shell.Common;
16+
using Windows.Win32.UI.WindowsAndMessaging;
17+
18+
namespace Files.App.Storage
19+
{
20+
public unsafe partial class WindowsFolderWatcher : IFolderWatcher
21+
{
22+
// Fields
23+
24+
private const uint WM_SHFLDRWATCHER = PInvoke.WM_APP | 0x0001U;
25+
private readonly WNDPROC _wndProc;
26+
27+
private uint _registrationID = 0U;
28+
private ITEMIDLIST* _pidl = default;
29+
30+
// Properties
31+
32+
public IMutableFolder Folder { get; private set; }
33+
34+
// Events
35+
36+
public event NotifyCollectionChangedEventHandler? CollectionChanged;
37+
38+
// Constructor
39+
40+
public WindowsFolderWatcher(WindowsFolder folder)
41+
{
42+
Folder = folder;
43+
44+
HINSTANCE hInst = PInvoke.GetModuleHandle(default(PWSTR));
45+
46+
_wndProc = new(WndProc);
47+
var pfnWndProc = (delegate* unmanaged[Stdcall]<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal.GetFunctionPointerForDelegate(_wndProc);
48+
49+
fixed (char* pszClassName = $"FolderWatcherWindowClass{Guid.NewGuid():B}")
50+
{
51+
WNDCLASSEXW wndClass = default;
52+
wndClass.cbSize = (uint)sizeof(WNDCLASSEXW);
53+
wndClass.lpfnWndProc = pfnWndProc;
54+
wndClass.hInstance = hInst;
55+
wndClass.lpszClassName = pszClassName;
56+
57+
PInvoke.RegisterClassEx(&wndClass);
58+
PInvoke.CreateWindowEx(0, pszClassName, null, 0, 0, 0, 0, 0, HWND.HWND_MESSAGE, default, hInst, null);
59+
}
60+
}
61+
62+
private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM lParam)
63+
{
64+
switch (uMessage)
65+
{
66+
case PInvoke.WM_CREATE:
67+
{
68+
PInvoke.CoInitialize();
69+
70+
ITEMIDLIST* pidl = default;
71+
IWindowsFolder folder = (IWindowsFolder)Folder;
72+
PInvoke.SHGetIDListFromObject((IUnknown*)folder.ThisPtr.Get(), &pidl);
73+
_pidl = pidl;
74+
75+
SHChangeNotifyEntry changeNotifyEntry = default;
76+
changeNotifyEntry.pidl = pidl;
77+
78+
_registrationID = PInvoke.SHChangeNotifyRegister(
79+
hWnd,
80+
SHCNRF_SOURCE.SHCNRF_ShellLevel | SHCNRF_SOURCE.SHCNRF_NewDelivery,
81+
(int)SHCNE_ID.SHCNE_ALLEVENTS,
82+
PInvoke.WM_APP | 1,
83+
1,
84+
&changeNotifyEntry);
85+
86+
if (_registrationID is 0U)
87+
break;
88+
}
89+
break;
90+
case PInvoke.WM_APP | 1:
91+
{
92+
ITEMIDLIST** ppidl;
93+
int lEvent = 0;
94+
HANDLE hLock = PInvoke.SHChangeNotification_Lock((HANDLE)(nint)wParam.Value, (uint)lParam.Value, &ppidl, &lEvent);
95+
96+
if (hLock.IsNull)
97+
break;
98+
99+
// TODO: Fire events
100+
101+
PInvoke.SHChangeNotification_Unlock(hLock);
102+
}
103+
break;
104+
case PInvoke.WM_DESTROY:
105+
{
106+
PInvoke.SHChangeNotifyDeregister(_registrationID);
107+
PInvoke.CoTaskMemFree(_pidl);
108+
PInvoke.CoUninitialize();
109+
PInvoke.PostQuitMessage(0);
110+
}
111+
break;
112+
}
113+
114+
return PInvoke.DefWindowProc(hWnd, uMessage, wParam, lParam);
115+
}
116+
117+
public void Dispose()
118+
{
119+
}
120+
121+
public ValueTask DisposeAsync()
122+
{
123+
return ValueTask.CompletedTask;
124+
}
125+
}
126+
}

src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public sealed partial class QuickAccessWidgetViewModel : BaseWidgetViewModel, IW
3636
// TODO: Replace with IMutableFolder.GetWatcherAsync() once it gets implemented in IWindowsStorable
3737
private readonly SystemIO.FileSystemWatcher _quickAccessFolderWatcher;
3838

39+
private readonly IFolderWatcher _watcher;
40+
3941
// Constructor
4042

4143
public QuickAccessWidgetViewModel()
@@ -46,19 +48,22 @@ public QuickAccessWidgetViewModel()
4648
PinToSidebarCommand = new AsyncRelayCommand<WidgetFolderCardItem>(ExecutePinToSidebarCommand);
4749
UnpinFromSidebarCommand = new AsyncRelayCommand<WidgetFolderCardItem>(ExecuteUnpinFromSidebarCommand);
4850

49-
_quickAccessFolderWatcher = new()
50-
{
51-
Path = SystemIO.Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"),
52-
Filter = "f01b4d95cf55d32a.automaticDestinations-ms",
53-
NotifyFilter = SystemIO.NotifyFilters.LastAccess | SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName
54-
};
51+
var quickAccessFolder = new WindowsFolder(new Guid("3936e9e4-d92c-4eee-a85a-bc16d5ea0819"));
52+
_watcher = quickAccessFolder.GetFolderWatcherAsync(default).Result;
5553

56-
_quickAccessFolderWatcher.Changed += async (s, e) =>
57-
{
58-
await RefreshWidgetAsync();
59-
};
54+
//_quickAccessFolderWatcher = new()
55+
//{
56+
// Path = SystemIO.Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"),
57+
// Filter = "f01b4d95cf55d32a.automaticDestinations-ms",
58+
// NotifyFilter = SystemIO.NotifyFilters.LastAccess | SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName
59+
//};
60+
61+
//_quickAccessFolderWatcher.Changed += async (s, e) =>
62+
//{
63+
// await RefreshWidgetAsync();
64+
//};
6065

61-
_quickAccessFolderWatcher.EnableRaisingEvents = true;
66+
//_quickAccessFolderWatcher.EnableRaisingEvents = true;
6267
}
6368

6469
// Methods

0 commit comments

Comments
 (0)