1 /*
2  * Shell Menu Band
3  *
4  * Copyright 2014 David Quintana
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 /*
22 This file implements the CMenuFocusManager class.
23 
24 This class manages the shell menus, by overriding the hot-tracking behaviour.
25 
26 For the shell menus, it uses a GetMessage hook,
27 where it intercepts messages directed to the menu windows.
28 
29 In order to show submenus using system popups, it also has a MessageFilter hook.
30 
31 The menu is tracked using a stack structure. When a CMenuBand wants to open a submenu,
32 it pushes the submenu band, or HMENU to track in case of system popups,
33 and when the menu has closed, it pops the same pointer or handle.
34 
35 While a shell menu is open, it overrides the menu toolbar's hottracking behaviour,
36 using its own logic to track both the active menu item, and the opened submenu's parent item.
37 
38 While a system popup is open, it tracks the mouse movements so that it can cancel the popup,
39 and switch to another submenu when the mouse goes over another item from the parent.
40 
41 */
42 #include "shellmenu.h"
43 #include <windowsx.h>
44 #include <commoncontrols.h>
45 #include <shlwapi_undoc.h>
46 
47 #include "CMenuFocusManager.h"
48 #include "CMenuToolbars.h"
49 #include "CMenuBand.h"
50 
51 #if DBG
52 #   undef _ASSERT
53 #   define _ASSERT(x) DbgAssert(!!(x), __FILE__, __LINE__, #x)
54 
55 bool DbgAssert(bool x, const char * filename, int line, const char * expr)
56 {
57     if (!x)
58     {
59         char szMsg[512];
60         const char *fname;
61 
62         fname = strrchr(filename, '\\');
63         if (fname == NULL)
64         {
65             fname = strrchr(filename, '/');
66         }
67 
68         if (fname == NULL)
69             fname = filename;
70         else
71             fname++;
72 
73         sprintf(szMsg, "%s:%d: Assertion failed: %s\n", fname, line, expr);
74 
75         OutputDebugStringA(szMsg);
76 
77         __debugbreak();
78     }
79     return x;
80 }
81 #else
82 #   undef _ASSERT
83 #   define _ASSERT(x) (!!(x))
84 #endif
85 
86 WINE_DEFAULT_DEBUG_CHANNEL(CMenuFocus);
87 
88 DWORD CMenuFocusManager::TlsIndex = 0;
89 
90 // Gets the thread's assigned manager without refcounting
91 CMenuFocusManager * CMenuFocusManager::GetManager()
92 {
93     return reinterpret_cast<CMenuFocusManager *>(TlsGetValue(TlsIndex));
94 }
95 
96 // Obtains a manager for the thread, with refcounting
97 CMenuFocusManager * CMenuFocusManager::AcquireManager()
98 {
99     CMenuFocusManager * obj = NULL;
100 
101     if (!TlsIndex)
102     {
103         if ((TlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
104             return NULL;
105     }
106 
107     obj = GetManager();
108 
109     if (!obj)
110     {
111         obj = new CComObject<CMenuFocusManager>();
112         TlsSetValue(TlsIndex, obj);
113     }
114 
115     obj->AddRef();
116 
117     return obj;
118 }
119 
120 // Releases a previously acquired manager, and deletes it if the refcount reaches 0
121 void CMenuFocusManager::ReleaseManager(CMenuFocusManager * obj)
122 {
123     if (!obj->Release())
124     {
125         TlsSetValue(TlsIndex, NULL);
126     }
127 }
128 
129 LRESULT CALLBACK CMenuFocusManager::s_MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
130 {
131     return GetManager()->MsgFilterHook(nCode, wParam, lParam);
132 }
133 
134 LRESULT CALLBACK CMenuFocusManager::s_GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
135 {
136     return GetManager()->GetMsgHook(nCode, wParam, lParam);
137 }
138 
139 HRESULT CMenuFocusManager::PushToArray(StackEntryType type, CMenuBand * mb, HMENU hmenu)
140 {
141     if (m_bandCount >= MAX_RECURSE)
142         return E_OUTOFMEMORY;
143 
144     m_bandStack[m_bandCount].type = type;
145     m_bandStack[m_bandCount].mb = mb;
146     m_bandStack[m_bandCount].hmenu = hmenu;
147     m_bandCount++;
148 
149     return S_OK;
150 }
151 
152 HRESULT CMenuFocusManager::PopFromArray(StackEntryType * pType, CMenuBand ** pMb, HMENU * pHmenu)
153 {
154     if (pType)  *pType = NoEntry;
155     if (pMb)    *pMb = NULL;
156     if (pHmenu) *pHmenu = NULL;
157 
158     if (m_bandCount <= 0)
159         return S_FALSE;
160 
161     m_bandCount--;
162 
163     if (pType)  *pType = m_bandStack[m_bandCount].type;
164     if (*pType == TrackedMenuEntry)
165     {
166         if (pHmenu) *pHmenu = m_bandStack[m_bandCount].hmenu;
167     }
168     else
169     {
170         if (pMb) *pMb = m_bandStack[m_bandCount].mb;
171     }
172 
173     return S_OK;
174 }
175 
176 CMenuFocusManager::CMenuFocusManager() :
177     m_current(NULL),
178     m_parent(NULL),
179     m_hMsgFilterHook(NULL),
180     m_hGetMsgHook(NULL),
181     m_mouseTrackDisabled(FALSE),
182     m_captureHwnd(0),
183     m_hwndUnderMouse(NULL),
184     m_entryUnderMouse(NULL),
185     m_selectedMenu(NULL),
186     m_selectedItem(0),
187     m_selectedItemFlags(0),
188     m_movedSinceDown(FALSE),
189     m_windowAtDown(NULL),
190     m_PreviousForeground(NULL),
191     m_bandCount(0),
192     m_menuDepth(0)
193 {
194     m_ptPrev.x = 0;
195     m_ptPrev.y = 0;
196     m_threadId = GetCurrentThreadId();
197 }
198 
199 CMenuFocusManager::~CMenuFocusManager()
200 {
201 }
202 
203 // Used so that the toolbar can properly ignore mouse events, when the menu is being used with the keyboard
204 void CMenuFocusManager::DisableMouseTrack(HWND parent, BOOL disableThis)
205 {
206     BOOL bDisable = FALSE;
207     BOOL lastDisable = FALSE;
208 
209     int i = m_bandCount;
210     while (--i >= 0)
211     {
212         StackEntry& entry = m_bandStack[i];
213 
214         if (entry.type != TrackedMenuEntry)
215         {
216             HWND hwnd;
217             HRESULT hr = entry.mb->_GetTopLevelWindow(&hwnd);
218             if (FAILED_UNEXPECTEDLY(hr))
219                 break;
220 
221             if (hwnd == parent)
222             {
223                 lastDisable = disableThis;
224                 entry.mb->_DisableMouseTrack(disableThis);
225                 bDisable = TRUE;
226             }
227             else
228             {
229                 lastDisable = bDisable;
230                 entry.mb->_DisableMouseTrack(bDisable);
231             }
232         }
233     }
234     m_mouseTrackDisabled = lastDisable;
235 }
236 
237 void CMenuFocusManager::SetMenuCapture(HWND child)
238 {
239     if (m_captureHwnd != child)
240     {
241         if (child)
242         {
243             ::SetCapture(child);
244             m_captureHwnd = child;
245             TRACE("Capturing %p\n", child);
246         }
247         else
248         {
249             ::ReleaseCapture();
250             m_captureHwnd = NULL;
251             TRACE("Capture is now off\n");
252         }
253 
254     }
255 }
256 
257 HRESULT CMenuFocusManager::IsTrackedWindow(HWND hWnd, StackEntry ** pentry)
258 {
259     if (pentry)
260         *pentry = NULL;
261 
262     for (int i = m_bandCount; --i >= 0;)
263     {
264         StackEntry& entry = m_bandStack[i];
265 
266         if (entry.type != TrackedMenuEntry)
267         {
268             HRESULT hr = entry.mb->IsWindowOwner(hWnd);
269             if (FAILED_UNEXPECTEDLY(hr))
270                 return hr;
271             if (hr == S_OK)
272             {
273                 if (pentry)
274                     *pentry = &entry;
275                 return S_OK;
276             }
277         }
278     }
279 
280     return S_FALSE;
281 }
282 
283 HRESULT CMenuFocusManager::IsTrackedWindowOrParent(HWND hWnd)
284 {
285     for (int i = m_bandCount; --i >= 0;)
286     {
287         StackEntry& entry = m_bandStack[i];
288 
289         if (entry.type != TrackedMenuEntry)
290         {
291             HRESULT hr = entry.mb->IsWindowOwner(hWnd);
292             if (FAILED_UNEXPECTEDLY(hr))
293                 return hr;
294             if (hr == S_OK)
295                 return S_OK;
296             if (entry.mb->_IsPopup() == S_OK)
297             {
298                 CComPtr<IUnknown> site;
299                 CComPtr<IOleWindow> pw;
300                 hr = entry.mb->GetSite(IID_PPV_ARG(IUnknown, &site));
301                 if (FAILED_UNEXPECTEDLY(hr))
302                     continue;
303                 hr = IUnknown_QueryService(site, SID_SMenuBandParent, IID_PPV_ARG(IOleWindow, &pw));
304                 if (FAILED_UNEXPECTEDLY(hr))
305                     continue;
306 
307                 HWND hParent;
308                 if (pw->GetWindow(&hParent) == S_OK && hParent == hWnd)
309                     return S_OK;
310             }
311         }
312     }
313 
314     return S_FALSE;
315 }
316 
317 LRESULT CMenuFocusManager::ProcessMouseMove(MSG* msg)
318 {
319     HWND child;
320     int iHitTestResult = -1;
321 
322     POINT pt2 = { GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam) };
323     ClientToScreen(msg->hwnd, &pt2);
324 
325     POINT pt = msg->pt;
326 
327     // Don't do anything if another window is capturing the mouse.
328     HWND cCapture = ::GetCapture();
329     if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
330         return TRUE;
331 
332     m_movedSinceDown = TRUE;
333 
334     m_ptPrev = pt;
335 
336     child = WindowFromPoint(pt);
337 
338     StackEntry * entry = NULL;
339     if (IsTrackedWindow(child, &entry) == S_OK)
340     {
341         TRACE("MouseMove");
342     }
343 
344     BOOL isTracking = FALSE;
345     if (entry && (entry->type == MenuBarEntry || m_current->type != TrackedMenuEntry))
346     {
347         ScreenToClient(child, &pt);
348         iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
349         isTracking = entry->mb->_IsTracking();
350 
351         if (SendMessage(child, WM_USER_ISTRACKEDITEM, iHitTestResult, 0) == S_FALSE)
352         {
353             // The current tracked item has changed, notify the toolbar
354 
355             TRACE("Hot item tracking detected a change (capture=%p / cCapture=%p)...\n", m_captureHwnd, cCapture);
356             DisableMouseTrack(NULL, FALSE);
357             if (isTracking && iHitTestResult >= 0 && m_current->type == TrackedMenuEntry)
358                 SendMessage(entry->hwnd, WM_CANCELMODE, 0, 0);
359             PostMessage(child, WM_USER_CHANGETRACKEDITEM, iHitTestResult, MAKELPARAM(isTracking, TRUE));
360             if (m_current->type == TrackedMenuEntry)
361                 return FALSE;
362         }
363     }
364 
365     if (m_entryUnderMouse != entry)
366     {
367         // Mouse moved away from a tracked window
368         if (m_entryUnderMouse)
369         {
370             m_entryUnderMouse->mb->_ChangeHotItem(NULL, -1, HICF_MOUSE);
371         }
372     }
373 
374     if (m_hwndUnderMouse != child)
375     {
376         if (entry)
377         {
378             // Mouse moved to a tracked window
379             if (m_current->type == MenuPopupEntry)
380             {
381                 ScreenToClient(child, &pt2);
382                 SendMessage(child, WM_MOUSEMOVE, msg->wParam, MAKELPARAM(pt2.x, pt2.y));
383             }
384         }
385 
386         m_hwndUnderMouse = child;
387         m_entryUnderMouse = entry;
388     }
389 
390     if (m_current->type == MenuPopupEntry)
391     {
392         HWND parent = GetAncestor(child, GA_ROOT);
393         DisableMouseTrack(parent, FALSE);
394     }
395 
396     return TRUE;
397 }
398 
399 LRESULT CMenuFocusManager::ProcessMouseDown(MSG* msg, BOOL isLButton)
400 {
401     HWND child;
402     int iHitTestResult = -1;
403 
404     TRACE("ProcessMouseDown %d %d %d\n", msg->message, msg->wParam, msg->lParam);
405 
406     // Don't do anything if another window is capturing the mouse.
407     HWND cCapture = ::GetCapture();
408     if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
409     {
410         TRACE("Foreign capture active.\n");
411         return TRUE;
412     }
413 
414     POINT pt = msg->pt;
415 
416     child = WindowFromPoint(pt);
417 
418     StackEntry * entry = NULL;
419     if (IsTrackedWindow(child, &entry) != S_OK)
420     {
421         TRACE("Foreign window detected.\n");
422         return TRUE;
423     }
424 
425     if (entry->type == MenuBarEntry)
426     {
427         if (entry != m_current)
428         {
429             TRACE("Menubar with popup active.\n");
430             return TRUE;
431         }
432     }
433 
434     if (entry)
435     {
436         ScreenToClient(child, &pt);
437         iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
438 
439         if (iHitTestResult >= 0)
440         {
441             TRACE("MouseDown send %d\n", iHitTestResult);
442             entry->mb->_MenuBarMouseDown(child, iHitTestResult, isLButton);
443         }
444     }
445 
446     msg->message = WM_NULL;
447 
448     m_movedSinceDown = FALSE;
449     m_windowAtDown = child;
450 
451     TRACE("MouseDown end\n");
452 
453     return TRUE;
454 }
455 
456 LRESULT CMenuFocusManager::ProcessMouseUp(MSG* msg, BOOL isLButton)
457 {
458     HWND child;
459     int iHitTestResult = -1;
460 
461     TRACE("ProcessMouseUp %d %d %d\n", msg->message, msg->wParam, msg->lParam);
462 
463     // Don't do anything if another window is capturing the mouse.
464     HWND cCapture = ::GetCapture();
465     if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
466         return TRUE;
467 
468     POINT pt = msg->pt;
469 
470     child = WindowFromPoint(pt);
471 
472     StackEntry * entry = NULL;
473     if (IsTrackedWindow(child, &entry) != S_OK)
474         return TRUE;
475 
476     if (entry)
477     {
478         ScreenToClient(child, &pt);
479         iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
480 
481         if (iHitTestResult >= 0)
482         {
483             TRACE("MouseUp send %d\n", iHitTestResult);
484             entry->mb->_MenuBarMouseUp(child, iHitTestResult, isLButton);
485         }
486     }
487 
488     return TRUE;
489 }
490 
491 LRESULT CMenuFocusManager::MsgFilterHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
492 {
493     if (nCode < 0)
494         return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
495 
496     if (nCode == MSGF_MENU)
497     {
498         BOOL callNext = TRUE;
499         MSG* msg = reinterpret_cast<MSG*>(hookLParam);
500 
501         switch (msg->message)
502         {
503         case WM_LBUTTONDOWN:
504         case WM_RBUTTONDOWN:
505             if (m_menuBar && m_current->type == TrackedMenuEntry)
506             {
507                 POINT pt = msg->pt;
508                 HWND child = WindowFromPoint(pt);
509                 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
510                 if (hoveringMenuBar)
511                 {
512                     m_menuBar->mb->_BeforeCancelPopup();
513                 }
514             }
515             break;
516         case WM_MOUSEMOVE:
517             callNext = ProcessMouseMove(msg);
518             break;
519         case WM_INITMENUPOPUP:
520             TRACE("WM_INITMENUPOPUP %p %p\n", msg->wParam, msg->lParam);
521             m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
522             m_selectedItem = -1;
523             m_selectedItemFlags = 0;
524             break;
525         case WM_MENUSELECT:
526             TRACE("WM_MENUSELECT %p %p\n", msg->wParam, msg->lParam);
527             m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
528             m_selectedItem = GET_X_LPARAM(msg->wParam);
529             m_selectedItemFlags = HIWORD(msg->wParam);
530             break;
531         case WM_KEYDOWN:
532             switch (msg->wParam)
533             {
534             case VK_LEFT:
535                 if (m_current->hmenu == m_selectedMenu)
536                 {
537                     m_parent->mb->_MenuItemSelect(VK_LEFT);
538                 }
539                 break;
540             case VK_RIGHT:
541                 if (m_selectedItem < 0 || !(m_selectedItemFlags & MF_POPUP))
542                 {
543                     m_parent->mb->_MenuItemSelect(VK_RIGHT);
544                 }
545                 break;
546             }
547             break;
548         }
549 
550         if (!callNext)
551             return 1;
552     }
553 
554     return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
555 }
556 
557 LRESULT CMenuFocusManager::GetMsgHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
558 {
559     BOOL isLButton = FALSE;
560     if (nCode < 0)
561         return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
562 
563     if (nCode == HC_ACTION)
564     {
565         BOOL callNext = TRUE;
566         MSG* msg = reinterpret_cast<MSG*>(hookLParam);
567         POINT pt = msg->pt;
568 
569         switch (msg->message)
570         {
571         case WM_CAPTURECHANGED:
572             if (m_captureHwnd)
573             {
574                 TRACE("Capture lost.\n");
575                 m_captureHwnd = NULL;
576             }
577             break;
578 
579         case WM_NCLBUTTONDOWN:
580         case WM_LBUTTONDOWN:
581             isLButton = TRUE;
582             TRACE("LB\n");
583 
584             if (m_menuBar && m_current->type == MenuPopupEntry)
585             {
586                 POINT pt = msg->pt;
587                 HWND child = WindowFromPoint(pt);
588                 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
589                 if (hoveringMenuBar)
590                 {
591                     m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
592                     break;
593                 }
594             }
595 
596             if (m_current->type == MenuPopupEntry)
597             {
598                 HWND child = WindowFromPoint(pt);
599 
600                 if (IsTrackedWindowOrParent(child) != S_OK)
601                 {
602                     m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
603                     break;
604                 }
605             }
606 
607             ProcessMouseDown(msg, isLButton);
608 
609             break;
610         case WM_NCRBUTTONUP:
611         case WM_RBUTTONUP:
612             ProcessMouseUp(msg, isLButton);
613             break;
614         case WM_NCLBUTTONUP:
615         case WM_LBUTTONUP:
616             isLButton = TRUE;
617             ProcessMouseUp(msg, isLButton);
618             break;
619         case WM_MOUSEMOVE:
620             callNext = ProcessMouseMove(msg);
621             break;
622         case WM_MOUSELEAVE:
623             callNext = ProcessMouseMove(msg);
624             //callNext = ProcessMouseLeave(msg);
625             break;
626         case WM_SYSKEYDOWN:
627         case WM_KEYDOWN:
628             if (m_current->type == MenuPopupEntry)
629             {
630                 DisableMouseTrack(m_current->hwnd, TRUE);
631                 switch (msg->wParam)
632                 {
633                 case VK_ESCAPE:
634                 case VK_MENU:
635                 case VK_LMENU:
636                 case VK_RMENU:
637                     m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
638                     break;
639                 case VK_RETURN:
640                     m_current->mb->_MenuItemSelect(MPOS_EXECUTE);
641                     break;
642                 case VK_LEFT:
643                     m_current->mb->_MenuItemSelect(VK_LEFT);
644                     break;
645                 case VK_RIGHT:
646                     m_current->mb->_MenuItemSelect(VK_RIGHT);
647                     break;
648                 case VK_UP:
649                     m_current->mb->_MenuItemSelect(VK_UP);
650                     break;
651                 case VK_DOWN:
652                     m_current->mb->_MenuItemSelect(VK_DOWN);
653                     break;
654                 }
655                 msg->message = WM_NULL;
656                 msg->lParam = 0;
657                 msg->wParam = 0;
658             }
659             break;
660         }
661 
662         if (!callNext)
663             return 1;
664     }
665 
666     return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
667 }
668 
669 HRESULT CMenuFocusManager::PlaceHooks()
670 {
671     if (m_hGetMsgHook)
672     {
673         WARN("GETMESSAGE hook already placed!\n");
674         return S_OK;
675     }
676     if (m_hMsgFilterHook)
677     {
678         WARN("MSGFILTER hook already placed!\n");
679         return S_OK;
680     }
681     if (m_current->type == TrackedMenuEntry)
682     {
683         TRACE("Entering MSGFILTER hook...\n");
684         m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
685     }
686     else
687     {
688         TRACE("Entering GETMESSAGE hook...\n");
689         m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
690     }
691     return S_OK;
692 }
693 
694 HRESULT CMenuFocusManager::RemoveHooks()
695 {
696     if (m_hMsgFilterHook)
697     {
698         TRACE("Removing MSGFILTER hook...\n");
699         UnhookWindowsHookEx(m_hMsgFilterHook);
700         m_hMsgFilterHook = NULL;
701     }
702     if (m_hGetMsgHook)
703     {
704         TRACE("Removing GETMESSAGE hook...\n");
705         UnhookWindowsHookEx(m_hGetMsgHook);
706         m_hGetMsgHook = NULL;
707     }
708     return S_OK;
709 }
710 
711 // Used to update the tracking info to account for a change in the top-level menu
712 HRESULT CMenuFocusManager::UpdateFocus()
713 {
714     HRESULT hr;
715     StackEntry * old = m_current;
716 
717     TRACE("UpdateFocus\n");
718 
719     // Assign the new current item
720     if (m_bandCount > 0)
721         m_current = &(m_bandStack[m_bandCount - 1]);
722     else
723         m_current = NULL;
724 
725     // Remove the menu capture if necesary
726     if (!m_current || m_current->type != MenuPopupEntry)
727     {
728         SetMenuCapture(NULL);
729         if (old && old->type == MenuPopupEntry && m_PreviousForeground)
730         {
731             ::SetForegroundWindow(m_PreviousForeground);
732             m_PreviousForeground = NULL;
733         }
734     }
735 
736     // Obtain the top-level window for the new active menu
737     if (m_current && m_current->type != TrackedMenuEntry)
738     {
739         hr = m_current->mb->_GetTopLevelWindow(&(m_current->hwnd));
740         if (FAILED_UNEXPECTEDLY(hr))
741             return hr;
742     }
743 
744     // Refresh the parent pointer
745     if (m_bandCount >= 2)
746     {
747         m_parent = &(m_bandStack[m_bandCount - 2]);
748         _ASSERT(m_parent->type != TrackedMenuEntry);
749     }
750     else
751     {
752         m_parent = NULL;
753     }
754 
755     // Refresh the menubar pointer, if applicable
756     if (m_bandCount >= 1 && m_bandStack[0].type == MenuBarEntry)
757     {
758         m_menuBar = &(m_bandStack[0]);
759     }
760     else
761     {
762         m_menuBar = NULL;
763     }
764 
765     // Remove the old hooks if the menu type changed, or we don't have a menu anymore
766     if (old && (!m_current || old->type != m_current->type))
767     {
768         if (m_current && m_current->type != TrackedMenuEntry)
769         {
770             DisableMouseTrack(m_current->hwnd, FALSE);
771         }
772 
773         hr = RemoveHooks();
774         if (FAILED_UNEXPECTEDLY(hr))
775             return hr;
776     }
777 
778     // And place new ones if necessary
779     if (m_current && (!old || old->type != m_current->type))
780     {
781         hr = PlaceHooks();
782         if (FAILED_UNEXPECTEDLY(hr))
783             return hr;
784     }
785 
786     // Give the user a chance to move the mouse to the new menu
787     if (m_parent)
788     {
789         DisableMouseTrack(m_parent->hwnd, TRUE);
790     }
791 
792     if (m_current && m_current->type == MenuPopupEntry)
793     {
794         if (m_captureHwnd == NULL)
795         {
796             // We need to restore the capture after a non-shell submenu or context menu is shown
797             StackEntry * topMenu = m_bandStack;
798             if (topMenu->type == MenuBarEntry)
799                 topMenu++;
800 
801             // Get the top-level window from the top popup
802             CComPtr<IServiceProvider> bandSite;
803             CComPtr<IOleWindow> deskBar;
804             hr = topMenu->mb->GetSite(IID_PPV_ARG(IServiceProvider, &bandSite));
805             if (FAILED(hr))
806                 goto NoCapture;
807             hr = bandSite->QueryService(SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &deskBar));
808             if (FAILED(hr))
809                 goto NoCapture;
810 
811             CComPtr<IOleWindow> deskBarSite;
812             hr = IUnknown_GetSite(deskBar, IID_PPV_ARG(IOleWindow, &deskBarSite));
813             if (FAILED(hr))
814                 goto NoCapture;
815 
816             // FIXME: Find the correct place for this
817             HWND hWndOwner;
818             hr = deskBarSite->GetWindow(&hWndOwner);
819             if (FAILED(hr))
820                 goto NoCapture;
821 
822             m_PreviousForeground = ::GetForegroundWindow();
823             if (m_PreviousForeground != hWndOwner)
824                 ::SetForegroundWindow(hWndOwner);
825             else
826                 m_PreviousForeground = NULL;
827 
828             // Get the HWND of the top-level window
829             HWND hWndSite;
830             hr = deskBar->GetWindow(&hWndSite);
831             if (FAILED(hr))
832                 goto NoCapture;
833             SetMenuCapture(hWndSite);
834         }
835 NoCapture:
836 
837         if (!m_parent || m_parent->type == MenuBarEntry)
838         {
839             if (old && old->type == TrackedMenuEntry)
840             {
841                 // FIXME: Debugging code, probably not right
842                 POINT pt2;
843                 RECT rc2;
844                 GetCursorPos(&pt2);
845                 ScreenToClient(m_current->hwnd, &pt2);
846                 GetClientRect(m_current->hwnd, &rc2);
847                 if (PtInRect(&rc2, pt2))
848                     SendMessage(m_current->hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt2.x, pt2.y));
849                 else
850                     SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
851             }
852         }
853     }
854 
855     _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
856 
857     return S_OK;
858 }
859 
860 // Begin tracking top-level menu bar (for file browser windows)
861 HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
862 {
863     TRACE("PushMenuBar %p\n", mb);
864 
865     mb->AddRef();
866 
867     _ASSERT(m_bandCount == 0);
868 
869     HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
870     if (FAILED_UNEXPECTEDLY(hr))
871         return hr;
872 
873     return UpdateFocus();
874 }
875 
876 // Begin tracking a shell menu popup (start menu or submenus)
877 HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
878 {
879     TRACE("PushTrackedPopup %p\n", mb);
880 
881     mb->AddRef();
882 
883     _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
884 
885     HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
886     if (FAILED_UNEXPECTEDLY(hr))
887         return hr;
888 
889     hr = UpdateFocus();
890 
891     m_menuDepth++;
892 
893     if (m_parent && m_parent->type != TrackedMenuEntry)
894     {
895         m_parent->mb->_SetChildBand(mb);
896         mb->_SetParentBand(m_parent->mb);
897     }
898 
899     return hr;
900 }
901 
902 // Begin tracking a system popup submenu (submenu of the file browser windows)
903 HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
904 {
905     TRACE("PushTrackedPopup %p\n", popup);
906 
907     _ASSERT(m_bandCount > 0);
908     _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
909 
910     HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
911     if (FAILED_UNEXPECTEDLY(hr))
912         return hr;
913 
914     TRACE("PushTrackedPopup %p\n", popup);
915     m_selectedMenu = popup;
916     m_selectedItem = -1;
917     m_selectedItemFlags = 0;
918 
919     return UpdateFocus();
920 }
921 
922 // Stop tracking the menubar
923 HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
924 {
925     StackEntryType type;
926     CMenuBand * mbc;
927     HRESULT hr;
928 
929     TRACE("PopMenuBar %p\n", mb);
930 
931     if (m_current == m_entryUnderMouse)
932     {
933         m_entryUnderMouse = NULL;
934     }
935 
936     hr = PopFromArray(&type, &mbc, NULL);
937     if (FAILED_UNEXPECTEDLY(hr))
938     {
939         UpdateFocus();
940         return hr;
941     }
942 
943     _ASSERT(type == MenuBarEntry);
944     if (type != MenuBarEntry)
945         return E_FAIL;
946 
947     if (!mbc)
948         return E_FAIL;
949 
950     mbc->_SetParentBand(NULL);
951 
952     mbc->Release();
953 
954     hr = UpdateFocus();
955     if (FAILED_UNEXPECTEDLY(hr))
956         return hr;
957 
958     if (m_current)
959     {
960         _ASSERT(m_current->type != TrackedMenuEntry);
961         m_current->mb->_SetChildBand(NULL);
962     }
963 
964     return S_OK;
965 }
966 
967 // Stop tracking a shell menu
968 HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
969 {
970     StackEntryType type;
971     CMenuBand * mbc;
972     HRESULT hr;
973 
974     TRACE("PopMenuPopup %p\n", mb);
975 
976     if (m_current == m_entryUnderMouse)
977     {
978         m_entryUnderMouse = NULL;
979     }
980 
981     m_menuDepth--;
982 
983     hr = PopFromArray(&type, &mbc, NULL);
984     if (FAILED_UNEXPECTEDLY(hr))
985     {
986         UpdateFocus();
987         return hr;
988     }
989 
990     _ASSERT(type == MenuPopupEntry);
991     if (type != MenuPopupEntry)
992         return E_FAIL;
993 
994     if (!mbc)
995         return E_FAIL;
996 
997     mbc->_SetParentBand(NULL);
998 
999     mbc->Release();
1000 
1001     hr = UpdateFocus();
1002     if (FAILED_UNEXPECTEDLY(hr))
1003         return hr;
1004 
1005     if (m_current)
1006     {
1007         _ASSERT(m_current->type != TrackedMenuEntry);
1008         m_current->mb->_SetChildBand(NULL);
1009     }
1010 
1011     return S_OK;
1012 }
1013 
1014 // Stop tracking a system popup submenu
1015 HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
1016 {
1017     StackEntryType type;
1018     HMENU hmenu;
1019     HRESULT hr;
1020 
1021     TRACE("PopTrackedPopup %p\n", popup);
1022 
1023     hr = PopFromArray(&type, NULL, &hmenu);
1024     if (FAILED_UNEXPECTEDLY(hr))
1025     {
1026         UpdateFocus();
1027         return hr;
1028     }
1029 
1030     _ASSERT(type == TrackedMenuEntry);
1031     if (type != TrackedMenuEntry)
1032         return E_FAIL;
1033 
1034     if (hmenu != popup)
1035         return E_FAIL;
1036 
1037     hr = UpdateFocus();
1038     if (FAILED_UNEXPECTEDLY(hr))
1039         return hr;
1040 
1041     return S_OK;
1042 }