The CMenuSite class and the window it creates act as an intermediary between a BaseBar and a MenuBand, forwarding the events and messages either to the child band, or to the parent bar, as necessary. It handles the sizing of the child band, to adapt it to changes in the available space. It also provides certain services related to the positioning and sizing of the child bar, which it handles by forwarding some requests to the child, and the rest to the parent.
This implementation was done within rshell, a DLL created by Giannis that is used to provide alternative implementations of the shell32 classes, which can be referenced instead of the real ones while constructing the Start Menu. With rshell, the code can be tested directly from within Visual Studio, using all of the debugging features of the IDE.
Gigaherz continued by writing a wrapper for CMenuBand that logs the calls, with parameters and return values, done to a real CMenuBand from Windows. This allowed for seeing the essential features needed to implement the basic displaying of content in a CMenuBand, in the way expected by Windows' Start Menu and Menu Site.
The rough, partial implementation of CMenuBand was ported from ReactOS' shell32 into rshell. Allow the start menu to use this CMenuBand required implementing support for QueryService and Exec methods. Although the behavior of the Exec system is still largely unknown, there's enough information from the call logs and stack traces to realize that command id 16 is used to set "large icons" mode, and id 19 is used in the Popup function, and requires returning S_FALSE.
The band currently assumes that a null mask means "give me everything", and it requests the optimal size from the toolbar to provide a value different than 0. This makes the CMenuBand at least partially functional.
Analyzing CMenuBand allowed for adding rudimentary support for a custom CMenuBand which shows both a shell folder and a static menu. The callbacks are also used to obtain the icons for the static menu items.
Gigaherz continued by writing a wrapper for CMenuDeskBar, in the same style used for CMenuBand. The partial implementation CMenuDeskBar was ported from shell32 to rshell. Merging the code from CBaseBar into it simplified the implementation. The combined log from the wrappers for CMenuDeskBar and CMenuBand allowed seeing that the SetIconSize from CMenuDeskBar is the one that is supposed to notify the CMenuBand of the icon size choice. To achieve this, it calls the Exec method with cmd=16 and opts=2. It appears that opts=2 means “big icons”, and calling this method with opts=0 keeps the icons small.
The Popup system is supposed to calculate the optimal size of the start menu, and notify the different objects that it are about to be displayed.
Clicking on Menu Items
Clicking on menu items involves sending a callback notification for the currently selected item, asking the callback to execute the appropriate action of the item.
Some of the items are not meant to be clicked on and are supposed to show a sub menu instead. Implementing these required subclassing the toolbar windows to add a timer on hover and handle the opening of the submenu in the resulting WM_TIMER event, which would be received by the toolbar, but needed to be handled by the code.
The OnSelect behavior seems to be something close to this:
- The DeskBar’s SubMenu parent is the IMenuPopup interface of the DeskBar’s “owner” Site, except for the top-level start menu, which does not have a submenu parent.
- The DeskBar’s SubMenu child is the contained Band obtained through the contained MenuSite by using the SID_SMenuPopup service.
- The Band’s SubMenu parent is the DeskBar obtained through the parent MenuSite which is itself obtained through the SID_SMenuBandChild.
- When a user clicks a toolbar item and the item executes an action, the Band sends an OnSelect(Execute) notification to its SubMenu parent.
- When a user presses the alt key, the Band sends an OnSelect(FullCancel) notification to its parent.
- When a different window activates, the DeskBar sends an OnSelect(FullCancel) to itself.
- When the DeskBar receives an OnSelect notification that results in closing of a submenu, it notifies the child using OnSelect(CancelLevel).
- When the DeskBar receives a notification other than CancelLevel, it notifies its parent menu. This is not mutually exclusive with the previous “rule”: Some notifications are forwarded to the child and parent.
- When the Band receives a cancellation event it cancels its current child, if any.
- When the Band receives a keyboard navigation event it either notifies the parent or, in the case of SelectRight, it opens the submenu of the currently selected item.
- SelectLeft is equivalent to CancelLevel except that it is notified to the parent also so it can select the appropriate item.
This implementation requires a focus manager class, which takes care of hooking the application’s message flow to intercept keyboard and mouse events and do the appropriate actions in each case.
Since the focus manager is unable to process the WM_ACTIVATION messages. a handler was added to the DeskBar class, which will send OnSelect notifications when a window gets deactivated in favour of a foreign one. To be able to prevent the menu from closing if the window being shown is a submenu, the GetSite method of the contained Band was repurposed to query the SubMenu child of the band. This is probably not the way Windows does it, but it serves its purpose until a better method is found.
CBandSite and COM Aggregation
The CBandSite class of browseui is the class used by the taskbar as a container for the taskbar toolbars, including the toolbar the hosts the application window icons. The issue was not a lack of implementation, but the lack of support for a COM programming concept known as “Aggregation”, where one COM class can use another COM class’ interfaces as if they were their own. Supporting Aggregation requires some new classes in the ATL library of ReactOS.
Previously, the ReactOS menu code (which was based on Wine’s) misused the MF_POPUP flag as an indicator of the menu having a submenu attached to it, while it’s clear from the MSDN description of the MENUITEMINFO structure that the hSubMenu member already has enough information: this member is NULL if the menu does NOT have a submenu.
The ancient method of adding items to a menu involved using AppendMenu and/or InsertMenu. These two functions use the MF_POPUP flag to indicate that the ID parameter will not be an id, and instead will be repurposed as a submenu handle. In modern versions of windows (Win2k and up), the InsertMenuItem function was introduced, together with some other related functions, which work with a MENUITEMINFO structure. In this structure, the MF_POPUP flag is not used anywhere, and it has separate fields for the id (wID), and the submenu handle (hSubMenu). Because of the wrong assumption that the item ID was reused as the submenu handle, the resource loading code was wrongly setting the submenu handle in the wID field. Fixing those made the functions behave as expected, both in how they store the popup information, and how they build the menus when they are read from an application’s resource data.
Tests confirm that Windows does return the same pointer in SHGetImageList as in Shell_GetImageLists, which is known to return the global, unsafe, image list pointers.
Focus Manager and Capture System
For navigation to work correctly, even with the keyboard, the parent menu has to be aware of when the child menu is opening/closing, not just when it was created.
Improving the behavior of hot-tracking across different level menus required adding a new feature to the focus manager, where it also Captures the input, so that using the keyboard prevents the mouse from resetting the hot item, while still allowing mouse movements from resuming the mouse-based hot-tracking.
The week began by improving the way the shell menus work without activating their windows. This would mean that the title bar of the parent window does not go grey when a shell menu is displayed. But when the logic that calculates when and how to cancel the menu relies on activation events, suddenly that system stops working, since the windows are never activating.
Redesigning the input captures system make it track mouse events and manages what receives those events. The Focus Manager class now truly manages the focus of the menu, arbitrating what receives the input at any given time. But alas, it can never be that simple.
This makes it possible to switch between menus in the horizontal menubar, simply by moving the mouse to a new item. A dual hook system was implemented, where shell menus use a GetMessage hook, and system popups use a MessageFilter hook. Moving the mouse to a new item causes the old item to be cancelled, and the new item to be activated. Or that was the theory. Because of the complex flow of messages and method calls going between the different windows and classes, once a shell menu is open, moving to a system popup behaves wrongly. It seemed that all the failed attempts at fixing tracking bugs were always traced back to the same place: TBN_HOTITEMCHANGE.