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 
DbgAssert(bool x,const char * filename,int line,const char * expr)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
GetManager()91 CMenuFocusManager * CMenuFocusManager::GetManager()
92 {
93     return reinterpret_cast<CMenuFocusManager *>(TlsGetValue(TlsIndex));
94 }
95 
96 // Obtains a manager for the thread, with refcounting
AcquireManager()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
ReleaseManager(CMenuFocusManager * obj)121 void CMenuFocusManager::ReleaseManager(CMenuFocusManager * obj)
122 {
123     if (!obj->Release())
124     {
125         TlsSetValue(TlsIndex, NULL);
126     }
127 }
128 
s_MsgFilterHook(INT nCode,WPARAM wParam,LPARAM lParam)129 LRESULT CALLBACK CMenuFocusManager::s_MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
130 {
131     return GetManager()->MsgFilterHook(nCode, wParam, lParam);
132 }
133 
s_GetMsgHook(INT nCode,WPARAM wParam,LPARAM lParam)134 LRESULT CALLBACK CMenuFocusManager::s_GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
135 {
136     return GetManager()->GetMsgHook(nCode, wParam, lParam);
137 }
138 
PushToArray(StackEntryType type,CMenuBand * mb,HMENU hmenu)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 
PopFromArray(StackEntryType * pType,CMenuBand ** pMb,HMENU * pHmenu)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 
CMenuFocusManager()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 
~CMenuFocusManager()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
DisableMouseTrack(HWND parent,BOOL disableThis)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 
SetMenuCapture(HWND child)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 
IsTrackedWindow(HWND hWnd,StackEntry ** pentry)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 
IsTrackedWindowOrParent(HWND hWnd)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 
ProcessMouseMove(MSG * msg)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\n");
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 
ProcessMouseDown(MSG * msg,BOOL isLButton)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 
ProcessMouseUp(MSG * msg,BOOL isLButton)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 
MsgFilterHook(INT nCode,WPARAM hookWParam,LPARAM hookLParam)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 
GetMsgHook(INT nCode,WPARAM hookWParam,LPARAM hookLParam)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_LBUTTONDBLCLK:
581         case WM_LBUTTONDOWN:
582             isLButton = TRUE;
583             TRACE("LB\n");
584 
585             if (m_menuBar && m_current->type == MenuPopupEntry)
586             {
587                 POINT pt = msg->pt;
588                 HWND child = WindowFromPoint(pt);
589                 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
590                 if (hoveringMenuBar)
591                 {
592                     m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
593                     break;
594                 }
595             }
596 
597             if (m_current->type == MenuPopupEntry)
598             {
599                 HWND child = WindowFromPoint(pt);
600 
601                 if (IsTrackedWindowOrParent(child) != S_OK)
602                 {
603                     m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
604                     break;
605                 }
606             }
607 
608             ProcessMouseDown(msg, isLButton);
609 
610             break;
611         case WM_NCRBUTTONUP:
612         case WM_RBUTTONUP:
613             ProcessMouseUp(msg, isLButton);
614             break;
615         case WM_NCLBUTTONUP:
616         case WM_LBUTTONUP:
617             isLButton = TRUE;
618             ProcessMouseUp(msg, isLButton);
619             break;
620         case WM_MOUSEMOVE:
621             callNext = ProcessMouseMove(msg);
622             break;
623         case WM_MOUSELEAVE:
624             callNext = ProcessMouseMove(msg);
625             //callNext = ProcessMouseLeave(msg);
626             break;
627         case WM_SYSKEYDOWN:
628         case WM_KEYDOWN:
629             if (m_current->type == MenuPopupEntry)
630             {
631                 DisableMouseTrack(m_current->hwnd, TRUE);
632                 switch (msg->wParam)
633                 {
634                 case VK_ESCAPE:
635                 case VK_MENU:
636                 case VK_LMENU:
637                 case VK_RMENU:
638                     m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
639                     break;
640                 case VK_RETURN:
641                     m_current->mb->_MenuItemSelect(MPOS_EXECUTE);
642                     break;
643                 case VK_LEFT:
644                     m_current->mb->_MenuItemSelect(VK_LEFT);
645                     break;
646                 case VK_RIGHT:
647                     m_current->mb->_MenuItemSelect(VK_RIGHT);
648                     break;
649                 case VK_UP:
650                     m_current->mb->_MenuItemSelect(VK_UP);
651                     break;
652                 case VK_DOWN:
653                     m_current->mb->_MenuItemSelect(VK_DOWN);
654                     break;
655                 }
656                 msg->message = WM_NULL;
657                 msg->lParam = 0;
658                 msg->wParam = 0;
659             }
660             break;
661         }
662 
663         if (!callNext)
664             return 1;
665     }
666 
667     return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
668 }
669 
PlaceHooks()670 HRESULT CMenuFocusManager::PlaceHooks()
671 {
672     if (m_hGetMsgHook)
673     {
674         WARN("GETMESSAGE hook already placed!\n");
675         return S_OK;
676     }
677     if (m_hMsgFilterHook)
678     {
679         WARN("MSGFILTER hook already placed!\n");
680         return S_OK;
681     }
682     if (m_current->type == TrackedMenuEntry)
683     {
684         TRACE("Entering MSGFILTER hook...\n");
685         m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
686     }
687     else
688     {
689         TRACE("Entering GETMESSAGE hook...\n");
690         m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
691     }
692     return S_OK;
693 }
694 
RemoveHooks()695 HRESULT CMenuFocusManager::RemoveHooks()
696 {
697     if (m_hMsgFilterHook)
698     {
699         TRACE("Removing MSGFILTER hook...\n");
700         UnhookWindowsHookEx(m_hMsgFilterHook);
701         m_hMsgFilterHook = NULL;
702     }
703     if (m_hGetMsgHook)
704     {
705         TRACE("Removing GETMESSAGE hook...\n");
706         UnhookWindowsHookEx(m_hGetMsgHook);
707         m_hGetMsgHook = NULL;
708     }
709     return S_OK;
710 }
711 
712 // Used to update the tracking info to account for a change in the top-level menu
UpdateFocus()713 HRESULT CMenuFocusManager::UpdateFocus()
714 {
715     HRESULT hr;
716     StackEntry * old = m_current;
717 
718     TRACE("UpdateFocus\n");
719 
720     // Assign the new current item
721     if (m_bandCount > 0)
722         m_current = &(m_bandStack[m_bandCount - 1]);
723     else
724         m_current = NULL;
725 
726     // Remove the menu capture if necesary
727     if (!m_current || m_current->type != MenuPopupEntry)
728     {
729         SetMenuCapture(NULL);
730         if (old && old->type == MenuPopupEntry && m_PreviousForeground)
731         {
732             ::SetForegroundWindow(m_PreviousForeground);
733             m_PreviousForeground = NULL;
734         }
735     }
736 
737     // Obtain the top-level window for the new active menu
738     if (m_current && m_current->type != TrackedMenuEntry)
739     {
740         hr = m_current->mb->_GetTopLevelWindow(&(m_current->hwnd));
741         if (FAILED_UNEXPECTEDLY(hr))
742             return hr;
743     }
744 
745     // Refresh the parent pointer
746     if (m_bandCount >= 2)
747     {
748         m_parent = &(m_bandStack[m_bandCount - 2]);
749         _ASSERT(m_parent->type != TrackedMenuEntry);
750     }
751     else
752     {
753         m_parent = NULL;
754     }
755 
756     // Refresh the menubar pointer, if applicable
757     if (m_bandCount >= 1 && m_bandStack[0].type == MenuBarEntry)
758     {
759         m_menuBar = &(m_bandStack[0]);
760     }
761     else
762     {
763         m_menuBar = NULL;
764     }
765 
766     // Remove the old hooks if the menu type changed, or we don't have a menu anymore
767     if (old && (!m_current || old->type != m_current->type))
768     {
769         if (m_current && m_current->type != TrackedMenuEntry)
770         {
771             DisableMouseTrack(m_current->hwnd, FALSE);
772         }
773 
774         hr = RemoveHooks();
775         if (FAILED_UNEXPECTEDLY(hr))
776             return hr;
777     }
778 
779     // And place new ones if necessary
780     if (m_current && (!old || old->type != m_current->type))
781     {
782         hr = PlaceHooks();
783         if (FAILED_UNEXPECTEDLY(hr))
784             return hr;
785     }
786 
787     // Give the user a chance to move the mouse to the new menu
788     if (m_parent)
789     {
790         DisableMouseTrack(m_parent->hwnd, TRUE);
791     }
792 
793     if (m_current && m_current->type == MenuPopupEntry)
794     {
795         if (m_captureHwnd == NULL)
796         {
797             // We need to restore the capture after a non-shell submenu or context menu is shown
798             StackEntry * topMenu = m_bandStack;
799             if (topMenu->type == MenuBarEntry)
800                 topMenu++;
801 
802             // Get the top-level window from the top popup
803             CComPtr<IServiceProvider> bandSite;
804             CComPtr<IOleWindow> deskBar;
805             hr = topMenu->mb->GetSite(IID_PPV_ARG(IServiceProvider, &bandSite));
806             if (FAILED(hr))
807                 goto NoCapture;
808             hr = bandSite->QueryService(SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &deskBar));
809             if (FAILED(hr))
810                 goto NoCapture;
811 
812             CComPtr<IOleWindow> deskBarSite;
813             hr = IUnknown_GetSite(deskBar, IID_PPV_ARG(IOleWindow, &deskBarSite));
814             if (FAILED(hr))
815                 goto NoCapture;
816 
817             // FIXME: Find the correct place for this
818             HWND hWndOwner;
819             hr = deskBarSite->GetWindow(&hWndOwner);
820             if (FAILED(hr))
821                 goto NoCapture;
822 
823             m_PreviousForeground = ::GetForegroundWindow();
824             if (m_PreviousForeground != hWndOwner)
825                 ::SetForegroundWindow(hWndOwner);
826             else
827                 m_PreviousForeground = NULL;
828 
829             // Get the HWND of the top-level window
830             HWND hWndSite;
831             hr = deskBar->GetWindow(&hWndSite);
832             if (FAILED(hr))
833                 goto NoCapture;
834             SetMenuCapture(hWndSite);
835         }
836 NoCapture:
837 
838         if (!m_parent || m_parent->type == MenuBarEntry)
839         {
840             if (old && old->type == TrackedMenuEntry)
841             {
842                 // FIXME: Debugging code, probably not right
843                 POINT pt2;
844                 RECT rc2;
845                 GetCursorPos(&pt2);
846                 ScreenToClient(m_current->hwnd, &pt2);
847                 GetClientRect(m_current->hwnd, &rc2);
848                 if (PtInRect(&rc2, pt2))
849                     SendMessage(m_current->hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt2.x, pt2.y));
850                 else
851                     SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
852             }
853         }
854     }
855 
856     _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
857 
858     return S_OK;
859 }
860 
861 // Begin tracking top-level menu bar (for file browser windows)
PushMenuBar(CMenuBand * mb)862 HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
863 {
864     TRACE("PushMenuBar %p\n", mb);
865 
866     mb->AddRef();
867 
868     _ASSERT(m_bandCount == 0);
869 
870     HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
871     if (FAILED_UNEXPECTEDLY(hr))
872         return hr;
873 
874     return UpdateFocus();
875 }
876 
877 // Begin tracking a shell menu popup (start menu or submenus)
PushMenuPopup(CMenuBand * mb)878 HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
879 {
880     TRACE("PushTrackedPopup %p\n", mb);
881 
882     mb->AddRef();
883 
884     _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
885 
886     HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
887     if (FAILED_UNEXPECTEDLY(hr))
888         return hr;
889 
890     hr = UpdateFocus();
891 
892     m_menuDepth++;
893 
894     if (m_parent && m_parent->type != TrackedMenuEntry)
895     {
896         m_parent->mb->_SetChildBand(mb);
897         mb->_SetParentBand(m_parent->mb);
898     }
899 
900     return hr;
901 }
902 
903 // Begin tracking a system popup submenu (submenu of the file browser windows)
PushTrackedPopup(HMENU popup)904 HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
905 {
906     TRACE("PushTrackedPopup %p\n", popup);
907 
908     _ASSERT(m_bandCount > 0);
909     _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
910 
911     HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
912     if (FAILED_UNEXPECTEDLY(hr))
913         return hr;
914 
915     TRACE("PushTrackedPopup %p\n", popup);
916     m_selectedMenu = popup;
917     m_selectedItem = -1;
918     m_selectedItemFlags = 0;
919 
920     return UpdateFocus();
921 }
922 
923 // Stop tracking the menubar
PopMenuBar(CMenuBand * mb)924 HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
925 {
926     StackEntryType type;
927     CMenuBand * mbc;
928     HRESULT hr;
929 
930     TRACE("PopMenuBar %p\n", mb);
931 
932     if (m_current == m_entryUnderMouse)
933     {
934         m_entryUnderMouse = NULL;
935     }
936 
937     hr = PopFromArray(&type, &mbc, NULL);
938     if (FAILED_UNEXPECTEDLY(hr))
939     {
940         UpdateFocus();
941         return hr;
942     }
943 
944     _ASSERT(type == MenuBarEntry);
945     if (type != MenuBarEntry)
946         return E_FAIL;
947 
948     if (!mbc)
949         return E_FAIL;
950 
951     mbc->_SetParentBand(NULL);
952 
953     mbc->Release();
954 
955     hr = UpdateFocus();
956     if (FAILED_UNEXPECTEDLY(hr))
957         return hr;
958 
959     if (m_current)
960     {
961         _ASSERT(m_current->type != TrackedMenuEntry);
962         m_current->mb->_SetChildBand(NULL);
963     }
964 
965     return S_OK;
966 }
967 
968 // Stop tracking a shell menu
PopMenuPopup(CMenuBand * mb)969 HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
970 {
971     StackEntryType type;
972     CMenuBand * mbc;
973     HRESULT hr;
974 
975     TRACE("PopMenuPopup %p\n", mb);
976 
977     if (m_current == m_entryUnderMouse)
978     {
979         m_entryUnderMouse = NULL;
980     }
981 
982     m_menuDepth--;
983 
984     hr = PopFromArray(&type, &mbc, NULL);
985     if (FAILED_UNEXPECTEDLY(hr))
986     {
987         UpdateFocus();
988         return hr;
989     }
990 
991     _ASSERT(type == MenuPopupEntry);
992     if (type != MenuPopupEntry)
993         return E_FAIL;
994 
995     if (!mbc)
996         return E_FAIL;
997 
998     mbc->_SetParentBand(NULL);
999 
1000     mbc->Release();
1001 
1002     hr = UpdateFocus();
1003     if (FAILED_UNEXPECTEDLY(hr))
1004         return hr;
1005 
1006     if (m_current)
1007     {
1008         _ASSERT(m_current->type != TrackedMenuEntry);
1009         m_current->mb->_SetChildBand(NULL);
1010     }
1011 
1012     return S_OK;
1013 }
1014 
1015 // Stop tracking a system popup submenu
PopTrackedPopup(HMENU popup)1016 HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
1017 {
1018     StackEntryType type;
1019     HMENU hmenu;
1020     HRESULT hr;
1021 
1022     TRACE("PopTrackedPopup %p\n", popup);
1023 
1024     hr = PopFromArray(&type, NULL, &hmenu);
1025     if (FAILED_UNEXPECTEDLY(hr))
1026     {
1027         UpdateFocus();
1028         return hr;
1029     }
1030 
1031     _ASSERT(type == TrackedMenuEntry);
1032     if (type != TrackedMenuEntry)
1033         return E_FAIL;
1034 
1035     if (hmenu != popup)
1036         return E_FAIL;
1037 
1038     hr = UpdateFocus();
1039     if (FAILED_UNEXPECTEDLY(hr))
1040         return hr;
1041 
1042     return S_OK;
1043 }
1044