; ; Menu Icons v2.21 ; by Lexikos ; ; Associates an icon with a menu item. ; NOTE: On versions of Windows other than Vista, the menu MUST be shown with ; MI_ShowMenu() for the icons to appear. ; ; MenuNameOrHandle ; The name or handle of a menu. When setting icons for multiple items, ; it is more efficient to use a handle returned by MI_GetMenuHandle("menuname"). ; ItemPos ; The position of the menu item, where 1 is the first item. ; FilenameOrHICON ; The filename or handle of an icon. ; SUPPORTS EXECUTABLE FILES ONLY (EXE/DLL/ICL/CPL/etc.) ; IconNumber ; The icon group to use (if omitted, it defaults to 1.) ; This is not used if FilenameOrHICON specifies an icon handle. ; IconSize ; The desired width and height of the icon. If omitted, the system's small icon size is used. ; h_bitmap ; h_icon ; v2.2: These parameters are no longer used as MI now automatically deletes the ; icon/bitmap if a new icon/bitmap is being set. ; OBSOLETE: ; These are set to the bitmap or icon resources which are used. ; Bitmaps and icons can be deleted as follows: ; DllCall("DeleteObject", "uint", h_bitmap) ; DllCall("DestroyIcon", "uint", h_icon) ; This is only necessary if the menu item displaying these resources ; is manually removed. ; Usually only one of h_icon or h_bitmap will be used, and the other will be 0 (NULL). ; ; OPERATING SYSTEM NOTES: ; ; Windows 2000 and above: ; PrivateExtractIcons() is used to extract the icon. ; ; Older versions of Windows: ; PrivateExtractIcons() is not available, so ExtractIconEx() is used. ; As a result, a 16x16 or 32x32 icon will be loaded. If a size is specified, ; the icon may be stretched to fit. If no size is specified, 16x16 is used. ; MI_SetMenuItemIcon(MenuNameOrHandle, ItemPos, FilenameOrHICON, IconNumber=1, IconSize=0, ByRef unused1="", ByRef unused2="") { ; Set for compatibility with older scripts: unused1=0 unused2=0 if MenuNameOrHandle is integer h_menu := MenuNameOrHandle else h_menu := MI_GetMenuHandle(MenuNameOrHandle) if !h_menu return false if FilenameOrHICON is integer { ; May be 0 to remove icon. h_icon := FilenameOrHICON ; Copy and potentially resize the icon. Since the caller is probably "caching" ; icon handles or assigning them to multiple items, we don't want to delete ; it if/when a future call to this function re-sets this item's icon. if h_icon h_icon := DllCall("CopyImage","uint",h_icon,"uint",1 ,"int",IconSize,"int",IconSize,"uint",0) ; else caller wants to remove and delete existing icon. } else { ; Load icon from file. Remember to clean up this icon if we end up using a bitmap. ; Resizing is not necessary in this case since MI_ExtractIcon already does that. if !(loaded_icon := h_icon := MI_ExtractIcon(FilenameOrHICON, IconNumber, IconSize)) return false } ; Windows Vista supports 32-bit alpha-blended bitmaps in menus. Note that ; A_OSVersion does not report WIN_VISTA when running in compatibility mode. ; To get nice icons on other versions of Windows, we need to owner-draw. ; DON'T TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING: ; use_bitmap MUST have the same value for each use on a given menu item. if A_OSVersion in WIN_VISTA,WIN_7 use_bitmap := true ; Get the previous bitmap or icon handle. VarSetCapacity(mii,48,0), NumPut(48,mii), NumPut(0xA0,mii,4) if DllCall("GetMenuItemInfo","uint",h_menu,"uint",ItemPos-1,"uint",1,"uint",&mii) h_previous := use_bitmap ? NumGet(mii,44,"int") : NumGet(mii,32,"int") if use_bitmap { if h_icon { h_bitmap := MI_GetBitmapFromIcon32Bit(h_icon, IconSize, IconSize) if loaded_icon { ; The icon we loaded is no longer needed. DllCall("DestroyIcon","uint",loaded_icon) ; Don't try to destroy the now invalid handle again: loaded_icon := 0 } if !h_bitmap return false } else ; Caller wants to remove and delete existing icon. h_bitmap := 0 NumPut(0x80,mii,4) ; fMask: Set hbmpItem only, not dwItemData. , NumPut(h_bitmap,mii,44) ; hbmpItem = h_bitmap } else { ; Associate the icon with the menu item. Relies on the probable case that no other ; script or dll will use dwItemData. If other scripts need to associate data with ; an item, MI should be expanded to allow it. NumPut(h_icon,mii,32) ; dwItemData = h_icon , NumPut(-1,mii,44) ; hbmpItem = HBMMENU_CALLBACK } if DllCall("SetMenuItemInfo","uint",h_menu,"uint",ItemPos-1,"uint",1,"uint",&mii) { ; Only now that we know it's a success, delete the previous icon or bitmap. if use_bitmap { ; Exclude NULL and predefined HBMMENU_ values (-1, 1..11). if (h_previous < -1 || h_previous > 11) DllCall("DeleteObject","uint",h_previous) } else DllCall("DestroyIcon","uint",h_previous) return true } ; ELSE FAIL if loaded_icon DllCall("DestroyIcon","uint",loaded_icon) return false } ; v2.2: This should be used to remove and delete all icons in a menu before deleting the menu. MI_RemoveIcons(MenuNameOrHandle) { if MenuNameOrHandle is integer h_menu := MenuNameOrHandle else h_menu := MI_GetMenuHandle(MenuNameOrHandle) if !h_menu return Loop % DllCall("GetMenuItemCount","uint",h_menu) MI_SetMenuItemIcon(h_menu, A_Index, 0) } ; Set a menu item's associated bitmap. ; hBitmap can be a handle to a bitmap, or a HBMMENU value (see below.) MI_SetMenuItemBitmap(MenuNameOrHandle, ItemPos, hBitmap) { if MenuNameOrHandle is integer h_menu := MenuNameOrHandle else h_menu := MI_GetMenuHandle(MenuNameOrHandle) if !h_menu return false VarSetCapacity(mii,48,0), NumPut(48,mii), NumPut(0x80,mii,4), NumPut(hBitmap,mii,44) return DllCall("SetMenuItemInfo","uint",h_menu,"uint",ItemPos-1,"uint",1,"uint",&mii) } /* HBMMENU_SYSTEM = 1 HBMMENU_MBAR_RESTORE = 2 HBMMENU_MBAR_MINIMIZE = 3 HBMMENU_MBAR_CLOSE = 5 HBMMENU_MBAR_CLOSE_D = 6 HBMMENU_MBAR_MINIMIZE_D = 7 HBMMENU_POPUP_CLOSE = 8 HBMMENU_POPUP_RESTORE = 9 HBMMENU_POPUP_MAXIMIZE = 10 HBMMENU_POPUP_MINIMIZE = 11 */ ; ; General Functions ; ; Gets a menu handle from a menu name. ; Adapted from Shimanov's Menu_AssignBitmap() ; http://www.autohotkey.com/forum/topic7526.html MI_GetMenuHandle(menu_name) { static h_menuDummy ; v2.2: Check for !h_menuDummy instead of h_menuDummy="" in case init failed last time. If !h_menuDummy { Menu, menuDummy, Add Menu, menuDummy, DeleteAll Gui, 99:Menu, menuDummy ; v2.2: Use LastFound method instead of window title. [Thanks animeaime.] Gui, 99:+LastFound h_menuDummy := DllCall("GetMenu", "uint", WinExist()) Gui, 99:Menu Gui, 99:Destroy ; v2.2: Return only after cleaning up. [Thanks animeaime.] if !h_menuDummy return 0 } Menu, menuDummy, Add, :%menu_name% h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0 ) DllCall( "RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400 ) Menu, menuDummy, Delete, :%menu_name% return h_menu } ; Valid (and safe to use) styles: ; MNS_AUTODISMISS 0x10000000 ; MNS_CHECKORBMP 0x04000000 The same space is reserved for the check mark and the bitmap. ; MNS_NOCHECK 0x80000000 No space is reserved to the left of an item for a check mark. MI_SetMenuStyle(MenuNameOrHandle, style) { if MenuNameOrHandle is integer h_menu := MenuNameOrHandle else h_menu := MI_GetMenuHandle(MenuNameOrHandle) if !h_menu return VarSetCapacity(mi,28,0), NumPut(28,mi) NumPut(0x10,mi,4) ; fMask=MIM_STYLE NumPut(style,mi,8) DllCall("SetMenuInfo","uint",h_menu,"uint",&mi) } ; Extract an icon from an executable, DLL or icon file. MI_ExtractIcon(Filename, IconNumber, IconSize) { static ExtractIconEx ; LoadImage is not used.. ; ..with exe/dll files because: ; it only works with modules loaded by the current process, ; it needs the resource ordinal (which is not the same as an icon index), and ; ..with ico files because: ; it can only load the first icon (of size %IconSize%) from an .ico file. ; If possible, use PrivateExtractIcons, which supports any size of icon. if (IconSize != 16 && IconSize != 32) { if A_OSVersion in WIN_7,WIN_XP,WIN_VISTA,WIN_2003,WIN_2000 { if DllCall("PrivateExtractIcons" ,"str",Filename,"int",IconNumber-1,"int",IconSize,"int",IconSize ,"uint*",h_icon,"uint*",0,"uint",1,"uint",0,"int") { return h_icon } } } if !ExtractIconEx ExtractIconEx := MI_DllProcAorW("shell32","ExtractIconEx") ; Use ExtractIconEx, which only returns 16x16 or 32x32 icons. if DllCall(ExtractIconEx,"str",Filename,"int",IconNumber-1 ,"uint*",h_icon,"uint*",h_icon_small,"uint",1) { SysGet, SmallIconSize, 49 ; Use the best-fit size; delete the other. Defaults to small icon. if (IconSize <= SmallIconSize) { DllCall("DestroyIcon","uint",h_icon) h_icon := h_icon_small } else DllCall("DestroyIcon","uint",h_icon_small) ; I think PrivateExtractIcons resizes icons automatically, ; so resize icons returned by ExtractIconEx for consistency. if (h_icon && IconSize) h_icon := DllCall("CopyImage","uint",h_icon,"uint",1,"int",IconSize ,"int",IconSize,"uint",4|8) } return h_icon ? h_icon : 0 } ; ; Owner-Drawn Menu Functions ; ; Sub-classes a window from THIS process to owner-draw menu icons. ; This allows the menu to be shown by means other than MI_ShowMenu(). MI_EnableOwnerDrawnMenus(hwnd="") { if (hwnd="") { ; Use the script's main window if hwnd was omitted. dhw := A_DetectHiddenWindows DetectHiddenWindows, On Process, Exist hwnd := WinExist("ahk_class AutoHotkey ahk_pid " ErrorLevel) DetectHiddenWindows, %dhw% } if !hwnd return wndProc := RegisterCallback("MI_OwnerDrawnMenuItemWndProc","",4 ,DllCall("GetWindowLong","uint",hwnd,"uint",-4)) return DllCall("SetWindowLong","uint",hwnd,"int",-4,"int",wndProc,"uint") } ; Shows a menu, allowing owner-drawn icons to be drawn. MI_ShowMenu(MenuNameOrHandle, x="", y="") { static hInstance, hwnd, ClassName := "OwnerDrawnMenuMsgWin" , CreateWindowEx if MenuNameOrHandle is integer h_menu := MenuNameOrHandle else h_menu := MI_GetMenuHandle(MenuNameOrHandle) if !h_menu return false if !hwnd { ; Create a message window to receive owner-draw messages from the menu. ; Only one window is created per instance of the script. if !hInstance hInstance := DllCall("GetModuleHandle", "UInt", 0) ; Register a window class to associate OwnerDrawnMenuItemWndProc() ; with the window we will create. wndProc := RegisterCallback("MI_OwnerDrawnMenuItemWndProc","",4,0) if !wndProc { ErrorLevel = RegisterCallback return false } ; Create a new window class. VarSetCapacity(wc, 40, 0) ; WNDCLASS wc NumPut(wndProc, wc, 4) ; lpfnWndProc NumPut(hInstance, wc,16) ; hInstance NumPut(&ClassName,wc,36) ; lpszClassname ; Register the class. if !DllCall("RegisterClass","uint",&wc) { ; failed, free the callback. DllCall("GlobalFree","uint",wndProc) ErrorLevel = RegisterClass return false } ; ; Create the message window. ; if A_OSVersion in WIN_XP,WIN_7,WIN_VISTA,WIN_2003 hwndParent = -3 ; HWND_MESSAGE (message-only window) else hwndParent = 0 ; un-owned hwnd := DllCall("CreateWindowEx","uint",0,"str",ClassName,"str",ClassName ,"uint",0,"int",0,"int",0,"int",0,"int",0,"uint",hwndParent ,"uint",0,"uint",hInstance,"uint",0) if !hwnd { ErrorLevel = CreateWindowEx return false } } prev_hwnd := DllCall("GetForegroundWindow") ; Required for the menu to initially have focus. ;DllCall("SetForegroundWindow","uint",hwnd) dhw := A_DetectHiddenWindows DetectHiddenWindows, On WinActivate, ahk_id %hwnd% DetectHiddenWindows, %dhw% if (x="" or y="") { CoordMode, Mouse, Screen MouseGetPos, x, y } ; returns non-zero on success. ret := DllCall("TrackPopupMenu","uint",h_menu,"uint",0,"int",x,"int",y ,"int",0,"uint",hwnd,"uint",0) if WinExist("ahk_id " prev_hwnd) DllCall("SetForegroundWindow","uint",prev_hwnd) ; Required to let AutoHotkey process WM_COMMAND messages we may have ; sent as a result of clicking a menu item. (Without this, the item-click ; won't register if there is an 'ExitApp' after ShowOwnerDrawnMenu returns.) Sleep, 1 return ret } MI_OwnerDrawnMenuItemWndProc(hwnd, Msg, wParam, lParam) { static WM_DRAWITEM = 0x002B, WM_MEASUREITEM = 0x002C, WM_COMMAND = 0x111 static ScriptHwnd Critical 500 if (Msg = WM_MEASUREITEM && wParam = 0) { ; MSDN: wParam - If the value is zero, the message was sent by a menu. h_icon := NumGet(lParam+20) if !h_icon return false ; Measure icon and put results into lParam. VarSetCapacity(buf,24) if DllCall("GetIconInfo","uint",h_icon,"uint",&buf) { hbmColor := NumGet(buf,16) hbmMask := NumGet(buf,12) x := DllCall("GetObject","uint",hbmColor,"int",24,"uint",&buf) DllCall("DeleteObject","uint",hbmColor) DllCall("DeleteObject","uint",hbmMask) if !x return false NumPut(NumGet(buf,4,"int")+2, lParam+12) ; width NumPut(NumGet(buf,8,"int") , lParam+16) ; height return true } return false } else if (Msg = WM_DRAWITEM && wParam = 0) { hdcDest := NumGet(lParam+24) x := NumGet(lParam+28) y := NumGet(lParam+32) h_icon := NumGet(lParam+44) if !(h_icon && hdcDest) return false return DllCall("DrawIconEx","uint",hdcDest,"int",x,"int",y,"uint",h_icon ,"uint",0,"uint",0,"uint",0,"uint",0,"uint",3) } else if (Msg = WM_COMMAND && !(wParam>>16)) ; (clicked a menu item) { DetectHiddenWindows, On WinGetClass, class, ahk_id %hwnd% if (class != "AutoHotkeyGUI" && class != "AutoHotkey") { if !ScriptHwnd { Process, Exist ScriptHwnd := WinExist("ahk_class AutoHotkey ahk_pid " ErrorLevel) } ; Forward this message to the AutoHotkey main window. PostMessage, Msg, wParam, lParam,, ahk_id %ScriptHwnd% return ErrorLevel } } if A_EventInfo ; Let the "super-class" window procedure handle all other messages. return DllCall("CallWindowProc","uint",A_EventInfo,"uint",hwnd,"uint",Msg,"uint",wParam,"uint",lParam) else ; Let the default window procedure handle all other messages. return DllCall("DefWindowProc","uint",hwnd,"uint",Msg,"uint",wParam,"uint",lParam) } ; ; Windows Vista Menu Icons ; ; Note: 32-bit alpha-blended menu item bitmaps are supported only on Windows Vista. ; Article on menu icons in Vista: ; http://shellrevealed.com/blogs/shellblog/archive/2007/02/06/Vista-Style-Menus_2C00_-Part-1-_2D00_-Adding-icons-to-standard-menus.aspx MI_GetBitmapFromIcon32Bit(h_icon, width=0, height=0) { VarSetCapacity(buf,40) ; used as ICONINFO (20), BITMAP (24), BITMAPINFO (40) if DllCall("GetIconInfo","uint",h_icon,"uint",&buf) { hbmColor := NumGet(buf,16) ; used to measure the icon hbmMask := NumGet(buf,12) ; used to generate alpha data (if necessary) } if !(width && height) { if !hbmColor or !DllCall("GetObject","uint",hbmColor,"int",24,"uint",&buf) return 0 width := NumGet(buf,4,"int"), height := NumGet(buf,8,"int") } ; Create a device context compatible with the screen. if (hdcDest := DllCall("CreateCompatibleDC","uint",0)) { ; Create a 32-bit bitmap to draw the icon onto. VarSetCapacity(buf,40,0), NumPut(40,buf), NumPut(1,buf,12,"ushort") NumPut(width,buf,4), NumPut(height,buf,8), NumPut(32,buf,14,"ushort") if (bm := DllCall("CreateDIBSection","uint",hdcDest,"uint",&buf,"uint",0 ,"uint*",pBits,"uint",0,"uint",0)) { ; SelectObject -- use hdcDest to draw onto bm if (bmOld := DllCall("SelectObject","uint",hdcDest,"uint",bm)) { ; Draw the icon onto the 32-bit bitmap. DllCall("DrawIconEx","uint",hdcDest,"int",0,"int",0,"uint",h_icon ,"uint",width,"uint",height,"uint",0,"uint",0,"uint",3) DllCall("SelectObject","uint",hdcDest,"uint",bmOld) } ; Check for alpha data. has_alpha_data := false Loop, % height*width if NumGet(pBits+0,(A_Index-1)*4) & 0xFF000000 { has_alpha_data := true break } if !has_alpha_data { ; Ensure the mask is the right size. hbmMask := DllCall("CopyImage","uint",hbmMask,"uint",0 ,"int",width,"int",height,"uint",4|8) VarSetCapacity(mask_bits, width*height*4, 0) if DllCall("GetDIBits","uint",hdcDest,"uint",hbmMask,"uint",0 ,"uint",height,"uint",&mask_bits,"uint",&buf,"uint",0) { ; Use icon mask to generate alpha data. Loop, % height*width if (NumGet(mask_bits, (A_Index-1)*4)) NumPut(0, pBits+(A_Index-1)*4) else NumPut(NumGet(pBits+(A_Index-1)*4) | 0xFF000000, pBits+(A_Index-1)*4) } else { ; Make the bitmap entirely opaque. Loop, % height*width NumPut(NumGet(pBits+(A_Index-1)*4) | 0xFF000000, pBits+(A_Index-1)*4) } } } ; Done using the device context. DllCall("DeleteDC","uint",hdcDest) } if hbmColor DllCall("DeleteObject","uint",hbmColor) if hbmMask DllCall("DeleteObject","uint",hbmMask) return bm } ; ; Utils ; MI_DllProcAorW(dll, func) { ; In AutoHotkey_L/AutoHotkeyU we can just use "dll\func" and "A" or "W" ; will be appended as appropriate if func is not found, but since that ; won't work in regular AutoHotkey (for some dlls), resolve it manually: return DllCall("GetProcAddress" ,"uint", DllCall("GetModuleHandle","str",dll) , A_IsUnicode ? "astr":"str" ; Always an ANSI string. , func . (A_IsUnicode ? "W":"A")) }