On Windows, passing SHCNRF_InterruptLevel as a flag to SHChangeNotifyRegister results in shell notification messages being sent on filesystem changes, rather than on explicit SHChangeNotify calls. Wine doesn't currently implement this, resulting in some strange shell behavior when pasting or creating a new folder.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=30752 Signed-off-by: Nigel Baillie metreckk@gmail.com --- dlls/shell32/tests/shlfolder.c | 158 +++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+)
diff --git a/dlls/shell32/tests/shlfolder.c b/dlls/shell32/tests/shlfolder.c index beee31f2ca..d964d77ae4 100644 --- a/dlls/shell32/tests/shlfolder.c +++ b/dlls/shell32/tests/shlfolder.c @@ -4978,6 +4978,163 @@ static void test_SHChangeNotify(BOOL test_new_delivery) ok(br == TRUE, "RemoveDirectory failed: %d\n", GetLastError()); }
+static void test_SHCNRF_InterruptLevel() +{ + HWND wnd; + ULONG notifyID, i; + HRESULT hr; + HANDLE test_file_handle; + BOOL br, has_unicode; + SHChangeNotifyEntry entries[1]; + struct ChNotifyTest create_event = {"CREATE", 1, 1, SHCNE_CREATE, "C:\shell32_cn_test.txt", ""}; + struct ChNotifyTest create_event2 = {"CREATE", 1, 1, SHCNE_CREATE, "C:\shell32_cn_test_dir\file.txt", ""}; + struct ChNotifyTest rename_event = {"RENAMEITEM", 1, 1, SHCNE_RENAMEITEM, "C:\shell32_cn_test.txt", "C:\shell32.txt"}; + struct ChNotifyTest delete_event = {"DELETE", 1, 1, SHCNE_DELETE, "C:\shell32.txt", ""}; + const CHAR root_dirA[] = "C:\"; + const WCHAR root_dirW[] = {'C',':','\',0}; + const CHAR root_dir2A[] = "C:\shell32_cn_test_dir"; + const WCHAR root_dir2W[] = {'C',':','\','s','h','e','l','l','3','2','_','c','n','_','t','e','s','t','_','d','i','r',0}; + + trace("Testing SHChangeNotifyRegister with SHCNRF_InterruptLevel\n"); + + CreateDirectoryW(NULL, NULL); + has_unicode = !(GetLastError() == ERROR_CALL_NOT_IMPLEMENTED); + + exp_data = &create_event; + test_new_delivery_flag = FALSE; + + /* create test window */ + wnd = CreateWindowExA(0, testwindow_class, testwindow_class, 0, + CW_USEDEFAULT, CW_USEDEFAULT, 130, 105, + NULL, NULL, GetModuleHandleA(NULL), 0); + ok(wnd != NULL, "Failed to make a window\n"); + + /* register for notifications on C:\ */ + entries[0].pidl = NULL; + if(has_unicode) + hr = SHILCreateFromPath(root_dirW, (LPITEMIDLIST*)&entries[0].pidl, 0); + else + hr = SHILCreateFromPath((const void *)root_dirA, (LPITEMIDLIST*)&entries[0].pidl, 0); + ok(hr == S_OK, "SHILCreateFromPath failed: 0x%08x\n", hr); + entries[0].fRecursive = TRUE; + + notifyID = SHChangeNotifyRegister( + wnd, SHCNRF_InterruptLevel, SHCNE_CREATE | SHCNE_RENAMEFOLDER | SHCNE_RENAMEITEM | SHCNE_DELETE, + WM_USER_NOTIFY, 1, entries); + ok(notifyID != 0, "Failed to register a window for change notifications\n"); + + /* create test file */ + trace("creating %s\n", create_event.path_1); + test_file_handle = CreateFileA(create_event.path_1, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + ok(test_file_handle != INVALID_HANDLE_VALUE, "CreateFile failed: %d\n", GetLastError()); + + /* see if a notification gets sent or not */ + SleepEx(1000, FALSE); + do_events(); + ok(exp_data->missing_events < 1, "%s: Expected wndproc to be called\n", exp_data->id); + + /* rename test file */ + CloseHandle(test_file_handle); + exp_data = &rename_event; + trace("renaming %s to %s\n", rename_event.path_1, rename_event.path_2); + br = MoveFileA(rename_event.path_1, rename_event.path_2); + ok(br, "failed to rename %s to %s\n", rename_event.path_1, rename_event.path_2); + + /* see if a notification gets sent or not */ + SleepEx(1000, FALSE); + do_events(); + ok(exp_data->missing_events < 1, "%s: Expected wndproc to be called\n", exp_data->id); + + /* delete test file */ + exp_data = &delete_event; + trace("deleting %s\n", delete_event.path_1); + DeleteFileA(delete_event.path_1); + + /* see if a notification gets sent or not */ + SleepEx(1000, FALSE); + do_events(); + ok(exp_data->missing_events < 1, "%s: Expected wndproc to be called\n", exp_data->id); + + /* deregister */ + SHChangeNotifyDeregister(notifyID); + ILFree((LPITEMIDLIST)entries[0].pidl); + DeleteFileA(create_event.path_1); + + /* create and listen on filename (rather than directory/drive) */ + test_file_handle = CreateFileA(create_event.path_1, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + ok(test_file_handle != INVALID_HANDLE_VALUE, "CreateFile failed: %d\n", GetLastError()); + + entries[0].pidl = ILCreateFromPathA(create_event.path_1); + ok(entries[0].pidl != NULL, "ILCreateFromPathA failed\n"); + entries[0].fRecursive = TRUE; + + notifyID = SHChangeNotifyRegister( + wnd, SHCNRF_InterruptLevel, SHCNE_RENAMEITEM, + WM_USER_NOTIFY, 1, entries); + ok(notifyID != 0, "Failed to register a window for change notifications\n"); + + /* rename the file again */ + CloseHandle(test_file_handle); + rename_event.missing_events = rename_event.notify_count; + exp_data = &rename_event; + trace("renaming %s to %s again\n", rename_event.path_1, rename_event.path_2); + br = MoveFileA(rename_event.path_1, rename_event.path_2); + ok(br, "failed to rename %s to %s\n", rename_event.path_1, rename_event.path_2); + + /* see if a notification gets sent or not */ + SleepEx(1000, FALSE); + do_events(); + ok(exp_data->missing_events < 1, "%s: Expected wndproc to be called\n", exp_data->id); + + /* deregister */ + SHChangeNotifyDeregister(notifyID); + ILFree((LPITEMIDLIST)entries[0].pidl); + DeleteFileA(rename_event.path_2); + + /* now, listen on a directory rather than a drive letter */ + trace("creating directory %s\n", root_dir2A); + if (has_unicode) + CreateDirectoryW(root_dir2W, NULL); + else + CreateDirectoryA(root_dir2A, NULL); + + entries[0].pidl = ILCreateFromPathA(root_dir2A); + ok(entries[0].pidl != NULL, "ILCreateFromPathA failed\n"); + entries[0].fRecursive = FALSE; + + notifyID = SHChangeNotifyRegister( + wnd, SHCNRF_InterruptLevel, SHCNE_CREATE, + WM_USER_NOTIFY, 1, entries); + ok(notifyID != 0, "Failed to register a window for change notifications\n"); + + /* create a file */ + exp_data = &create_event2; + trace("creating %s\n", create_event2.path_1); + test_file_handle = CreateFileA(create_event2.path_1, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + ok(test_file_handle != INVALID_HANDLE_VALUE, "CreateFile failed: %d\n", GetLastError()); + + /* see if a notification gets sent or not */ + SleepEx(1000, FALSE); + do_events(); + ok(exp_data->missing_events < 1, "%s: Expected wndproc to be called\n", exp_data->id); + + /* delete test file */ + trace("deleting %s\n", create_event2.path_1); + DeleteFileA(create_event2.path_1); + + /* final cleanup */ + SHChangeNotifyDeregister(notifyID); + DestroyWindow(wnd); + ILFree((LPITEMIDLIST)entries[0].pidl); + RemoveDirectoryA(root_dir2A); +} + static void test_SHCreateDefaultContextMenu(void) { HKEY keys[16]; @@ -5334,6 +5491,7 @@ START_TEST(shlfolder) test_ShellItemCompare(); test_SHChangeNotify(FALSE); test_SHChangeNotify(TRUE); + test_SHCNRF_InterruptLevel(); test_ShellItemBindToHandler(); test_ShellItemGetAttributes(); test_ShellItemArrayGetAttributes();
Additionally, SendMessageTimeoutA is used instead of SendMessageA because notify_recipient will be called from another thread and may need to be canceled when the window is being destroyed.
Signed-off-by: Nigel Baillie metreckk@gmaill.com --- dlls/shell32/changenotify.c | 106 +++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 44 deletions(-)
diff --git a/dlls/shell32/changenotify.c b/dlls/shell32/changenotify.c index 2efb297ad5..7656f43fee 100644 --- a/dlls/shell32/changenotify.c +++ b/dlls/shell32/changenotify.c @@ -56,6 +56,15 @@ typedef struct _NOTIFICATIONLIST static struct list notifications = LIST_INIT( notifications ); static LONG next_id;
+struct new_delivery_notification +{ + LONG event; + DWORD pidl1_size; + DWORD pidl2_size; + LPITEMIDLIST pidls[2]; + BYTE data[1]; +}; + #define SHCNE_NOITEMEVENTS ( \ SHCNE_ASSOCCHANGED )
@@ -150,6 +159,58 @@ void FreeChangeNotifications(void) DeleteCriticalSection(&SHELL32_ChangenotifyCS); }
+static BOOL notify_recipient( + HWND hwnd, DWORD msg, DWORD flags, LONG event_id, + LPITEMIDLIST pidls[2], HANDLE *shared_data) +{ + LRESULT sendmsg_result = 0; + TRACE("notifying %p, event %s(%x)\n", hwnd, DumpEvent(event_id), event_id); + + if (flags & SHCNRF_NewDelivery) { + if(!(*shared_data)) { + struct new_delivery_notification *notification; + UINT size1 = ILGetSize(pidls[0]), size2 = ILGetSize(pidls[1]); + UINT offset = (size1+sizeof(int)-1)/sizeof(int)*sizeof(int); + + notification = SHAlloc(sizeof(struct new_delivery_notification)+offset+size2); + if(!notification) { + ERR("out of memory\n"); + } else { + notification->event = event_id; + notification->pidl1_size = size1; + notification->pidl2_size = size2; + if(size1) + memcpy(notification->data, pidls[0], size1); + if(size2) + memcpy(notification->data+offset, pidls[1], size2); + + *shared_data = SHAllocShared(notification, + sizeof(struct new_delivery_notification)+size1+size2, + GetCurrentProcessId()); + SHFree(notification); + } + } + + if(*shared_data) { + sendmsg_result = SendMessageTimeoutA( + hwnd, msg, (WPARAM)*shared_data, GetCurrentProcessId(), + SMTO_NOTIMEOUTIFNOTHUNG | SMTO_ERRORONEXIT, + 10000, NULL + ); + } + else + ERR("out of memory\n"); + } else { + sendmsg_result = SendMessageTimeoutA( + hwnd, msg, (WPARAM)pidls, event_id, + SMTO_NOTIMEOUTIFNOTHUNG | SMTO_ERRORONEXIT, + 10000, NULL + ); + } + + return sendmsg_result != 0; +} + /************************************************************************* * SHChangeNotifyRegister [SHELL32.2] * @@ -231,15 +292,6 @@ BOOL WINAPI SHChangeNotifyUpdateEntryList(DWORD unknown1, DWORD unknown2, return TRUE; }
-struct new_delivery_notification -{ - LONG event; - DWORD pidl1_size; - DWORD pidl2_size; - LPITEMIDLIST pidls[2]; - BYTE data[1]; -}; - static BOOL should_notify( LPCITEMIDLIST changed, LPCITEMIDLIST watched, BOOL sub ) { TRACE("%p %p %d\n", changed, watched, sub ); @@ -371,41 +423,7 @@ void WINAPI SHChangeNotify(LONG wEventId, UINT uFlags, LPCVOID dwItem1, LPCVOID
LIST_FOR_EACH_ENTRY_SAFE(cur, next, &recipients, struct notification_recipients, entry) { - TRACE("notifying %p, event %s(%x)\n", cur->hwnd, DumpEvent(wEventId), wEventId); - - if (cur->flags & SHCNRF_NewDelivery) { - if(!shared_data) { - struct new_delivery_notification *notification; - UINT size1 = ILGetSize(Pidls[0]), size2 = ILGetSize(Pidls[1]); - UINT offset = (size1+sizeof(int)-1)/sizeof(int)*sizeof(int); - - notification = SHAlloc(sizeof(struct new_delivery_notification)+offset+size2); - if(!notification) { - ERR("out of memory\n"); - } else { - notification->event = wEventId; - notification->pidl1_size = size1; - notification->pidl2_size = size2; - if(size1) - memcpy(notification->data, Pidls[0], size1); - if(size2) - memcpy(notification->data+offset, Pidls[1], size2); - - shared_data = SHAllocShared(notification, - sizeof(struct new_delivery_notification)+size1+size2, - GetCurrentProcessId()); - SHFree(notification); - } - } - - if(shared_data) - SendMessageA(cur->hwnd, cur->msg, (WPARAM)shared_data, GetCurrentProcessId()); - else - ERR("out of memory\n"); - } else { - SendMessageA(cur->hwnd, cur->msg, (WPARAM)Pidls, wEventId); - } - + notify_recipient(cur->hwnd, cur->msg, cur->flags, wEventId, Pidls, &shared_data); list_remove(&cur->entry); SHFree(cur); }
Hi,
While running your changed tests on Windows, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=48303
Your paranoid android.
=== debian9 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit Chinese:China report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit WoW report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (64 bit WoW report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
With interrupt-level shell notifications, it's possible for a rename to have the same destination as an existing entry. Without this check, such renames result in duplicate items in the list view.
Signed-off-by: Nigel Baillie metreckk@gmail.com --- dlls/shell32/shlview.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/dlls/shell32/shlview.c b/dlls/shell32/shlview.c index c0c027fbd3..478a78ee2d 100644 --- a/dlls/shell32/shlview.c +++ b/dlls/shell32/shlview.c @@ -608,6 +608,11 @@ static BOOLEAN LV_RenameItem(IShellViewImpl * This, LPCITEMIDLIST pidlOld, LPCIT
TRACE("(%p)(pidlold=%p pidlnew=%p)\n", This, pidlOld, pidlNew);
+ /* if the destination already exists, remove it */ + nItem = LV_FindItemByPidl(This, ILFindLastID(pidlNew)); + if ( -1 != nItem ) + SendMessageW(This->hWndList, LVM_DELETEITEM, nItem, 0); + nItem = LV_FindItemByPidl(This, ILFindLastID(pidlOld)); if ( -1 != nItem ) {
Hi,
While running your changed tests on Windows, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=48304
Your paranoid android.
=== debian9 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit Chinese:China report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit WoW report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (64 bit WoW report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
When SHChangeNotifyRegister is called with the SHCNRF_InterruptLevel flag, the caller should receive notifications for external changes to the filesystem. This is accomplished using ReadDirectoryChangesW, which is called in a separate thread so that the listener doesn't need to enter an alertable wait state to receive notifications.
This fixes an issue with new folder/rename operations appearing to not function from certain paths.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=30752 Signed-off-by: Nigel Baillie metreckk@gmail.com --- dlls/shell32/changenotify.c | 390 +++++++++++++++++++++++++++++++++++- 1 file changed, 388 insertions(+), 2 deletions(-)
diff --git a/dlls/shell32/changenotify.c b/dlls/shell32/changenotify.c index 7656f43fee..bb51ce3b3d 100644 --- a/dlls/shell32/changenotify.c +++ b/dlls/shell32/changenotify.c @@ -26,6 +26,7 @@ #include "wine/list.h" #include "wine/debug.h" #include "shell32_main.h" +#include "shlwapi.h"
WINE_DEFAULT_DEBUG_CHANNEL(shell);
@@ -40,14 +41,35 @@ static CRITICAL_SECTION SHELL32_ChangenotifyCS = { &critsect_debug, -1, 0, 0, 0,
typedef SHChangeNotifyEntry *LPNOTIFYREGISTER;
+/* forward declaration */ +struct _NOTIFICATIONLIST; + +/* info needed for ReadDirectoryChangesW (internal) */ +typedef struct _DIRCHANGEINFO +{ + OVERLAPPED overlapped; + struct _NOTIFICATIONLIST *nl; /* parent NOTIFICATIONLIST */ + LPNOTIFYREGISTER watch; /* apidl entry */ + HANDLE handle; /* handle being watched */ + WCHAR listen_path[MAX_PATH]; /* directory being watched */ + WCHAR *target_file; /* file being watched (relative to listen_path) */ + BYTE buffer1[MAX_PATH * 4]; /* buffer to store incoming FILE_NOTIFY_INFORMATION */ + BYTE buffer2[MAX_PATH * 4]; /* second buffer to avoid race condition */ + BOOLEAN use_buffer1; /* TRUE when buffer1 is in use, FALSE when buffer2 is in use */ +} DIRCHANGEINFO; + /* internal list of notification clients (internal) */ typedef struct _NOTIFICATIONLIST { struct list entry; HWND hwnd; /* window to notify */ DWORD uMsg; /* message to send */ + DIRCHANGEINFO *changeinfo; /* array of directory change info */ + HANDLE listener_thread; /* thread that waits for ReadDirectoryChangesW */ + HANDLE listener_ready; /* set when listener is done constructing */ + HANDLE listener_done; /* set when listener thread should terminate */ LPNOTIFYREGISTER apidl; /* array of entries to watch*/ - UINT cidl; /* number of pidls in array */ + UINT cidl; /* number of pidls and dirchangeinfos in array */ LONG wEventMask; /* subscribed events */ DWORD dwFlags; /* client flags */ ULONG id; @@ -128,6 +150,22 @@ static void DeleteNode(LPNOTIFICATIONLIST item) UINT i;
TRACE("item=%p\n", item); + + if (item->dwFlags & SHCNRF_InterruptLevel) + { + for (i = 0; i < item->cidl; i++) + CloseHandle(item->changeinfo[i].handle); + + SetEvent(item->listener_done); + WaitForSingleObject(item->listener_thread, INFINITE); + CloseHandle(item->listener_thread); + CloseHandle(item->listener_ready); + CloseHandle(item->listener_done); + for (i = 0; i < item->cidl; i++) + if (item->changeinfo[i].target_file != NULL) + SHFree(item->changeinfo[i].target_file); + SHFree(item->changeinfo); + }
/* remove item from list */ list_remove( &item->entry ); @@ -135,6 +173,7 @@ static void DeleteNode(LPNOTIFICATIONLIST item) /* free the item */ for (i=0; i<item->cidl; i++) SHFree((LPITEMIDLIST)item->apidl[i].pidl); + SHFree(item->apidl); SHFree(item); } @@ -211,6 +250,304 @@ static BOOL notify_recipient( return sendmsg_result != 0; }
+/* imperfect conversion from SHCNE_* event to FILE_NOTIFY_CHANGE_* filter */ +static DWORD notify_filter_for_event(LONG event) +{ + DWORD notify_filter = 0; + + if (event & (SHCNE_CREATE | + SHCNE_DELETE | + SHCNE_RENAMEFOLDER | + SHCNE_RENAMEITEM | + SHCNE_RMDIR)) + notify_filter |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME; + if (event & SHCNE_ATTRIBUTES) + notify_filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES; + if (event & SHCNE_FREESPACE) + notify_filter |= FILE_NOTIFY_CHANGE_SIZE; + + return notify_filter; +} + +static FILE_NOTIFY_INFORMATION *dirchangeinfo_buffer(DIRCHANGEINFO *changeinfo) +{ + /* at least in real win32, there's a race condition when using the same buffer in + subsequent calls to ReadDirectoryChangesW */ + if (changeinfo->use_buffer1) + return (FILE_NOTIFY_INFORMATION *)changeinfo->buffer1; + else + return (FILE_NOTIFY_INFORMATION *)changeinfo->buffer2; +} + +static void CALLBACK directory_change_completion(DWORD, DWORD, OVERLAPPED *); + +static BOOLEAN dirchangeinfo_register_callback(DIRCHANGEINFO *changeinfo) +{ + return ReadDirectoryChangesW( + changeinfo->handle, + dirchangeinfo_buffer(changeinfo), + sizeof(changeinfo->buffer1), + changeinfo->watch->fRecursive && (changeinfo->nl->dwFlags & SHCNRF_RecursiveInterrupt), + notify_filter_for_event(changeinfo->nl->wEventMask), + NULL, + (OVERLAPPED *)changeinfo, + directory_change_completion + ); +} + +/* appends path2 onto the end of path1 (non-null-terminated strings) */ +static BOOLEAN append_path( + WCHAR *path1, ULONG path1_len, + WCHAR *path2, ULONG path2_len) +{ + /* first, see if path1 needs a slash */ + if (path1[path1_len-1] != '\') + path1_len += 1; + + /* the lengths here exclude the null terminator */ + if (path1_len + path2_len + 1 > MAX_PATH) + return FALSE; + + /* make sure path1 has its slash */ + path1[path1_len-1] = '\'; + + CopyMemory(path1+path1_len, path2, path2_len*sizeof(WCHAR)); + path1[path1_len + path2_len] = '\0'; + return TRUE; +} + +static FILE_NOTIFY_INFORMATION *increment_fni( + FILE_NOTIFY_INFORMATION *fni, + WCHAR *base_path, + ULONG base_path_len) +{ + if (fni->NextEntryOffset == 0) + return NULL; + + fni = (FILE_NOTIFY_INFORMATION *)(((BYTE *)fni) + fni->NextEntryOffset); + + if (!append_path(base_path, base_path_len, fni->FileName, fni->FileNameLength/sizeof(WCHAR))) + { + TRACE("notification path too long\n"); + return NULL; + } + + return fni; +} + +static WCHAR *get_file_spec_w(WCHAR *path) +{ + LONG i, path_len = strlenW(path); + + for (i = path_len-1; i >= 0; i--) + if (path[i] == '\') + return path + i + 1; + + return NULL; +} + +static void CALLBACK directory_change_completion( + DWORD error, + DWORD bytes, + OVERLAPPED *overlapped) +{ + /* the OVERLAPPED should be the first member of DIRCHANGEINFO */ + DIRCHANGEINFO *changeinfo = (DIRCHANGEINFO *)overlapped; + FILE_NOTIFY_INFORMATION *change; + HANDLE shared_data = NULL; + WCHAR path[MAX_PATH]; + ULONG path_len; + LPITEMIDLIST pidls[2]; + BOOL notify_succeeded = TRUE; + + if (error != ERROR_SUCCESS || bytes == 0) + { + if (error == ERROR_OPERATION_ABORTED || error == 0xFFFFFFF6) + TRACE("ReadDirectoryChangesW aborted\n"); + else + TRACE("error in ReadDirectoryChangesW completion function %i\n", error); + return; + } + + ZeroMemory(pidls, sizeof(pidls)); + CopyMemory(path, changeinfo->listen_path, sizeof(changeinfo->listen_path)); + path_len = strlenW(path); + + change = dirchangeinfo_buffer(changeinfo); + if (!append_path(path, path_len, change->FileName, change->FileNameLength/sizeof(WCHAR))) + { + TRACE("notification path too long\n"); + return; + } + + while (change != NULL) + { + LONG event_id = 0; + BOOL skip = FALSE; + notify_succeeded = TRUE; + + /* skip notifications that we aren't interested in */ + if (changeinfo->target_file != NULL) + { + WCHAR *file_spec = get_file_spec_w(path); + TRACE("changed file spec: %s (from %s); target file spec: %s\n", + wine_dbgstr_w(file_spec), wine_dbgstr_w(path), + wine_dbgstr_w(changeinfo->target_file)); + if (file_spec == NULL || strcmpW(file_spec, changeinfo->target_file) != 0) + skip = TRUE; + } + + if (!skip) + { + switch (change->Action) + { + case FILE_ACTION_ADDED: + event_id = SHCNE_CREATE; + pidls[0] = ILCreateFromPathW(path); + break; + + case FILE_ACTION_REMOVED: + event_id = SHCNE_DELETE; + pidls[0] = SHSimpleIDListFromPathW(path); + break; + + case FILE_ACTION_MODIFIED: + event_id = SHCNE_ATTRIBUTES; + pidls[0] = ILCreateFromPathW(path); + break; + + case FILE_ACTION_RENAMED_OLD_NAME: + pidls[0] = SHSimpleIDListFromPathW(path); + change = increment_fni(change, path, path_len); + if (change == NULL || change->Action != FILE_ACTION_RENAMED_NEW_NAME) + { + ERR("FILE_ACTION_RENAMED_OLD_NAME showed up without FILE_ACTION_RENAMED_NEW_NAME\n"); + skip = TRUE; + break; + } + pidls[1] = SHSimpleIDListFromPathW(path); + + event_id = PathIsDirectoryW(path) ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; + break; + + default: + TRACE("unexpected file action 0x%x\n", change->Action); + } + } + + if (!skip) + { + /* send notification message */ + if (pidls[0]) + { + TRACE("sending 0x%x for %s\n", event_id, wine_dbgstr_w(path)); + notify_succeeded = notify_recipient(changeinfo->nl->hwnd, changeinfo->nl->uMsg, + changeinfo->nl->dwFlags, event_id, pidls, &shared_data); + } + else + TRACE("pidl didn't get set for 0x%x %s\n", event_id, wine_dbgstr_w(path)); + } + + if (pidls[0]) + SHFree(pidls[0]); + if (pidls[1]) + SHFree(pidls[1]); + ZeroMemory(pidls, sizeof(pidls)); + + /* if notify_recipient returned FALSE, we want to just clean up and quit */ + if (notify_succeeded) + change = increment_fni(change, path, path_len); + else { + TRACE("notifications canceled\n"); + change = NULL; + } + } + + SHFreeShared(shared_data, GetCurrentProcessId()); + shared_data = NULL; + + changeinfo->use_buffer1 = !changeinfo->use_buffer1; + + if (notify_succeeded && !dirchangeinfo_register_callback(changeinfo)) + TRACE("failed to reissue ReadDirectoryChangesW\n"); +} + +static void CALLBACK directory_change_listener_thread(void *parameter) +{ + NOTIFICATIONLIST *item = (NOTIFICATIONLIST *)parameter; + UINT i; + DWORD wait_result; + + /* set up ReadDirectoryChangesW for each idlist being watched */ + for (i = 0; i < item->cidl; i++) + { + BOOL path_is_directory; + BOOL result; + + item->changeinfo[i].nl = item; + item->changeinfo[i].watch = &item->apidl[i]; + if (!SHGetPathFromIDListW(item->apidl[i].pidl, item->changeinfo[i].listen_path)) + { + TRACE("failed to get path for pidl (%p) (%s)\n", item->apidl[i].pidl, NodeName(item)); + continue; + } + + /* we need to see if this path points to a directory or not */ + path_is_directory = PathIsRootW(item->changeinfo[i].listen_path) + || PathIsDirectoryW(item->changeinfo[i].listen_path); + + if (!path_is_directory) + { + /* save the target file name */ + WCHAR *file_spec = get_file_spec_w(item->changeinfo[i].listen_path); + if (file_spec == NULL || file_spec == item->changeinfo[i].listen_path) + { + TRACE("failed to remove filename from %s\n", wine_dbgstr_w(item->changeinfo[i].listen_path)); + continue; + } + + item->changeinfo[i].target_file = (WCHAR *)SHAlloc(MAX_PATH); + strcpyW(item->changeinfo[i].target_file, file_spec); + PathRemoveFileSpecW(item->changeinfo[i].listen_path); + + TRACE("listen path: %s, target file: %s\n", + wine_dbgstr_w(item->changeinfo[i].listen_path), + wine_dbgstr_w(item->changeinfo[i].target_file)); + } + + /* grab a handle for the directory to watch */ + item->changeinfo[i].handle = CreateFileW( + item->changeinfo[i].listen_path, + FILE_LIST_DIRECTORY | SYNCHRONIZE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + + if (item->changeinfo[i].handle == INVALID_HANDLE_VALUE) + { + TRACE("failed to retrieve handle for %s\n", wine_dbgstr_w(item->changeinfo[i].listen_path)); + continue; + } + + result = dirchangeinfo_register_callback(&item->changeinfo[i]); + + if (!result) + ERR("ReadDirectoryChangesW failed on %s\n", wine_dbgstr_w(item->changeinfo[i].listen_path)); + } /* set up directory change listener */ + + /* notify main thread that we're done setting up */ + SetEvent(item->listener_ready); + + /* remain in an alertable wait state while the completion functions roll in */ + do { + wait_result = WaitForSingleObjectEx(item->listener_done, INFINITE, TRUE); + } while (wait_result == WAIT_IO_COMPLETION); + + TRACE("shutting down change listener\n"); +} + /************************************************************************* * SHChangeNotifyRegister [SHELL32.2] * @@ -253,6 +590,53 @@ SHChangeNotifyRegister(
LeaveCriticalSection(&SHELL32_ChangenotifyCS);
+ if (fSources & SHCNRF_InterruptLevel) + { + HANDLE handles[2]; + DWORD wait_result; + + TRACE("registering with SHCNRF_InterruptLevel\n"); + + item->changeinfo = SHAlloc(sizeof(DIRCHANGEINFO) * cItems); + ZeroMemory(item->changeinfo, sizeof(DIRCHANGEINFO) * cItems); + + /* listener_done is set by us when we want the listener to quit */ + item->listener_done = CreateEventW(NULL, FALSE, FALSE, NULL); + /* listener_ready is set by the listener when it's done setting up */ + handles[0] = item->listener_ready = CreateEventW(NULL, TRUE, FALSE, NULL); + handles[1] = item->listener_thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)directory_change_listener_thread, + (void *)item, 0, 0 + ); + + /* wait for the thread to either terminate or signal that it's done setting up */ + wait_result = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + + switch (wait_result) + { + case WAIT_OBJECT_0: + TRACE("listener thread started\n"); + break; + + case WAIT_OBJECT_0+1: + ERR("listener thread failed to start\n"); + break; + + case WAIT_FAILED: + ERR("failed to wait for change listener thread: %u\n", GetLastError()); + break; + + default: break; + } + } + else + { + item->listener_ready = NULL; + item->listener_done = NULL; + item->listener_thread = NULL; + } + + return item->id; }
@@ -394,7 +778,9 @@ void WINAPI SHChangeNotify(LONG wEventId, UINT uFlags, LPCVOID dwItem1, LPCVOID
if (wEventId & ptr->wEventMask) { - if( !pidl ) /* all ? */ + if ( !(ptr->dwFlags & SHCNRF_ShellLevel) ) + notify = FALSE; + else if( !pidl ) /* all ? */ notify = TRUE; else if( wEventId & SHCNE_NOITEMEVENTS ) notify = TRUE;
Converting PIDLs to unixfs in ShellView is no longer necessary with SHCNRF_InterruptLevel working properly. The conversion was a workaround that fixed pasted items not showing up, but caused new folders to sometimes not show up instead.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=30752 Signed-off-by: Nigel Baillie metreckk@gmail.com --- dlls/shell32/shlview.c | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-)
diff --git a/dlls/shell32/shlview.c b/dlls/shell32/shlview.c index 478a78ee2d..56c40753ac 100644 --- a/dlls/shell32/shlview.c +++ b/dlls/shell32/shlview.c @@ -700,31 +700,17 @@ static LRESULT ShellView_OnCreate(IShellViewImpl *This) hr = IShellFolder_QueryInterface(This->pSFParent, &IID_IPersistFolder2, (LPVOID*)&ppf2); if (hr == S_OK) { - LPITEMIDLIST raw_pidl; + LPITEMIDLIST cur_folder_pidl; SHChangeNotifyEntry ntreg;
- hr = IPersistFolder2_GetCurFolder(ppf2, &raw_pidl); + hr = IPersistFolder2_GetCurFolder(ppf2, &cur_folder_pidl); if(SUCCEEDED(hr)) { - LPITEMIDLIST computer_pidl; - SHGetFolderLocation(NULL,CSIDL_DRIVES,NULL,0,&computer_pidl); - if(ILIsParent(computer_pidl,raw_pidl,FALSE)) - { - /* Normalize the pidl to unixfs to workaround an issue with - * sending notifications on dos paths - */ - WCHAR path[MAX_PATH]; - SHGetPathFromIDListW(raw_pidl,path); - SHParseDisplayName(path,NULL,(LPITEMIDLIST*)&ntreg.pidl,0,NULL); - SHFree(raw_pidl); - } - else - ntreg.pidl = raw_pidl; + ntreg.pidl = cur_folder_pidl; ntreg.fRecursive = TRUE; - This->hNotify = SHChangeNotifyRegister(This->hWnd, SHCNRF_InterruptLevel, SHCNE_ALLEVENTS, + This->hNotify = SHChangeNotifyRegister(This->hWnd, SHCNRF_InterruptLevel | SHCNRF_ShellLevel, SHCNE_ALLEVENTS, SHV_CHANGE_NOTIFY, 1, &ntreg); SHFree((LPITEMIDLIST)ntreg.pidl); - SHFree(computer_pidl); } IPersistFolder2_Release(ppf2); }
Hi,
While running your changed tests on Windows, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=48302
Your paranoid android.
=== wxppro (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w2003std (32 bit report) ===
shell32: shlfolder.c:4955: Test failed: MKDIR: Expected wndproc to be called shlfolder.c:4867: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 8) shlfolder.c:4955: Test failed: CREATE: Expected wndproc to be called shlfolder.c:4867: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 2) shlfolder.c:4955: Test failed: RMDIR: Expected wndproc to be called shlfolder.c:4867: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 10) shlfolder.c:4955: Test failed: MKDIR: Expected wndproc to be called shlfolder.c:4867: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 6ac) shlfolder.c:4955: Test failed: CREATE: Expected wndproc to be called shlfolder.c:4867: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 6ac) shlfolder.c:4955: Test failed: RMDIR: Expected wndproc to be called shlfolder.c:4867: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 6ac) shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== wvistau64 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== wvistau64_zh_CN (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== wvistau64_fr (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== wvistau64_he (32 bit report) ===
shell32: shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w2008s64 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w7u (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w7pro64 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w8 (32 bit report) ===
shell32: shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called
=== w8adm (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called
=== w864 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w1064 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== wvistau64 (64 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w2008s64 (64 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w7pro64 (64 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w864 (64 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== w1064 (64 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit French report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit Japanese:Japan report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit Chinese:China report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (32 bit WoW report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called
=== debian9 (64 bit WoW report) ===
shell32: shlfolder.c:5036: Test failed: CREATE: Expected wndproc to be called shlfolder.c:5048: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5058: Test failed: DELETE: Expected wndproc to be called shlfolder.c:5091: Test failed: RENAMEITEM: Expected wndproc to be called shlfolder.c:5125: Test failed: CREATE: Expected wndproc to be called