/* * Process Hacker - * extended menus * * Copyright (C) 2010-2011 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include #include #include static const PH_FLAG_MAPPING EMenuTypeMappings[] = { { PH_EMENU_MENUBARBREAK, MFT_MENUBARBREAK }, { PH_EMENU_MENUBREAK, MFT_MENUBREAK }, { PH_EMENU_RADIOCHECK, MFT_RADIOCHECK } }; static const PH_FLAG_MAPPING EMenuStateMappings[] = { { PH_EMENU_CHECKED, MFS_CHECKED }, { PH_EMENU_DEFAULT, MFS_DEFAULT }, { PH_EMENU_DISABLED, MFS_DISABLED }, { PH_EMENU_HIGHLIGHT, MFS_HILITE } }; PPH_EMENU_ITEM PhAllocateEMenuItem( VOID ) { PPH_EMENU_ITEM item; item = PhAllocate(sizeof(PH_EMENU_ITEM)); memset(item, 0, sizeof(PH_EMENU_ITEM)); return item; } /** * Creates a menu item. * * \param Flags A combination of the following: * \li \c PH_EMENU_DISABLED The menu item is greyed and cannot be selected. * \li \c PH_EMENU_CHECKED A check mark is displayed. * \li \c PH_EMENU_HIGHLIGHT The menu item is highlighted. * \li \c PH_EMENU_MENUBARBREAK Places the menu item in a new column, separated by a vertical line. * \li \c PH_EMENU_MENUBREAK Places the menu item in a new column, with no vertical line. * \li \c PH_EMENU_DEFAULT The menu item is displayed as the default item. This causes the text to * be bolded. * \li \c PH_EMENU_RADIOCHECK Uses a radio-button mark instead of a check mark. * \param Id A unique identifier for the menu item. * \param Text The text displayed for the menu item. * \param Bitmap A bitmap image for the menu item. * \param Context A user-defined value. */ PPH_EMENU_ITEM PhCreateEMenuItem( _In_ ULONG Flags, _In_ ULONG Id, _In_ PWSTR Text, _In_opt_ HBITMAP Bitmap, _In_opt_ PVOID Context ) { PPH_EMENU_ITEM item; item = PhAllocateEMenuItem(); item->Flags = Flags; item->Id = Id; item->Text = Text; item->Bitmap = Bitmap; item->Context = Context; return item; } /** * Frees resources used by a menu item and its children. * * \param Item The menu item. * * \remarks The menu item is NOT automatically removed from its parent. It is safe to call this * function while enumerating menu items. */ VOID PhpDestroyEMenuItem( _In_ PPH_EMENU_ITEM Item ) { if (Item->DeleteFunction) Item->DeleteFunction(Item); if ((Item->Flags & PH_EMENU_TEXT_OWNED) && Item->Text) PhFree(Item->Text); if ((Item->Flags & PH_EMENU_BITMAP_OWNED) && Item->Bitmap) DeleteObject(Item->Bitmap); if (Item->Items) { ULONG i; for (i = 0; i < Item->Items->Count; i++) { PhpDestroyEMenuItem(Item->Items->Items[i]); } PhDereferenceObject(Item->Items); } PhFree(Item); } /** * Frees resources used by a menu item and its children. * * \param Item The menu item. * * \remarks The menu item is automatically removed from its parent. */ VOID PhDestroyEMenuItem( _In_ PPH_EMENU_ITEM Item ) { // Remove the item from its parent, if it has one. if (Item->Parent) PhRemoveEMenuItem(NULL, Item, -1); PhpDestroyEMenuItem(Item); } /** * Finds a child menu item. * * \param Item The parent menu item. * \param Flags A combination of the following: * \li \c PH_EMENU_FIND_DESCEND Searches recursively within child menu items. * \li \c PH_EMENU_FIND_STARTSWITH Performs a partial text search instead of an exact search. * \li \c PH_EMENU_FIND_LITERAL Performs a literal search instead of ignoring prefix characters * (ampersands). * \param Text The text of the menu item to find. If NULL, the text is ignored. * \param Id The identifier of the menu item to find. If 0, the identifier is ignored. * * \return The found menu item, or NULL if the menu item could not be found. */ PPH_EMENU_ITEM PhFindEMenuItem( _In_ PPH_EMENU_ITEM Item, _In_ ULONG Flags, _In_opt_ PWSTR Text, _In_opt_ ULONG Id ) { return PhFindEMenuItemEx(Item, Flags, Text, Id, NULL, NULL); } /** * Finds a child menu item. * * \param Item The parent menu item. * \param Flags A combination of the following: * \li \c PH_EMENU_FIND_DESCEND Searches recursively within child menu items. * \li \c PH_EMENU_FIND_STARTSWITH Performs a partial text search instead of an exact search. * \li \c PH_EMENU_FIND_LITERAL Performs a literal search instead of ignoring prefix characters * (ampersands). * \param Text The text of the menu item to find. If NULL, the text is ignored. * \param Id The identifier of the menu item to find. If 0, the identifier is ignored. * \param FoundParent A variable which receives the parent of the found menu item. * \param FoundIndex A variable which receives the index of the found menu item. * * \return The found menu item, or NULL if the menu item could not be found. */ PPH_EMENU_ITEM PhFindEMenuItemEx( _In_ PPH_EMENU_ITEM Item, _In_ ULONG Flags, _In_opt_ PWSTR Text, _In_opt_ ULONG Id, _Out_opt_ PPH_EMENU_ITEM *FoundParent, _Out_opt_ PULONG FoundIndex ) { PH_STRINGREF searchText; ULONG i; PPH_EMENU_ITEM item; if (!Item->Items) return NULL; if (Text && (Flags & PH_EMENU_FIND_LITERAL)) PhInitializeStringRef(&searchText, Text); for (i = 0; i < Item->Items->Count; i++) { item = Item->Items->Items[i]; if (Text) { if (Flags & PH_EMENU_FIND_LITERAL) { PH_STRINGREF text; PhInitializeStringRef(&text, item->Text); if (Flags & PH_EMENU_FIND_STARTSWITH) { if (PhStartsWithStringRef(&text, &searchText, TRUE)) goto FoundItemHere; } else { if (PhEqualStringRef(&text, &searchText, TRUE)) goto FoundItemHere; } } else { if (PhCompareUnicodeStringZIgnoreMenuPrefix(Text, item->Text, TRUE, !!(Flags & PH_EMENU_FIND_STARTSWITH)) == 0) goto FoundItemHere; } } if (Id && item->Id == Id) goto FoundItemHere; if (Flags & PH_EMENU_FIND_DESCEND) { PPH_EMENU_ITEM foundItem; PPH_EMENU_ITEM foundParent; ULONG foundIndex; foundItem = PhFindEMenuItemEx(item, Flags, Text, Id, &foundParent, &foundIndex); if (foundItem) { if (FoundParent) *FoundParent = foundParent; if (FoundIndex) *FoundIndex = foundIndex; return foundItem; } } } return NULL; FoundItemHere: if (FoundParent) *FoundParent = Item; if (FoundIndex) *FoundIndex = i; return item; } /** * Determines the index of a menu item. * * \param Parent The parent menu item. * \param Item The child menu item. * * \return The index of the menu item, or -1 if the menu item was not found in the parent menu item. */ ULONG PhIndexOfEMenuItem( _In_ PPH_EMENU_ITEM Parent, _In_ PPH_EMENU_ITEM Item ) { if (!Parent->Items) return -1; return PhFindItemList(Parent->Items, Item); } /** * Inserts a menu item into a parent menu item. * * \param Parent The parent menu item. * \param Item The menu item to insert. * \param Index The index at which to insert the menu item. If the index is too large, the menu item * is inserted at the last position. */ VOID PhInsertEMenuItem( _Inout_ PPH_EMENU_ITEM Parent, _Inout_ PPH_EMENU_ITEM Item, _In_ ULONG Index ) { // Remove the item from its old parent if it has one. if (Item->Parent) PhRemoveEMenuItem(Item->Parent, Item, 0); if (!Parent->Items) Parent->Items = PhCreateList(16); if (Index > Parent->Items->Count) Index = Parent->Items->Count; if (Index == -1) PhAddItemList(Parent->Items, Item); else PhInsertItemList(Parent->Items, Index, Item); Item->Parent = Parent; } /** * Removes a menu item from its parent. * * \param Parent The parent menu item. If \a Item is NULL, this parameter must be specified. * \param Item The child menu item. This may be NULL if \a Index is specified. * \param Index The index of the menu item to remove. If \a Item is specified, this parameter is * ignored. */ BOOLEAN PhRemoveEMenuItem( _Inout_opt_ PPH_EMENU_ITEM Parent, _In_opt_ PPH_EMENU_ITEM Item, _In_opt_ ULONG Index ) { if (Item) { if (!Parent) Parent = Item->Parent; if (!Parent->Items) return FALSE; Index = PhFindItemList(Parent->Items, Item); if (Index == -1) return FALSE; } else { if (!Parent) return FALSE; if (!Parent->Items) return FALSE; } Item = Parent->Items->Items[Index]; PhRemoveItemList(Parent->Items, Index); Item->Parent = NULL; return TRUE; } /** * Removes all children from a menu item. * * \param Parent The parent menu item. */ VOID PhRemoveAllEMenuItems( _Inout_ PPH_EMENU_ITEM Parent ) { ULONG i; if (!Parent->Items) return; for (i = 0; i < Parent->Items->Count; i++) { PhpDestroyEMenuItem(Parent->Items->Items[i]); } PhClearList(Parent->Items); } /** * Creates a root menu. */ PPH_EMENU PhCreateEMenu( VOID ) { PPH_EMENU menu; menu = PhAllocate(sizeof(PH_EMENU)); memset(menu, 0, sizeof(PH_EMENU)); menu->Items = PhCreateList(16); return menu; } /** * Frees resources used by a root menu and its children. * * \param Menu A root menu. */ VOID PhDestroyEMenu( _In_ PPH_EMENU Menu ) { ULONG i; for (i = 0; i < Menu->Items->Count; i++) { PhpDestroyEMenuItem(Menu->Items->Items[i]); } PhDereferenceObject(Menu->Items); PhFree(Menu); } /** * Initializes a data structure containing additional information resulting from a call to * PhEMenuToHMenu(). */ VOID PhInitializeEMenuData( _Out_ PPH_EMENU_DATA Data ) { Data->IdToItem = PhCreateList(16); } /** * Frees resources used by a data structure initialized by PhInitializeEMenuData(). */ VOID PhDeleteEMenuData( _Inout_ PPH_EMENU_DATA Data ) { PhDereferenceObject(Data->IdToItem); } /** * Converts an EMENU to a Windows menu object. * * \param Menu The menu item to convert. * \param Flags A combination of the following: * \li \c PH_EMENU_CONVERT_ID Automatically assigns a unique identifier to each converted menu item. * The resulting mappings are placed in \a Data. * \param Data Additional data resulting from the conversion. The data structure must be initialized * by PhInitializeEMenuData() prior to calling this function. * * \return A menu handle. The menu object must be destroyed using DestroyMenu() when it is no longer * needed. */ HMENU PhEMenuToHMenu( _In_ PPH_EMENU_ITEM Menu, _In_ ULONG Flags, _Inout_opt_ PPH_EMENU_DATA Data ) { HMENU menuHandle; menuHandle = CreatePopupMenu(); if (!menuHandle) return NULL; PhEMenuToHMenu2(menuHandle, Menu, Flags, Data); if (!(Menu->Flags & PH_EMENU_SEPARATECHECKSPACE)) { MENUINFO menuInfo; memset(&menuInfo, 0, sizeof(MENUINFO)); menuInfo.cbSize = sizeof(MENUINFO); menuInfo.fMask = MIM_STYLE; menuInfo.dwStyle = MNS_CHECKORBMP; SetMenuInfo(menuHandle, &menuInfo); } return menuHandle; } /** * Converts an EMENU to a Windows menu object. * * \param MenuHandle A handle to a Windows menu object. * \param Menu The menu item to convert. The items are appended to \a MenuHandle. * \param Flags A combination of the following: * \li \c PH_EMENU_CONVERT_ID Automatically assigns a unique identifier to each converted menu item. * The resulting mappings are placed in \a Data. * \param Data Additional data resulting from the conversion. The data structure must be initialized * by PhInitializeEMenuData() prior to calling this function. */ VOID PhEMenuToHMenu2( _In_ HMENU MenuHandle, _In_ PPH_EMENU_ITEM Menu, _In_ ULONG Flags, _Inout_opt_ PPH_EMENU_DATA Data ) { ULONG i; PPH_EMENU_ITEM item; MENUITEMINFO menuItemInfo; for (i = 0; i < Menu->Items->Count; i++) { item = Menu->Items->Items[i]; memset(&menuItemInfo, 0, sizeof(MENUITEMINFO)); menuItemInfo.cbSize = sizeof(MENUITEMINFO); // Type menuItemInfo.fMask = MIIM_FTYPE | MIIM_STATE; if (item->Flags & PH_EMENU_SEPARATOR) { menuItemInfo.fType = MFT_SEPARATOR; } else { menuItemInfo.fType = MFT_STRING; menuItemInfo.fMask |= MIIM_STRING; menuItemInfo.dwTypeData = item->Text; } PhMapFlags1( &menuItemInfo.fType, item->Flags, EMenuTypeMappings, sizeof(EMenuTypeMappings) / sizeof(PH_FLAG_MAPPING) ); // Bitmap if (item->Bitmap) { menuItemInfo.fMask |= MIIM_BITMAP; menuItemInfo.hbmpItem = item->Bitmap; } // Id if (Flags & PH_EMENU_CONVERT_ID) { ULONG id; if (Data) { PhAddItemList(Data->IdToItem, item); id = Data->IdToItem->Count; menuItemInfo.fMask |= MIIM_ID; menuItemInfo.wID = id; } } else { if (item->Id) { menuItemInfo.fMask |= MIIM_ID; menuItemInfo.wID = item->Id; } } // State PhMapFlags1( &menuItemInfo.fState, item->Flags, EMenuStateMappings, sizeof(EMenuStateMappings) / sizeof(PH_FLAG_MAPPING) ); // Context menuItemInfo.fMask |= MIIM_DATA; menuItemInfo.dwItemData = (ULONG_PTR)item; // Submenu if (item->Items && item->Items->Count != 0) { menuItemInfo.fMask |= MIIM_SUBMENU; menuItemInfo.hSubMenu = PhEMenuToHMenu(item, Flags, Data); } InsertMenuItem(MenuHandle, MAXINT, TRUE, &menuItemInfo); } } /** * Converts a Windows menu object to an EMENU. * * \param MenuItem The menu item in which the converted menu items will be placed. * \param MenuHandle A menu handle. */ VOID PhHMenuToEMenuItem( _Inout_ PPH_EMENU_ITEM MenuItem, _In_ HMENU MenuHandle ) { ULONG i; ULONG count; count = GetMenuItemCount(MenuHandle); if (count != -1) { for (i = 0; i < count; i++) { MENUITEMINFO menuItemInfo; WCHAR buffer[256]; PPH_EMENU_ITEM menuItem; menuItemInfo.cbSize = sizeof(menuItemInfo); menuItemInfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING | MIIM_SUBMENU; menuItemInfo.cch = sizeof(buffer) / sizeof(WCHAR); menuItemInfo.dwTypeData = buffer; if (!GetMenuItemInfo(MenuHandle, i, TRUE, &menuItemInfo)) continue; menuItem = PhCreateEMenuItem( PH_EMENU_TEXT_OWNED, menuItemInfo.wID, PhDuplicateStringZ(buffer), NULL, NULL ); if (menuItemInfo.fType & MFT_SEPARATOR) menuItem->Flags |= PH_EMENU_SEPARATOR; PhMapFlags2( &menuItem->Flags, menuItemInfo.fType, EMenuTypeMappings, sizeof(EMenuTypeMappings) / sizeof(PH_FLAG_MAPPING) ); PhMapFlags2( &menuItem->Flags, menuItemInfo.fState, EMenuStateMappings, sizeof(EMenuStateMappings) / sizeof(PH_FLAG_MAPPING) ); if (menuItemInfo.hSubMenu) PhHMenuToEMenuItem(menuItem, menuItemInfo.hSubMenu); PhInsertEMenuItem(MenuItem, menuItem, -1); } } } /** * Loads a menu resource and converts it to an EMENU. * * \param MenuItem The menu item in which the converted menu items will be placed. * \param InstanceHandle The module containing the menu resource. * \param Resource The resource identifier. * \param SubMenuIndex The index of the sub menu to use, or -1 to use the root menu. */ VOID PhLoadResourceEMenuItem( _Inout_ PPH_EMENU_ITEM MenuItem, _In_ HINSTANCE InstanceHandle, _In_ PWSTR Resource, _In_ ULONG SubMenuIndex ) { HMENU menu; HMENU realMenu; menu = LoadMenu(InstanceHandle, Resource); if (SubMenuIndex != -1) realMenu = GetSubMenu(menu, SubMenuIndex); else realMenu = menu; PhHMenuToEMenuItem(MenuItem, realMenu); DestroyMenu(menu); } /** * Displays a menu. * * \param Menu A menu. * \param WindowHandle The window that owns the popup menu. * \param Flags A combination of the following: * \li \c PH_EMENU_SHOW_SEND_COMMAND A WM_COMMAND message is sent to the window when the user clicks * on a menu item. * \li \c PH_EMENU_SHOW_LEFTRIGHT The user can select menu items with both the left and right mouse * buttons. * \param Align The alignment of the menu. * \param X The horizontal location of the menu. * \param Y The vertical location of the menu. * * \return The selected menu item, or NULL if the menu was cancelled. */ PPH_EMENU_ITEM PhShowEMenu( _In_ PPH_EMENU Menu, _In_ HWND WindowHandle, _In_ ULONG Flags, _In_ ULONG Align, _In_ ULONG X, _In_ ULONG Y ) { PPH_EMENU_ITEM selectedItem; ULONG result; ULONG flags; PH_EMENU_DATA data; HMENU popupMenu; selectedItem = NULL; flags = TPM_RETURNCMD | TPM_NONOTIFY; // Flags if (Flags & PH_EMENU_SHOW_LEFTRIGHT) flags |= TPM_RIGHTBUTTON; else flags |= TPM_LEFTBUTTON; // Align if (Align & PH_ALIGN_LEFT) flags |= TPM_LEFTALIGN; else if (Align & PH_ALIGN_RIGHT) flags |= TPM_RIGHTALIGN; else flags |= TPM_CENTERALIGN; if (Align & PH_ALIGN_TOP) flags |= TPM_TOPALIGN; else if (Align & PH_ALIGN_BOTTOM) flags |= TPM_BOTTOMALIGN; else flags |= TPM_VCENTERALIGN; PhInitializeEMenuData(&data); if (popupMenu = PhEMenuToHMenu(Menu, PH_EMENU_CONVERT_ID, &data)) { result = TrackPopupMenu( popupMenu, flags, X, Y, 0, WindowHandle, NULL ); if (result != 0) { selectedItem = data.IdToItem->Items[result - 1]; } DestroyMenu(popupMenu); } PhDeleteEMenuData(&data); if ((Flags & PH_EMENU_SHOW_SEND_COMMAND) && selectedItem && selectedItem->Id != 0) SendMessage(WindowHandle, WM_COMMAND, MAKEWPARAM(selectedItem->Id, 0), 0); return selectedItem; } /** * Sets the flags of a menu item. * * \param Item The parent menu item. * \param Id The identifier of the child menu item. * \param Mask The flags to modify. * \param Value The new value of the flags. */ BOOLEAN PhSetFlagsEMenuItem( _Inout_ PPH_EMENU_ITEM Item, _In_ ULONG Id, _In_ ULONG Mask, _In_ ULONG Value ) { PPH_EMENU_ITEM item; item = PhFindEMenuItem(Item, PH_EMENU_FIND_DESCEND, NULL, Id); if (item) { item->Flags &= ~Mask; item->Flags |= Value; return TRUE; } else { return FALSE; } } /** * Sets flags for all children of a menu item. * * \param Item The parent menu item. * \param Mask The flags to modify. * \param Value The new value of the flags. */ VOID PhSetFlagsAllEMenuItems( _In_ PPH_EMENU_ITEM Item, _In_ ULONG Mask, _In_ ULONG Value ) { ULONG i; for (i = 0; i < Item->Items->Count; i++) { PPH_EMENU_ITEM item = Item->Items->Items[i]; item->Flags &= ~Mask; item->Flags |= Value; } } VOID PhModifyEMenuItem( _Inout_ PPH_EMENU_ITEM Item, _In_ ULONG ModifyFlags, _In_ ULONG OwnedFlags, _In_opt_ PWSTR Text, _In_opt_ HBITMAP Bitmap ) { if (ModifyFlags & PH_EMENU_MODIFY_TEXT) { if ((Item->Flags & PH_EMENU_TEXT_OWNED) && Item->Text) PhFree(Item->Text); Item->Text = Text; Item->Flags &= ~PH_EMENU_TEXT_OWNED; Item->Flags |= OwnedFlags & PH_EMENU_TEXT_OWNED; } if (ModifyFlags & PH_EMENU_MODIFY_BITMAP) { if ((Item->Flags & PH_EMENU_BITMAP_OWNED) && Item->Bitmap) DeleteObject(Item->Bitmap); Item->Bitmap = Bitmap; Item->Flags &= ~PH_EMENU_BITMAP_OWNED; Item->Flags |= OwnedFlags & PH_EMENU_BITMAP_OWNED; } }