[PATCH 2/3] shell32: Make ShellView listen for filesystem changes
Nigel Baillie
metreckk at gmail.com
Tue Jan 8 19:52:15 CST 2019
There was an issue where "new folder" operations didn't show the new
folder when performed in certain locations. That issue was caused by a
work-around for a another issue where pasting files in certain locations
wouldn't show up until a refresh. The reason for those two issues stem
from the fact that SHChangeNotifyRegister and SHChangeNotify operate on
PIDLs, and there can be multiple PIDLs for a single path. |My
Computer|C:\|users| and |/|home|users| for instance.
Here we make shell views listen for external changes to their current
location (if there's actually a filesystem path associated with it) by
spinning up a thread that waits on FindFirstChangeNotification. When a
change notification comes in, a message is posted to tell the shell view
to update its list view (without re-sorting the list so as to not
confuse the user too much -- similar to how Windows does it). As a
result, SHChangeNotify is no longer necessary to get ShellViews to
update their contents when files change.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=30752
Signed-off-by: Nigel Baillie <metreckk at gmail.com>
---
dlls/shell32/shlview.c | 140 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 131 insertions(+), 9 deletions(-)
diff --git a/dlls/shell32/shlview.c b/dlls/shell32/shlview.c
index c0c027fbd3..b31b81664e 100644
--- a/dlls/shell32/shlview.c
+++ b/dlls/shell32/shlview.c
@@ -100,6 +100,9 @@ typedef struct
UINT uState;
UINT cidl;
LPITEMIDLIST *apidl;
+ WCHAR current_path[MAX_PATH];
+ HANDLE fs_listener_thread;
+ HANDLE change_handle;
LISTVIEW_SORT_INFO ListViewSortInfo;
ULONG hNotify; /* change notification handle */
HANDLE hAccel;
@@ -169,6 +172,7 @@ static inline IShellViewImpl *impl_from_IShellFolderViewDual3(IShellFolderViewDu
#define ID_LISTVIEW 1
#define SHV_CHANGE_NOTIFY WM_USER + 0x1111
+#define SHV_UPDATE WM_USER + 0x1112
/*windowsx.h */
#define GET_WM_COMMAND_ID(wp, lp) LOWORD(wp)
@@ -573,7 +577,7 @@ static int LV_FindItemByPidl(
return -1;
}
-static void shellview_add_item(IShellViewImpl *shellview, LPCITEMIDLIST pidl)
+static void shellview_add_item(IShellViewImpl *shellview, LPCITEMIDLIST pidl, INT index)
{
LVITEMW item;
UINT i;
@@ -581,7 +585,7 @@ static void shellview_add_item(IShellViewImpl *shellview, LPCITEMIDLIST pidl)
TRACE("(%p)(pidl=%p)\n", shellview, pidl);
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
- item.iItem = 0;
+ item.iItem = index;
item.iSubItem = 0;
item.lParam = (LPARAM)pidl;
item.pszText = LPSTR_TEXTCALLBACKW;
@@ -591,7 +595,7 @@ static void shellview_add_item(IShellViewImpl *shellview, LPCITEMIDLIST pidl)
for (i = 1; i < shellview->columns; i++)
{
item.mask = LVIF_TEXT;
- item.iItem = 0;
+ item.iItem = index;
item.iSubItem = 1;
item.pszText = LPSTR_TEXTCALLBACKW;
SendMessageW(shellview->hWndList, LVM_SETITEMW, 0, (LPARAM)&item);
@@ -654,7 +658,7 @@ static HRESULT ShellView_FillList(IShellViewImpl *This)
while ((S_OK == IEnumIDList_Next(pEnumIDList, 1, &pidl, &fetched)) && fetched)
{
if (IncludeObject(This, pidl) == S_OK)
- shellview_add_item(This, pidl);
+ shellview_add_item(This, pidl, 0);
}
SendMessageW(This->hWndList, LVM_SORTITEMS, (WPARAM)This->pSFParent, (LPARAM)ShellView_CompareItems);
@@ -665,6 +669,106 @@ static HRESULT ShellView_FillList(IShellViewImpl *This)
return S_OK;
}
+/**********************************************************
+* ShellView_UpdateList()
+*
+* similar to ShellView_FillList, but only adds new items and deletes absent items
+*/
+static HRESULT ShellView_UpdateList(IShellViewImpl *This)
+{
+ LPENUMIDLIST pEnumIDList;
+ LPITEMIDLIST pidl;
+ BOOLEAN *items_to_keep = NULL;
+ INT item_count, index;
+ DWORD fetched;
+ HRESULT hr;
+
+ TRACE("(%p)\n", This);
+
+ /* see how many items we currently have */
+ item_count = SendMessageW(This->hWndList, LVM_GETITEMCOUNT, 0, 0);
+ if (item_count > 0)
+ {
+ items_to_keep = SHAlloc(item_count);
+ ZeroMemory(items_to_keep, item_count);
+ }
+
+ /* get the itemlist from the shfolder */
+ hr = IShellFolder_EnumObjects(This->pSFParent, This->hWnd, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &pEnumIDList);
+ if (hr != S_OK)
+ return hr;
+
+ /* add new items to the list and keep track of items that are already present */
+ while ((S_OK == IEnumIDList_Next(pEnumIDList, 1, &pidl, &fetched)) && fetched)
+ {
+ index = LV_FindItemByPidl(This, ILFindLastID(pidl));
+
+ if (index == -1)
+ {
+ if (IncludeObject(This, pidl) == S_OK)
+ shellview_add_item(This, pidl, item_count);
+ }
+ else
+ items_to_keep[index] = TRUE;
+ }
+
+ /* remove items that are no longer present.
+ iterate backward to keep list view indices consistent with items_to_keep */
+ for (index = item_count-1; index >= 0; index--)
+ if (!items_to_keep[index])
+ SendMessageW(This->hWndList, LVM_DELETEITEM, index, 0);
+
+ if (items_to_keep != NULL)
+ SHFree(items_to_keep);
+
+ IEnumIDList_Release(pEnumIDList);
+ return S_OK;
+}
+
+/**********************************************************
+* ShellView_FsChangeListener
+*/
+static DWORD CALLBACK ShellView_FsChangeListener(void *parameter)
+{
+ IShellViewImpl *This = (IShellViewImpl *)parameter;
+ DWORD wait_status;
+
+ This->change_handle = FindFirstChangeNotificationW(This->current_path, FALSE,
+ FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_DIR_NAME);
+
+ if (This->change_handle == INVALID_HANDLE_VALUE || This->change_handle == NULL)
+ {
+ ERR("Failed to listen for external filesystem changes\n");
+ return 1;
+ }
+
+ while (TRUE) {
+ wait_status = WaitForSingleObject(This->change_handle, INFINITE);
+
+ switch (wait_status)
+ {
+ case WAIT_OBJECT_0:
+ if (!PostMessageW(This->hWnd, SHV_UPDATE, 0, 0))
+ {
+ ERR("Failed to post SHV_UPDATE\n");
+ return 2;
+ }
+
+ if (!FindNextChangeNotification(This->change_handle))
+ {
+ ERR("Failed to find next change notification\n");
+ return 3;
+ }
+ break;
+
+ default:
+ ERR("Encountered unknown wait status\n");
+ return 4;
+ }
+ }
+}
+
/**********************************************************
* ShellView_OnCreate()
*/
@@ -692,6 +796,8 @@ static LRESULT ShellView_OnCreate(IShellViewImpl *This)
}
/* register for receiving notifications */
+ This->fs_listener_thread = INVALID_HANDLE_VALUE;
+ This->change_handle = INVALID_HANDLE_VALUE;
hr = IShellFolder_QueryInterface(This->pSFParent, &IID_IPersistFolder2, (LPVOID*)&ppf2);
if (hr == S_OK)
{
@@ -718,6 +824,11 @@ static LRESULT ShellView_OnCreate(IShellViewImpl *This)
ntreg.fRecursive = TRUE;
This->hNotify = SHChangeNotifyRegister(This->hWnd, SHCNRF_InterruptLevel, SHCNE_ALLEVENTS,
SHV_CHANGE_NOTIFY, 1, &ntreg);
+
+ if (SHGetPathFromIDListW(raw_pidl, This->current_path))
+ This->fs_listener_thread = CreateThread(NULL, 0, ShellView_FsChangeListener,
+ (void *)This, 0, NULL);
+
SHFree((LPITEMIDLIST)ntreg.pidl);
SHFree(computer_pidl);
}
@@ -729,6 +840,17 @@ static LRESULT ShellView_OnCreate(IShellViewImpl *This)
return S_OK;
}
+static void ShellView_OnDestroy(IShellViewImpl *This)
+{
+ RevokeDragDrop(This->hWnd);
+ SHChangeNotifyDeregister(This->hNotify);
+
+ if (This->fs_listener_thread != INVALID_HANDLE_VALUE && This->fs_listener_thread != NULL)
+ TerminateThread(This->fs_listener_thread, 0);
+ if (This->change_handle != INVALID_HANDLE_VALUE && This->change_handle != NULL)
+ FindCloseChangeNotification(This->change_handle);
+}
+
/**********************************************************
* #### Handling of the menus ####
*/
@@ -1629,7 +1751,7 @@ static LRESULT ShellView_OnChange(IShellViewImpl * This, const LPCITEMIDLIST *pi
case SHCNE_MKDIR:
case SHCNE_CREATE:
pidl = ILClone(ILFindLastID(pidls[0]));
- shellview_add_item(This, pidl);
+ shellview_add_item(This, pidl, 0);
break;
case SHCNE_RMDIR:
case SHCNE_DELETE:
@@ -1678,6 +1800,8 @@ static LRESULT CALLBACK ShellView_WndProc(HWND hWnd, UINT uMessage, WPARAM wPara
GET_WM_COMMAND_CMD(wParam, lParam),
GET_WM_COMMAND_HWND(wParam, lParam));
case SHV_CHANGE_NOTIFY: return ShellView_OnChange(pThis, (const LPCITEMIDLIST*)wParam, (LONG)lParam);
+ case SHV_UPDATE: ShellView_UpdateList(pThis);
+ return 0;
case WM_CONTEXTMENU: ShellView_DoContextMenu(pThis, LOWORD(lParam), HIWORD(lParam), FALSE);
return 0;
@@ -1689,10 +1813,8 @@ static LRESULT CALLBACK ShellView_WndProc(HWND hWnd, UINT uMessage, WPARAM wPara
case WM_SETFONT: return SendMessageW(pThis->hWndList, WM_SETFONT, wParam, lParam);
case WM_GETFONT: return SendMessageW(pThis->hWndList, WM_GETFONT, wParam, lParam);
- case WM_DESTROY:
- RevokeDragDrop(pThis->hWnd);
- SHChangeNotifyDeregister(pThis->hNotify);
- break;
+ case WM_DESTROY: ShellView_OnDestroy(pThis);
+ break;
case WM_ERASEBKGND:
if ((pThis->FolderSettings.fFlags & FWF_DESKTOP) ||
--
2.19.1
More information about the wine-devel
mailing list