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