[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