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@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) ||