New Lexer:
;
; 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"))
}