From 6a85db5bece6170be1e83598def09bdcde16b5a6 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Fri, 9 May 2025 15:40:15 +0900 Subject: [PATCH 1/2] Init --- src/Files.App.CsWin32/NativeMethods.txt | 9 + .../Storables/WindowsStorage/IWindowsFile.cs | 9 + .../WindowsStorage/IWindowsFolder.cs | 9 + .../WindowsStorage/IWindowsStorable.cs | 2 +- .../Storables/WindowsStorage/WindowsFile.cs | 2 +- .../Storables/WindowsStorage/WindowsFolder.cs | 8 +- .../WindowsStorage/WindowsFolderWatcher.cs | 273 ++++++++++++++++++ .../WindowsFolderWatcherEventArgs.cs | 23 ++ .../WindowsStorage/WindowsStorable.cs | 2 +- .../Widgets/QuickAccessWidgetViewModel.cs | 27 +- 10 files changed, 349 insertions(+), 15 deletions(-) create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcherEventArgs.cs diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt index ce5524f6885e..255745967dc0 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -225,3 +225,12 @@ QITIPF_FLAGS GetKeyboardState MapVirtualKey GetKeyboardLayout +SHChangeNotifyRegister +SHChangeNotifyDeregister +SHChangeNotification_Lock +SHChangeNotification_Unlock +CoInitialize +CoUninitialize +PostQuitMessage +HWND_MESSAGE +SHCNE_ID diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs new file mode 100644 index 000000000000..43f30155f907 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs @@ -0,0 +1,9 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Storage +{ + public interface IWindowsFile : IWindowsStorable, IChildFile + { + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs new file mode 100644 index 000000000000..bc21ce68bf41 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs @@ -0,0 +1,9 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Storage +{ + public interface IWindowsFolder : IWindowsStorable, IChildFolder, IMutableFolder + { + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs index 421d7a68dddd..4dd82c398487 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs @@ -6,7 +6,7 @@ namespace Files.App.Storage { - public interface IWindowsStorable : IDisposable + public interface IWindowsStorable : IStorableChild, IEquatable, IDisposable { ComPtr ThisPtr { get; } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs index 3ce56f786c2f..728552e06127 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs @@ -8,7 +8,7 @@ namespace Files.App.Storage { [DebuggerDisplay("{" + nameof(ToString) + "()}")] - public sealed class WindowsFile : WindowsStorable, IChildFile + public sealed class WindowsFile : WindowsStorable, IWindowsFile { public WindowsFile(ComPtr nativeObject) { diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs index f4105687184f..fc2ff081fb31 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs @@ -10,7 +10,7 @@ namespace Files.App.Storage { [DebuggerDisplay("{" + nameof(ToString) + "()}")] - public sealed class WindowsFolder : WindowsStorable, IChildFolder + public sealed class WindowsFolder : WindowsStorable, IWindowsFolder { public WindowsFolder(ComPtr nativeObject) { @@ -85,5 +85,11 @@ unsafe bool GetNext() } } } + + public Task GetFolderWatcherAsync(CancellationToken cancellationToken = default) + { + IFolderWatcher watcher = new WindowsFolderWatcher(this); + return Task.FromResult(watcher); + } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs new file mode 100644 index 000000000000..b52a1eff1573 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs @@ -0,0 +1,273 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using System.Collections.Specialized; +using System.Runtime.InteropServices; +using Windows.Foundation; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com; +using Windows.Win32.UI.Shell; +using Windows.Win32.UI.Shell.Common; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Files.App.Storage +{ + /// + /// Represents an implementation of that uses Windows Shell notifications to watch for changes in a folder. + /// + public unsafe partial class WindowsFolderWatcher : IFolderWatcher + { + // Fields + + private const uint WM_NOTIFYFOLDERCHANGE = PInvoke.WM_APP | 0x0001U; + private readonly WNDPROC _wndProc; + + private uint _watcherRegID = 0U; + private ITEMIDLIST* _targetItemPIDL = default; + + // Properties + + public IMutableFolder Folder { get; private set; } + + // Events + + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + public event TypedEventHandler? ItemAssocChanged; // SHCNE_ASSOCCHANGED + public event TypedEventHandler? ItemAttributesChanged; // SHCNE_ATTRIBUTES + public event TypedEventHandler? ItemImageUpdated; // SHCNE_UPDATEIMAGE + + public event TypedEventHandler? FileRenamed; // SHCNE_RENAMEITEM + public event TypedEventHandler? FileCreated; // SHCNE_CREATE + public event TypedEventHandler? FileDeleted; // SHCNE_DELETE + public event TypedEventHandler? FileUpdated; // SHCNE_UPDATEITEM + + public event TypedEventHandler? FolderRenamed; // SHCNE_RENAMEFOLDER + public event TypedEventHandler? FolderCreated; // SHCNE_MKDIR + public event TypedEventHandler? FolderDeleted; // SHCNE_RMDIR + public event TypedEventHandler? FolderUpdated; // SHCNE_UPDATEDIR + + public event TypedEventHandler? MediaInserted; // SHCNE_MEDIAINSERTED + public event TypedEventHandler? MediaRemoved; // SHCNE_MEDIAREMOVED + public event TypedEventHandler? DriveRemoved; // SHCNE_DRIVEREMOVED + public event TypedEventHandler? DriveAdded; // SHCNE_DRIVEADD + public event TypedEventHandler? DriveAddedViaGUI; // SHCNE_DRIVEADDGUI + public event TypedEventHandler? FreeSpaceUpdated; // SHCNE_FREESPACE + + public event TypedEventHandler? SharingStarted; // SHCNE_NETSHARE + public event TypedEventHandler? SharingStopped; // SHCNE_NETUNSHARE + + public event TypedEventHandler? DisconnectedFromServer; // SHCNE_SERVERDISCONNECT + + public event TypedEventHandler? ExtendedEventOccurred; // SHCNE_EXTENDED_EVENT + public event TypedEventHandler? SystemInterruptOccurred; // SHCNE_INTERRUPT + + // Constructor + + /// Initializes a new instance of the class. + /// Specifies the folder to be monitored for changes. + public WindowsFolderWatcher(WindowsFolder folder) + { + Folder = folder; + + fixed (char* pszClassName = $"FolderWatcherWindowClass{Guid.NewGuid():B}") + { + _wndProc = new(WndProc); + + WNDCLASSEXW wndClass = default; + wndClass.cbSize = (uint)sizeof(WNDCLASSEXW); + wndClass.lpfnWndProc = (delegate* unmanaged[Stdcall])Marshal.GetFunctionPointerForDelegate(_wndProc); + wndClass.hInstance = PInvoke.GetModuleHandle(default(PWSTR)); + wndClass.lpszClassName = pszClassName; + + PInvoke.RegisterClassEx(&wndClass); + PInvoke.CreateWindowEx(0, pszClassName, null, 0, 0, 0, 0, 0, HWND.HWND_MESSAGE, default, wndClass.hInstance, null); + } + } + + private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM lParam) + { + switch (uMessage) + { + case PInvoke.WM_CREATE: + { + PInvoke.CoInitialize(); + + ITEMIDLIST* pidl = default; + IWindowsFolder folder = (IWindowsFolder)Folder; + PInvoke.SHGetIDListFromObject((IUnknown*)folder.ThisPtr.Get(), &pidl); + _targetItemPIDL = pidl; + + SHChangeNotifyEntry changeNotifyEntry = default; + changeNotifyEntry.pidl = pidl; + + _watcherRegID = PInvoke.SHChangeNotifyRegister( + hWnd, + SHCNRF_SOURCE.SHCNRF_ShellLevel | SHCNRF_SOURCE.SHCNRF_NewDelivery, + (int)SHCNE_ID.SHCNE_ALLEVENTS, + WM_NOTIFYFOLDERCHANGE, + 1, + &changeNotifyEntry); + + if (_watcherRegID is 0U) + break; + } + break; + case WM_NOTIFYFOLDERCHANGE: + { + ITEMIDLIST** ppidl; + uint lEvent = 0; + HANDLE hLock = PInvoke.SHChangeNotification_Lock((HANDLE)(nint)wParam.Value, (uint)lParam.Value, &ppidl, (int*)&lEvent); + + if (hLock.IsNull) + break; + + // TODO: Fire events + + PInvoke.SHChangeNotification_Unlock(hLock); + } + break; + case PInvoke.WM_DESTROY: + { + Dispose(); + } + break; + } + + return PInvoke.DefWindowProc(hWnd, uMessage, wParam, lParam); + } + + private void FireEvent(SHCNE_ID eventType, ITEMIDLIST** ppidl) + { + switch (eventType) + { + case SHCNE_ID.SHCNE_ASSOCCHANGED: + { + ItemAssocChanged?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_ATTRIBUTES: + { + ItemAttributesChanged?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_UPDATEIMAGE: + { + ItemImageUpdated?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_RENAMEITEM: + { + FileRenamed?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_CREATE: + { + FileCreated?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_DELETE: + { + FileDeleted?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_UPDATEITEM: + { + FileUpdated?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_RENAMEFOLDER: + { + FolderRenamed?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_MKDIR: + { + FolderCreated?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_RMDIR: + { + FolderDeleted?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_UPDATEDIR: + { + FolderUpdated?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_MEDIAINSERTED: + { + MediaInserted?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_MEDIAREMOVED: + { + MediaRemoved?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_DRIVEREMOVED: + { + DriveRemoved?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_DRIVEADD: + { + DriveAdded?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_DRIVEADDGUI: + { + DriveAddedViaGUI?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_FREESPACE: + { + FreeSpaceUpdated?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_NETSHARE: + { + SharingStarted?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_NETUNSHARE: + { + SharingStopped?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_SERVERDISCONNECT: + { + DisconnectedFromServer?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_EXTENDED_EVENT: + { + ExtendedEventOccurred?.Invoke(this, new(eventType)); + } + break; + case SHCNE_ID.SHCNE_INTERRUPT: + { + SystemInterruptOccurred?.Invoke(this, new(eventType)); + } + break; + } + } + + public void Dispose() + { + PInvoke.SHChangeNotifyDeregister(_watcherRegID); + PInvoke.CoTaskMemFree(_targetItemPIDL); + PInvoke.CoUninitialize(); + PInvoke.PostQuitMessage(0); + } + + public ValueTask DisposeAsync() + { + Dispose(); + + return ValueTask.CompletedTask; + } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcherEventArgs.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcherEventArgs.cs new file mode 100644 index 000000000000..69158c39a26b --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcherEventArgs.cs @@ -0,0 +1,23 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Windows.Win32.UI.Shell; + +namespace Files.App.Storage +{ + public class WindowsFolderWatcherEventArgs : EventArgs + { + public SHCNE_ID EventType { get; init; } + + public IWindowsStorable? OldItem { get; init; } + + public IWindowsStorable? NewItem { get; init; } + + public WindowsFolderWatcherEventArgs(SHCNE_ID eventType, IWindowsStorable? _oldItem = null, IWindowsStorable? _newItem = null) + { + EventType = eventType; + OldItem = _oldItem; + NewItem = _newItem; + } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs index 3fdc51e33389..6dd76debb3e0 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs @@ -8,7 +8,7 @@ namespace Files.App.Storage { - public abstract class WindowsStorable : IWindowsStorable, IStorableChild, IEquatable + public abstract class WindowsStorable : IWindowsStorable { public ComPtr ThisPtr { get; protected set; } diff --git a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs index 2739fb84162a..75f958d77d4c 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs @@ -36,6 +36,8 @@ public sealed partial class QuickAccessWidgetViewModel : BaseWidgetViewModel, IW // TODO: Replace with IMutableFolder.GetWatcherAsync() once it gets implemented in IWindowsStorable private readonly SystemIO.FileSystemWatcher _quickAccessFolderWatcher; + private readonly IFolderWatcher _watcher; + // Constructor public QuickAccessWidgetViewModel() @@ -46,19 +48,22 @@ public QuickAccessWidgetViewModel() PinToSidebarCommand = new AsyncRelayCommand(ExecutePinToSidebarCommand); UnpinFromSidebarCommand = new AsyncRelayCommand(ExecuteUnpinFromSidebarCommand); - _quickAccessFolderWatcher = new() - { - Path = SystemIO.Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"), - Filter = "f01b4d95cf55d32a.automaticDestinations-ms", - NotifyFilter = SystemIO.NotifyFilters.LastAccess | SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName - }; + var quickAccessFolder = new WindowsFolder(new Guid("3936e9e4-d92c-4eee-a85a-bc16d5ea0819")); + _watcher = quickAccessFolder.GetFolderWatcherAsync(default).Result; - _quickAccessFolderWatcher.Changed += async (s, e) => - { - await RefreshWidgetAsync(); - }; + //_quickAccessFolderWatcher = new() + //{ + // Path = SystemIO.Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"), + // Filter = "f01b4d95cf55d32a.automaticDestinations-ms", + // NotifyFilter = SystemIO.NotifyFilters.LastAccess | SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName + //}; + + //_quickAccessFolderWatcher.Changed += async (s, e) => + //{ + // await RefreshWidgetAsync(); + //}; - _quickAccessFolderWatcher.EnableRaisingEvents = true; + //_quickAccessFolderWatcher.EnableRaisingEvents = true; } // Methods From 45887e05d2bbfb7500a9675919564c97451ee414 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 2 Jun 2025 02:55:21 +0900 Subject: [PATCH 2/2] Update --- .../WindowsStorage/IWindowsFolderWatcher.cs | 41 ++++++++++++++++ .../WindowsStorage/WindowsFolderWatcher.cs | 21 ++++++-- .../WindowsStorage/WindowsStorable.cs | 48 ++++++++++++------- .../Widgets/QuickAccessWidgetViewModel.cs | 22 ++------- 4 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolderWatcher.cs diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolderWatcher.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolderWatcher.cs new file mode 100644 index 000000000000..4b1e7972d173 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolderWatcher.cs @@ -0,0 +1,41 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Windows.Foundation; + +namespace Files.App.Storage +{ + public interface IWindowsFolderWatcher : IFolderWatcher + { + public event TypedEventHandler? EventOccurred; + + public event TypedEventHandler? ItemAssocChanged; // SHCNE_ASSOCCHANGED + public event TypedEventHandler? ItemAttributesChanged; // SHCNE_ATTRIBUTES + public event TypedEventHandler? ItemImageUpdated; // SHCNE_UPDATEIMAGE + + public event TypedEventHandler? FileRenamed; // SHCNE_RENAMEITEM + public event TypedEventHandler? FileCreated; // SHCNE_CREATE + public event TypedEventHandler? FileDeleted; // SHCNE_DELETE + public event TypedEventHandler? FileUpdated; // SHCNE_UPDATEITEM + + public event TypedEventHandler? FolderRenamed; // SHCNE_RENAMEFOLDER + public event TypedEventHandler? FolderCreated; // SHCNE_MKDIR + public event TypedEventHandler? FolderDeleted; // SHCNE_RMDIR + public event TypedEventHandler? FolderUpdated; // SHCNE_UPDATEDIR + + public event TypedEventHandler? MediaInserted; // SHCNE_MEDIAINSERTED + public event TypedEventHandler? MediaRemoved; // SHCNE_MEDIAREMOVED + public event TypedEventHandler? DriveRemoved; // SHCNE_DRIVEREMOVED + public event TypedEventHandler? DriveAdded; // SHCNE_DRIVEADD + public event TypedEventHandler? DriveAddedViaGUI; // SHCNE_DRIVEADDGUI + public event TypedEventHandler? FreeSpaceUpdated; // SHCNE_FREESPACE + + public event TypedEventHandler? SharingStarted; // SHCNE_NETSHARE + public event TypedEventHandler? SharingStopped; // SHCNE_NETUNSHARE + + public event TypedEventHandler? DisconnectedFromServer; // SHCNE_SERVERDISCONNECT + + public event TypedEventHandler? ExtendedEventOccurred; // SHCNE_EXTENDED_EVENT + public event TypedEventHandler? SystemInterruptOccurred; // SHCNE_INTERRUPT + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs index b52a1eff1573..b799dc741311 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolderWatcher.cs @@ -16,7 +16,7 @@ namespace Files.App.Storage /// /// Represents an implementation of that uses Windows Shell notifications to watch for changes in a folder. /// - public unsafe partial class WindowsFolderWatcher : IFolderWatcher + public unsafe partial class WindowsFolderWatcher : IWindowsFolderWatcher { // Fields @@ -24,7 +24,7 @@ public unsafe partial class WindowsFolderWatcher : IFolderWatcher private readonly WNDPROC _wndProc; private uint _watcherRegID = 0U; - private ITEMIDLIST* _targetItemPIDL = default; + private ITEMIDLIST* _folderPidl = default; // Properties @@ -34,6 +34,8 @@ public unsafe partial class WindowsFolderWatcher : IFolderWatcher public event NotifyCollectionChangedEventHandler? CollectionChanged; + public event TypedEventHandler? EventOccurred; + public event TypedEventHandler? ItemAssocChanged; // SHCNE_ASSOCCHANGED public event TypedEventHandler? ItemAttributesChanged; // SHCNE_ATTRIBUTES public event TypedEventHandler? ItemImageUpdated; // SHCNE_UPDATEIMAGE @@ -86,6 +88,8 @@ public WindowsFolderWatcher(WindowsFolder folder) } } + // Methods + private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM lParam) { switch (uMessage) @@ -97,7 +101,7 @@ private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM l ITEMIDLIST* pidl = default; IWindowsFolder folder = (IWindowsFolder)Folder; PInvoke.SHGetIDListFromObject((IUnknown*)folder.ThisPtr.Get(), &pidl); - _targetItemPIDL = pidl; + _folderPidl = pidl; SHChangeNotifyEntry changeNotifyEntry = default; changeNotifyEntry.pidl = pidl; @@ -123,7 +127,7 @@ private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM l if (hLock.IsNull) break; - // TODO: Fire events + FireEvent((SHCNE_ID)lEvent, ppidl); PInvoke.SHChangeNotification_Unlock(hLock); } @@ -140,6 +144,11 @@ private unsafe LRESULT WndProc(HWND hWnd, uint uMessage, WPARAM wParam, LPARAM l private void FireEvent(SHCNE_ID eventType, ITEMIDLIST** ppidl) { + //ITEMIDLIST* pOldPidl = ppidl[0]; + //ITEMIDLIST* pNewPidl = ppidl[1]; + + EventOccurred?.Invoke(this, new(eventType, null, null)); // WindowsStorable.TryParse(pOldPidl), WindowsStorable.TryParse(pNewPidl) + switch (eventType) { case SHCNE_ID.SHCNE_ASSOCCHANGED: @@ -255,10 +264,12 @@ private void FireEvent(SHCNE_ID eventType, ITEMIDLIST** ppidl) } } + // Disposers + public void Dispose() { PInvoke.SHChangeNotifyDeregister(_watcherRegID); - PInvoke.CoTaskMemFree(_targetItemPIDL); + PInvoke.CoTaskMemFree(_folderPidl); PInvoke.CoUninitialize(); PInvoke.PostQuitMessage(0); } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs index 6dd76debb3e0..e31ebe525f84 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs @@ -5,6 +5,7 @@ using Windows.Win32.Foundation; using Windows.Win32.System.SystemServices; using Windows.Win32.UI.Shell; +using Windows.Win32.UI.Shell.Common; namespace Files.App.Storage { @@ -19,46 +20,59 @@ public abstract class WindowsStorable : IWindowsStorable public static unsafe WindowsStorable? TryParse(string parsablePath) { HRESULT hr = default; - ComPtr pShellItem = default; - var IID_IShellItem = typeof(IShellItem).GUID; + IShellItem* pShellItem = default; fixed (char* pszParsablePath = parsablePath) { hr = PInvoke.SHCreateItemFromParsingName( pszParsablePath, null, - &IID_IShellItem, - (void**)pShellItem.GetAddressOf()); + IID.IID_IShellItem, + (void**)&pShellItem); } - if (pShellItem.IsNull) + if (hr.ThrowIfFailedOnDebug().Failed) return null; - return pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER) - ? new WindowsFolder(pShellItem) - : new WindowsFile(pShellItem); + bool isFolder = + pShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && + returnedAttributes is SFGAO_FLAGS.SFGAO_FOLDER; + + return isFolder ? new WindowsFolder(pShellItem) : new WindowsFile(pShellItem); } public static unsafe WindowsStorable? TryParse(IShellItem* ptr) { - ComPtr pShellItem = default; - pShellItem.Attach(ptr); + bool isFolder = + ptr->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && + returnedAttributes is SFGAO_FLAGS.SFGAO_FOLDER; + + return isFolder ? new WindowsFolder(ptr) : new WindowsFile(ptr); + } + + public static unsafe WindowsStorable? TryParse(ITEMIDLIST* pidl) + { + IShellItem* pShellItem = default; + HRESULT hr = PInvoke.SHCreateItemFromIDList(pidl, IID.IID_IShellItem, (void**)&pShellItem); + if (hr.ThrowIfFailedOnDebug().Failed || pShellItem is null) + return null; - return pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER) - ? new WindowsFolder(pShellItem) - : new WindowsFile(pShellItem); + bool isFolder = + pShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && + returnedAttributes is SFGAO_FLAGS.SFGAO_FOLDER; + + return isFolder ? new WindowsFolder(pShellItem) : new WindowsFile(pShellItem); } public unsafe Task GetParentAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - ComPtr pParentFolder = default; - HRESULT hr = ThisPtr.Get()->GetParent(pParentFolder.GetAddressOf()); + IShellItem* pParentFolder = default; + HRESULT hr = ThisPtr.Get()->GetParent(&pParentFolder); if (hr.Failed) { - if (!pParentFolder.IsNull) pParentFolder.Dispose(); - + if (pParentFolder is not null) pParentFolder->Release(); return Task.FromResult(null); } diff --git a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs index 75f958d77d4c..5dc025e820d2 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs @@ -33,10 +33,7 @@ public sealed partial class QuickAccessWidgetViewModel : BaseWidgetViewModel, IW // Fields - // TODO: Replace with IMutableFolder.GetWatcherAsync() once it gets implemented in IWindowsStorable - private readonly SystemIO.FileSystemWatcher _quickAccessFolderWatcher; - - private readonly IFolderWatcher _watcher; + private readonly IWindowsFolderWatcher _watcher; // Constructor @@ -49,21 +46,8 @@ public QuickAccessWidgetViewModel() UnpinFromSidebarCommand = new AsyncRelayCommand(ExecuteUnpinFromSidebarCommand); var quickAccessFolder = new WindowsFolder(new Guid("3936e9e4-d92c-4eee-a85a-bc16d5ea0819")); - _watcher = quickAccessFolder.GetFolderWatcherAsync(default).Result; - - //_quickAccessFolderWatcher = new() - //{ - // Path = SystemIO.Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"), - // Filter = "f01b4d95cf55d32a.automaticDestinations-ms", - // NotifyFilter = SystemIO.NotifyFilters.LastAccess | SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName - //}; - - //_quickAccessFolderWatcher.Changed += async (s, e) => - //{ - // await RefreshWidgetAsync(); - //}; - - //_quickAccessFolderWatcher.EnableRaisingEvents = true; + _watcher = (IWindowsFolderWatcher)quickAccessFolder.GetFolderWatcherAsync(default).Result; + _watcher.EventOccurred += async (s, e) => { await RefreshWidgetAsync(); }; } // Methods