xref: /reactos/dll/win32/shell32/changenotify.cpp (revision ede7a20a)
1 /*
2  * PROJECT:     shell32
3  * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * PURPOSE:     Shell change notification
5  * COPYRIGHT:   Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6  */
7 
8 #include "precomp.h"
9 
10 WINE_DEFAULT_DEBUG_CHANNEL(shcn);
11 
12 CRITICAL_SECTION SHELL32_ChangenotifyCS;
13 
14 // This function requests creation of the server window if it doesn't exist yet
15 static HWND
16 GetNotificationServer(BOOL bCreate)
17 {
18     static HWND s_hwndServer = NULL;
19 
20     // use cache if any
21     if (s_hwndServer && IsWindow(s_hwndServer))
22         return s_hwndServer;
23 
24     // get the shell window
25     HWND hwndShell = GetShellWindow();
26     if (hwndShell == NULL)
27     {
28         TRACE("GetShellWindow() returned NULL\n");
29         return NULL;
30     }
31 
32     // Get the window of the notification server that runs in explorer
33     HWND hwndServer = (HWND)SendMessageW(hwndShell, WM_DESKTOP_GET_CNOTIFY_SERVER, bCreate, 0);
34     if (!IsWindow(hwndServer))
35     {
36         ERR("Unable to get server window\n");
37         hwndServer = NULL;
38     }
39 
40     // save and return
41     s_hwndServer = hwndServer;
42     return hwndServer;
43 }
44 
45 // This function will be called from DllMain.DLL_PROCESS_ATTACH.
46 EXTERN_C void InitChangeNotifications(void)
47 {
48     InitializeCriticalSection(&SHELL32_ChangenotifyCS);
49 }
50 
51 // This function will be called from DllMain.DLL_PROCESS_DETACH.
52 EXTERN_C void FreeChangeNotifications(void)
53 {
54     HWND hwndServer = GetNotificationServer(FALSE);
55     if (hwndServer)
56         SendMessageW(hwndServer, CN_UNREGISTER_PROCESS, GetCurrentProcessId(), 0);
57     DeleteCriticalSection(&SHELL32_ChangenotifyCS);
58 }
59 
60 //////////////////////////////////////////////////////////////////////////////////////
61 // There are two delivery methods: "old delivery method" and "new delivery method".
62 //
63 // The old delivery method creates a broker window in the caller process
64 // for message trampoline. The old delivery method is slow and deprecated.
65 //
66 // The new delivery method is enabled by SHCNRF_NewDelivery flag.
67 // With the new delivery method the server directly sends the delivery message.
68 
69 typedef CWinTraits <
70     WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
71     WS_EX_TOOLWINDOW
72 > CBrokerTraits;
73 
74 // This class brokers all notifications that don't have the SHCNRF_NewDelivery flag
75 class CChangeNotifyBroker :
76     public CWindowImpl<CChangeNotifyBroker, CWindow, CBrokerTraits>
77 {
78 public:
79     CChangeNotifyBroker(HWND hwndClient, UINT uMsg) :
80         m_hwndClient(hwndClient), m_uMsg(uMsg)
81     {
82     }
83 
84     // Message handlers
85     LRESULT OnBrokerNotification(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
86     {
87         return BrokerNotification((HANDLE)wParam, (DWORD)lParam);
88     }
89 
90     void OnFinalMessage(HWND)
91     {
92         // The server will destroy this window.
93         // After the window gets destroyed we can delete this broker here.
94         delete this;
95     }
96 
97     DECLARE_WND_CLASS_EX(L"WorkerW", 0, 0)
98 
99     BEGIN_MSG_MAP(CChangeNotifyBroker)
100         MESSAGE_HANDLER(WM_BROKER_NOTIFICATION, OnBrokerNotification)
101     END_MSG_MAP()
102 
103 private:
104     HWND m_hwndClient;
105     UINT m_uMsg;
106 
107     BOOL BrokerNotification(HANDLE hTicket, DWORD dwOwnerPID)
108     {
109         // lock the ticket
110         PIDLIST_ABSOLUTE *ppidl = NULL;
111         LONG lEvent;
112         HANDLE hLock = SHChangeNotification_Lock(hTicket, dwOwnerPID, &ppidl, &lEvent);
113         if (hLock == NULL)
114         {
115             ERR("hLock is NULL\n");
116             return FALSE;
117         }
118 
119         // perform the delivery
120         TRACE("broker notifying: %p, 0x%x, %p, 0x%lx\n",
121               m_hwndClient, m_uMsg, ppidl, lEvent);
122         SendMessageW(m_hwndClient, m_uMsg, (WPARAM)ppidl, lEvent);
123 
124         // unlock the ticket
125         SHChangeNotification_Unlock(hLock);
126         return TRUE;
127     }
128 };
129 
130 // This function creates a notification broker for old method. Used in SHChangeNotifyRegister.
131 static HWND
132 CreateNotificationBroker(HWND hwnd, UINT wMsg)
133 {
134     // Create a new broker. It will be freed when the window gets destroyed
135     CChangeNotifyBroker* pBroker = new CChangeNotifyBroker(hwnd, wMsg);
136     if (pBroker == NULL)
137     {
138         ERR("Out of memory\n");
139         return NULL;
140     }
141 
142     HWND hwndBroker = pBroker->Create(0);
143     if (hwndBroker == NULL)
144     {
145         ERR("hwndBroker == NULL\n");
146         delete pBroker;
147     }
148 
149     return hwndBroker;
150 }
151 
152 // This function creates a delivery ticket for shell change nofitication.
153 // Used in SHChangeNotify.
154 static HANDLE
155 CreateNotificationParam(LONG wEventId, UINT uFlags, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2,
156                         DWORD dwOwnerPID, DWORD dwTick)
157 {
158     // pidl1 and pidl2 have variable length. To store them into the delivery ticket,
159     // we have to consider the offsets and the sizes of pidl1 and pidl2.
160     DWORD cbPidl1 = 0, cbPidl2 = 0, ibOffset1 = 0, ibOffset2 = 0;
161     if (pidl1)
162     {
163         cbPidl1 = ILGetSize(pidl1);
164         ibOffset1 = DWORD_ALIGNMENT(sizeof(DELITICKET));
165     }
166     if (pidl2)
167     {
168         cbPidl2 = ILGetSize(pidl2);
169         ibOffset2 = DWORD_ALIGNMENT(ibOffset1 + cbPidl1);
170     }
171 
172     // allocate the delivery ticket
173     DWORD cbSize = ibOffset2 + cbPidl2;
174     HANDLE hTicket = SHAllocShared(NULL, cbSize, dwOwnerPID);
175     if (hTicket == NULL)
176     {
177         ERR("Out of memory\n");
178         return NULL;
179     }
180 
181     // lock the ticket
182     LPDELITICKET pTicket = (LPDELITICKET)SHLockSharedEx(hTicket, dwOwnerPID, TRUE);
183     if (pTicket == NULL)
184     {
185         ERR("SHLockSharedEx failed\n");
186         SHFreeShared(hTicket, dwOwnerPID);
187         return NULL;
188     }
189 
190     // populate the ticket
191     pTicket->dwMagic  = DELITICKET_MAGIC;
192     pTicket->wEventId = wEventId;
193     pTicket->uFlags = uFlags;
194     pTicket->ibOffset1 = ibOffset1;
195     pTicket->ibOffset2 = ibOffset2;
196     if (pidl1)
197         memcpy((LPBYTE)pTicket + ibOffset1, pidl1, cbPidl1);
198     if (pidl2)
199         memcpy((LPBYTE)pTicket + ibOffset2, pidl2, cbPidl2);
200 
201     // unlock the ticket and return
202     SHUnlockShared(pTicket);
203     return hTicket;
204 }
205 
206 // This function creates a "handbag" by using a delivery ticket.
207 // The handbag is created in SHChangeNotification_Lock and used in OnBrokerNotification.
208 // hTicket is a ticket handle of a shared memory block and dwOwnerPID is
209 // the owner PID of the ticket.
210 static LPHANDBAG
211 DoGetHandbagFromTicket(HANDLE hTicket, DWORD dwOwnerPID)
212 {
213     // lock and validate the delivery ticket
214     LPDELITICKET pTicket = (LPDELITICKET)SHLockSharedEx(hTicket, dwOwnerPID, FALSE);
215     if (pTicket == NULL || pTicket->dwMagic != DELITICKET_MAGIC)
216     {
217         ERR("pTicket is invalid\n");
218         SHUnlockShared(pTicket);
219         return NULL;
220     }
221 
222     // allocate the handbag
223     LPHANDBAG pHandbag = (LPHANDBAG)LocalAlloc(LMEM_FIXED, sizeof(HANDBAG));
224     if (pHandbag == NULL)
225     {
226         ERR("Out of memory\n");
227         SHUnlockShared(pTicket);
228         return NULL;
229     }
230 
231     // populate the handbag
232     pHandbag->dwMagic = HANDBAG_MAGIC;
233     pHandbag->pTicket = pTicket;
234 
235     pHandbag->pidls[0] = pHandbag->pidls[1] = NULL;
236     if (pTicket->ibOffset1)
237         pHandbag->pidls[0] = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset1);
238     if (pTicket->ibOffset2)
239         pHandbag->pidls[1] = (LPITEMIDLIST)((LPBYTE)pTicket + pTicket->ibOffset2);
240 
241     return pHandbag;
242 }
243 
244 // This function creates a registration entry in SHChangeNotifyRegister function.
245 static HANDLE
246 CreateRegistrationParam(ULONG nRegID, HWND hwnd, UINT wMsg, INT fSources, LONG fEvents,
247                         LONG fRecursive, LPCITEMIDLIST pidl, DWORD dwOwnerPID,
248                         HWND hwndBroker)
249 {
250     // pidl has variable length. To store it into the registration entry,
251     // we have to consider the length of pidl.
252     DWORD cbPidl = ILGetSize(pidl);
253     DWORD ibPidl = DWORD_ALIGNMENT(sizeof(REGENTRY));
254     DWORD cbSize = ibPidl + cbPidl;
255 
256     // create the registration entry and lock it
257     HANDLE hRegEntry = SHAllocShared(NULL, cbSize, dwOwnerPID);
258     if (hRegEntry == NULL)
259     {
260         ERR("Out of memory\n");
261         return NULL;
262     }
263     LPREGENTRY pRegEntry = (LPREGENTRY)SHLockSharedEx(hRegEntry, dwOwnerPID, TRUE);
264     if (pRegEntry == NULL)
265     {
266         ERR("SHLockSharedEx failed\n");
267         SHFreeShared(hRegEntry, dwOwnerPID);
268         return NULL;
269     }
270 
271     // populate the registration entry
272     pRegEntry->dwMagic = REGENTRY_MAGIC;
273     pRegEntry->cbSize = cbSize;
274     pRegEntry->nRegID = nRegID;
275     pRegEntry->hwnd = hwnd;
276     pRegEntry->uMsg = wMsg;
277     pRegEntry->fSources = fSources;
278     pRegEntry->fEvents = fEvents;
279     pRegEntry->fRecursive = fRecursive;
280     pRegEntry->hwndBroker = hwndBroker;
281     pRegEntry->ibPidl = 0;
282     if (pidl)
283     {
284         pRegEntry->ibPidl = ibPidl;
285         memcpy((LPBYTE)pRegEntry + ibPidl, pidl, cbPidl);
286     }
287 
288     // unlock and return
289     SHUnlockShared(pRegEntry);
290     return hRegEntry;
291 }
292 
293 // This function is the body of SHChangeNotify function.
294 // It creates a delivery ticket and send CN_DELIVER_NOTIFICATION message to
295 // transport the change.
296 static void
297 CreateNotificationParamAndSend(LONG wEventId, UINT uFlags, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2,
298                                DWORD dwTick)
299 {
300     // get server window
301     HWND hwndServer = GetNotificationServer(FALSE);
302     if (hwndServer == NULL)
303         return;
304 
305     // the ticket owner is the process of the notification server
306     DWORD pid;
307     GetWindowThreadProcessId(hwndServer, &pid);
308 
309     // create a delivery ticket
310     HANDLE hTicket = CreateNotificationParam(wEventId, uFlags, pidl1, pidl2, pid, dwTick);
311     if (hTicket == NULL)
312         return;
313 
314     TRACE("hTicket: %p, 0x%lx\n", hTicket, pid);
315 
316     // send the ticket by using CN_DELIVER_NOTIFICATION
317     if (pid != GetCurrentProcessId() ||
318         (uFlags & (SHCNF_FLUSH | SHCNF_FLUSHNOWAIT)) == SHCNF_FLUSH)
319     {
320         SendMessageW(hwndServer, CN_DELIVER_NOTIFICATION, (WPARAM)hTicket, pid);
321     }
322     else
323     {
324         SendNotifyMessageW(hwndServer, CN_DELIVER_NOTIFICATION, (WPARAM)hTicket, pid);
325     }
326 }
327 
328 struct ALIAS_PIDL
329 {
330     INT csidl1; // from
331     INT csidl2; // to
332     LPITEMIDLIST pidl1; // from
333     LPITEMIDLIST pidl2; // to
334     WCHAR szPath1[MAX_PATH]; // from
335     WCHAR szPath2[MAX_PATH]; // to
336 };
337 
338 static ALIAS_PIDL AliasPIDLs[] =
339 {
340     { CSIDL_PERSONAL, CSIDL_PERSONAL },
341     { CSIDL_DESKTOP, CSIDL_COMMON_DESKTOPDIRECTORY, },
342     { CSIDL_DESKTOP, CSIDL_DESKTOPDIRECTORY },
343 };
344 
345 static VOID DoInitAliasPIDLs(void)
346 {
347     static BOOL s_bInit = FALSE;
348     if (!s_bInit)
349     {
350         for (SIZE_T i = 0; i < _countof(AliasPIDLs); ++i)
351         {
352             ALIAS_PIDL *alias = &AliasPIDLs[i];
353 
354             SHGetSpecialFolderLocation(NULL, alias->csidl1, &alias->pidl1);
355             SHGetPathFromIDListW(alias->pidl1, alias->szPath1);
356 
357             SHGetSpecialFolderLocation(NULL, alias->csidl2, &alias->pidl2);
358             SHGetPathFromIDListW(alias->pidl2, alias->szPath2);
359         }
360         s_bInit = TRUE;
361     }
362 }
363 
364 static BOOL DoGetAliasPIDLs(LPITEMIDLIST apidls[2], PCIDLIST_ABSOLUTE pidl)
365 {
366     DoInitAliasPIDLs();
367 
368     apidls[0] = apidls[1] = NULL;
369 
370     INT k = 0;
371     for (SIZE_T i = 0; i < _countof(AliasPIDLs); ++i)
372     {
373         const ALIAS_PIDL *alias = &AliasPIDLs[i];
374         if (ILIsEqual(pidl, alias->pidl1))
375         {
376             if (alias->csidl1 == alias->csidl2)
377             {
378                 apidls[k++] = ILCreateFromPathW(alias->szPath2);
379             }
380             else
381             {
382                 apidls[k++] = ILClone(alias->pidl2);
383             }
384             if (k >= 2)
385                 break;
386         }
387     }
388 
389     return k > 0;
390 }
391 
392 /*************************************************************************
393  * SHChangeNotifyRegister           [SHELL32.2]
394  */
395 EXTERN_C ULONG WINAPI
396 SHChangeNotifyRegister(HWND hwnd, INT fSources, LONG wEventMask, UINT uMsg,
397                        INT cItems, SHChangeNotifyEntry *lpItems)
398 {
399     HWND hwndServer, hwndBroker = NULL;
400     HANDLE hRegEntry;
401     INT iItem;
402     ULONG nRegID = INVALID_REG_ID;
403     DWORD dwOwnerPID;
404     LPREGENTRY pRegEntry;
405 
406     TRACE("(%p,0x%08x,0x%08x,0x%08x,%d,%p)\n",
407           hwnd, fSources, wEventMask, uMsg, cItems, lpItems);
408 
409     // sanity check
410     if (wEventMask == 0 || cItems <= 0 || cItems > 0x7FFF || lpItems == NULL ||
411         hwnd == NULL || !IsWindow(hwnd))
412     {
413         return INVALID_REG_ID;
414     }
415 
416     // request the window of the server
417     hwndServer = GetNotificationServer(TRUE);
418     if (hwndServer == NULL)
419         return INVALID_REG_ID;
420 
421     // disable recursive interrupt in specific condition
422     if ((fSources & SHCNRF_RecursiveInterrupt) && !(fSources & SHCNRF_InterruptLevel))
423     {
424         fSources &= ~SHCNRF_RecursiveInterrupt;
425     }
426 
427     // if it is old delivery method, then create a broker window
428     if ((fSources & SHCNRF_NewDelivery) == 0)
429     {
430         hwndBroker = hwnd = CreateNotificationBroker(hwnd, uMsg);
431         uMsg = WM_BROKER_NOTIFICATION;
432     }
433 
434     // The owner PID is the process ID of the server
435     GetWindowThreadProcessId(hwndServer, &dwOwnerPID);
436 
437     EnterCriticalSection(&SHELL32_ChangenotifyCS);
438     for (iItem = 0; iItem < cItems; ++iItem)
439     {
440         // create a registration entry
441         hRegEntry = CreateRegistrationParam(nRegID, hwnd, uMsg, fSources, wEventMask,
442                                             lpItems[iItem].fRecursive, lpItems[iItem].pidl,
443                                             dwOwnerPID, hwndBroker);
444         if (hRegEntry)
445         {
446             TRACE("CN_REGISTER: hwnd:%p, hRegEntry:%p, pid:0x%lx\n",
447                   hwndServer, hRegEntry, dwOwnerPID);
448 
449             // send CN_REGISTER to the server
450             SendMessageW(hwndServer, CN_REGISTER, (WPARAM)hRegEntry, dwOwnerPID);
451 
452             // update nRegID
453             pRegEntry = (LPREGENTRY)SHLockSharedEx(hRegEntry, dwOwnerPID, FALSE);
454             if (pRegEntry)
455             {
456                 nRegID = pRegEntry->nRegID;
457                 SHUnlockShared(pRegEntry);
458             }
459 
460             // free registration entry
461             SHFreeShared(hRegEntry, dwOwnerPID);
462         }
463 
464         if (nRegID == INVALID_REG_ID)
465         {
466             ERR("Delivery failed\n");
467 
468             if (hwndBroker)
469             {
470                 // destroy the broker
471                 DestroyWindow(hwndBroker);
472             }
473             break;
474         }
475 
476         // PIDL alias
477         LPITEMIDLIST apidlAlias[2];
478         if (DoGetAliasPIDLs(apidlAlias, lpItems[iItem].pidl))
479         {
480             if (apidlAlias[0])
481             {
482                 // create another registration entry
483                 hRegEntry = CreateRegistrationParam(nRegID, hwnd, uMsg, fSources, wEventMask,
484                                                     lpItems[iItem].fRecursive, apidlAlias[0],
485                                                     dwOwnerPID, hwndBroker);
486                 if (hRegEntry)
487                 {
488                     TRACE("CN_REGISTER: hwnd:%p, hRegEntry:%p, pid:0x%lx\n",
489                           hwndServer, hRegEntry, dwOwnerPID);
490 
491                     // send CN_REGISTER to the server
492                     SendMessageW(hwndServer, CN_REGISTER, (WPARAM)hRegEntry, dwOwnerPID);
493 
494                     // free registration entry
495                     SHFreeShared(hRegEntry, dwOwnerPID);
496                 }
497                 ILFree(apidlAlias[0]);
498             }
499 
500             if (apidlAlias[1])
501             {
502                 // create another registration entry
503                 hRegEntry = CreateRegistrationParam(nRegID, hwnd, uMsg, fSources, wEventMask,
504                                                     lpItems[iItem].fRecursive, apidlAlias[1],
505                                                     dwOwnerPID, hwndBroker);
506                 if (hRegEntry)
507                 {
508                     TRACE("CN_REGISTER: hwnd:%p, hRegEntry:%p, pid:0x%lx\n",
509                           hwndServer, hRegEntry, dwOwnerPID);
510 
511                     // send CN_REGISTER to the server
512                     SendMessageW(hwndServer, CN_REGISTER, (WPARAM)hRegEntry, dwOwnerPID);
513 
514                     // free registration entry
515                     SHFreeShared(hRegEntry, dwOwnerPID);
516                 }
517                 ILFree(apidlAlias[1]);
518             }
519         }
520     }
521     LeaveCriticalSection(&SHELL32_ChangenotifyCS);
522 
523     return nRegID;
524 }
525 
526 /*************************************************************************
527  * SHChangeNotifyDeregister         [SHELL32.4]
528  */
529 EXTERN_C BOOL WINAPI
530 SHChangeNotifyDeregister(ULONG hNotify)
531 {
532     TRACE("(0x%08x)\n", hNotify);
533 
534     // get the server window
535     HWND hwndServer = GetNotificationServer(FALSE);
536     if (hwndServer == NULL)
537         return FALSE;
538 
539     // send CN_UNREGISTER message and try to unregister
540     BOOL ret = (BOOL)SendMessageW(hwndServer, CN_UNREGISTER, hNotify, 0);
541     if (!ret)
542         ERR("CN_UNREGISTER failed\n");
543 
544     return ret;
545 }
546 
547 /*************************************************************************
548  * SHChangeNotifyUpdateEntryList            [SHELL32.5]
549  */
550 EXTERN_C BOOL WINAPI
551 SHChangeNotifyUpdateEntryList(DWORD unknown1, DWORD unknown2,
552                               DWORD unknown3, DWORD unknown4)
553 {
554     FIXME("(0x%08x, 0x%08x, 0x%08x, 0x%08x)\n",
555           unknown1, unknown2, unknown3, unknown4);
556     return TRUE;
557 }
558 
559 /* for dumping events */
560 static LPCSTR DumpEvent(LONG event)
561 {
562     if (event == SHCNE_ALLEVENTS)
563         return "SHCNE_ALLEVENTS";
564 #define DUMPEV(x)  ,( event & SHCNE_##x )? #x " " : ""
565     return wine_dbg_sprintf( "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"
566     DUMPEV(RENAMEITEM)
567     DUMPEV(CREATE)
568     DUMPEV(DELETE)
569     DUMPEV(MKDIR)
570     DUMPEV(RMDIR)
571     DUMPEV(MEDIAINSERTED)
572     DUMPEV(MEDIAREMOVED)
573     DUMPEV(DRIVEREMOVED)
574     DUMPEV(DRIVEADD)
575     DUMPEV(NETSHARE)
576     DUMPEV(NETUNSHARE)
577     DUMPEV(ATTRIBUTES)
578     DUMPEV(UPDATEDIR)
579     DUMPEV(UPDATEITEM)
580     DUMPEV(SERVERDISCONNECT)
581     DUMPEV(UPDATEIMAGE)
582     DUMPEV(DRIVEADDGUI)
583     DUMPEV(RENAMEFOLDER)
584     DUMPEV(FREESPACE)
585     DUMPEV(EXTENDED_EVENT)
586     DUMPEV(ASSOCCHANGED)
587     DUMPEV(INTERRUPT)
588     );
589 #undef DUMPEV
590 }
591 
592 /*************************************************************************
593  * SHChangeRegistrationReceive      [SHELL32.646]
594  */
595 EXTERN_C BOOL WINAPI
596 SHChangeRegistrationReceive(LPVOID lpUnknown1, DWORD dwUnknown2)
597 {
598     FIXME("SHChangeRegistrationReceive() stub\n");
599     return FALSE;
600 }
601 
602 EXTERN_C VOID WINAPI
603 SHChangeNotifyReceiveEx(LONG lEvent, UINT uFlags,
604                         LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, DWORD dwTick)
605 {
606     // TODO: Queueing notifications
607     CreateNotificationParamAndSend(lEvent, uFlags, pidl1, pidl2, dwTick);
608 }
609 
610 /*************************************************************************
611  * SHChangeNotifyReceive        [SHELL32.643]
612  */
613 EXTERN_C VOID WINAPI
614 SHChangeNotifyReceive(LONG lEvent, UINT uFlags, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
615 {
616     SHChangeNotifyReceiveEx(lEvent, uFlags, pidl1, pidl2, GetTickCount());
617 }
618 
619 EXTERN_C VOID WINAPI
620 SHChangeNotifyTransmit(LONG lEvent, UINT uFlags, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, DWORD dwTick)
621 {
622     SHChangeNotifyReceiveEx(lEvent, uFlags, pidl1, pidl2, dwTick);
623 }
624 
625 /*************************************************************************
626  * SHChangeNotify               [SHELL32.@]
627  */
628 EXTERN_C void WINAPI
629 SHChangeNotify(LONG wEventId, UINT uFlags, LPCVOID dwItem1, LPCVOID dwItem2)
630 {
631     LPITEMIDLIST pidl1 = NULL, pidl2 = NULL, pidlTemp1 = NULL, pidlTemp2 = NULL;
632     DWORD dwTick = GetTickCount();
633     WCHAR szPath1[MAX_PATH], szPath2[MAX_PATH];
634     LPWSTR psz1, psz2;
635     TRACE("(0x%08x,0x%08x,%p,%p)\n", wEventId, uFlags, dwItem1, dwItem2);
636 
637     switch (uFlags & SHCNF_TYPE)
638     {
639         case SHCNF_IDLIST:
640             pidl1 = (LPITEMIDLIST)dwItem1;
641             pidl2 = (LPITEMIDLIST)dwItem2;
642             break;
643 
644         case SHCNF_PATHA:
645             psz1 = psz2 = NULL;
646             if (dwItem1)
647             {
648                 SHAnsiToUnicode((LPCSTR)dwItem1, szPath1, _countof(szPath1));
649                 psz1 = szPath1;
650             }
651             if (dwItem2)
652             {
653                 SHAnsiToUnicode((LPCSTR)dwItem2, szPath2, _countof(szPath2));
654                 psz2 = szPath2;
655             }
656             uFlags &= ~SHCNF_TYPE;
657             uFlags |= SHCNF_PATHW;
658             SHChangeNotify(wEventId, uFlags, psz1, psz2);
659             return;
660 
661         case SHCNF_PATHW:
662             if (dwItem1)
663             {
664                 pidl1 = pidlTemp1 = SHSimpleIDListFromPathW((LPCWSTR)dwItem1);
665             }
666             if (dwItem2)
667             {
668                 pidl2 = pidlTemp2 = SHSimpleIDListFromPathW((LPCWSTR)dwItem2);
669             }
670             break;
671 
672         case SHCNF_PRINTERA:
673         case SHCNF_PRINTERW:
674             FIXME("SHChangeNotify with (uFlags & SHCNF_PRINTER)\n");
675             return;
676 
677         default:
678             FIXME("unknown type %08x\n", uFlags & SHCNF_TYPE);
679             return;
680     }
681 
682     if (wEventId == 0 || (wEventId & SHCNE_ASSOCCHANGED) || pidl1 != NULL)
683     {
684         TRACE("notifying event %s(%x)\n", DumpEvent(wEventId), wEventId);
685         SHChangeNotifyTransmit(wEventId, uFlags, pidl1, pidl2, dwTick);
686     }
687 
688     if (pidlTemp1)
689         ILFree(pidlTemp1);
690     if (pidlTemp2)
691         ILFree(pidlTemp2);
692 }
693 
694 /*************************************************************************
695  * NTSHChangeNotifyRegister            [SHELL32.640]
696  */
697 EXTERN_C ULONG WINAPI
698 NTSHChangeNotifyRegister(HWND hwnd, INT fSources, LONG fEvents, UINT msg,
699                          INT count, SHChangeNotifyEntry *idlist)
700 {
701     return SHChangeNotifyRegister(hwnd, fSources | SHCNRF_NewDelivery,
702                                   fEvents, msg, count, idlist);
703 }
704 
705 /*************************************************************************
706  * SHChangeNotification_Lock            [SHELL32.644]
707  */
708 EXTERN_C HANDLE WINAPI
709 SHChangeNotification_Lock(HANDLE hTicket, DWORD dwOwnerPID, LPITEMIDLIST **lppidls,
710                           LPLONG lpwEventId)
711 {
712     TRACE("%p %08x %p %p\n", hTicket, dwOwnerPID, lppidls, lpwEventId);
713 
714     // create a handbag from the ticket
715     LPHANDBAG pHandbag = DoGetHandbagFromTicket(hTicket, dwOwnerPID);
716     if (pHandbag == NULL || pHandbag->dwMagic != HANDBAG_MAGIC)
717     {
718         ERR("pHandbag is invalid\n");
719         return NULL;
720     }
721 
722     // populate parameters from the handbag
723     if (lppidls)
724         *lppidls = pHandbag->pidls;
725     if (lpwEventId)
726         *lpwEventId = (pHandbag->pTicket->wEventId & ~SHCNE_INTERRUPT);
727 
728     // return the handbag
729     return pHandbag;
730 }
731 
732 /*************************************************************************
733  * SHChangeNotification_Unlock          [SHELL32.645]
734  */
735 EXTERN_C BOOL WINAPI
736 SHChangeNotification_Unlock(HANDLE hLock)
737 {
738     TRACE("%p\n", hLock);
739 
740     // validate the handbag
741     LPHANDBAG pHandbag = (LPHANDBAG)hLock;
742     if (pHandbag == NULL || pHandbag->dwMagic != HANDBAG_MAGIC)
743     {
744         ERR("pHandbag is invalid\n");
745         return FALSE;
746     }
747 
748     // free the handbag
749     BOOL ret = SHUnlockShared(pHandbag->pTicket);
750     LocalFree(hLock);
751     return ret;
752 }
753 
754 /*************************************************************************
755  * NTSHChangeNotifyDeregister           [SHELL32.641]
756  */
757 EXTERN_C DWORD WINAPI
758 NTSHChangeNotifyDeregister(ULONG hNotify)
759 {
760     FIXME("(0x%08x):semi stub.\n", hNotify);
761     return SHChangeNotifyDeregister(hNotify);
762 }
763 
764 /*************************************************************************
765  * SHChangeNotifySuspendResume          [SHELL32.277]
766  */
767 EXTERN_C BOOL
768 WINAPI
769 SHChangeNotifySuspendResume(BOOL bSuspend,
770                             LPITEMIDLIST pidl,
771                             BOOL bRecursive,
772                             DWORD dwReserved)
773 {
774     FIXME("SHChangeNotifySuspendResume() stub\n");
775     return FALSE;
776 }
777