 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Micha
Joined: 15 Nov 2005 Posts: 433 Location: Germany
|
Posted: Wed Jul 25, 2007 8:35 am Post subject: Get info from context menu |
|
|
Hi, there are solutions how to get information from a standard menu (File, edit, help) but I haven't found a solution for context menus (i.e. right click in explorer)
Here's a script that traces the names of every entry of a windows standard popup menu and it traces the state (selected, enabled, disabled...)
If you find a software where the script does not work, it uses self-programmed context menus (office, pspad).
To use the code, copy the functions to your script or just delete the demo: label
Ciao
Micha
| Code: | ;
; AutoHotkey Version: 1.x
; Language: English
; Platform: Win9x/NT
; Author: micha
;
; Script Function:
; Demonstrates how to retrieve infos from a context/ popup menu
;
/*
This is the struct we are using.
typedef struct tagMENUITEMINFO {
UINT cbSize;
UINT fMask;
UINT fType;
UINT fState;
UINT wID;
HMENU hSubMenu;
HBITMAP hbmpChecked;
HBITMAP hbmpUnchecked;
ULONG_PTR dwItemData;
LPTSTR dwTypeData;
UINT cch;
HBITMAP hbmpItem;
} MENUITEMINFO, *LPMENUITEMINFO;
*/
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
#Persistent
SetTimer, Demo, 500
return
Demo:
;constants
MFS_ENABLED = 0
MFS_CHECKED = 8
MFS_DEFAULT = 0x1000
MFS_DISABLED = 2
MFS_GRAYED = 1
MFS_HILITE = 0x80
;MFS_UNCHECKED = 0
;MFS_UNHILITE = 0
;Get mouse position and handle to wnd under the mouse cursor
MouseGetPos, MouseScreenX, MouseScreenY, MouseWindowUID, MouseControlID
WinGet,ControlHwnd, ID,ahk_id %MouseWindowUID%
;Get count of menu items
ContextMenCnt := GetContextMenuCount(ControlHwnd)
if ContextMenCnt < 1
{
Tooltip,
return
}
TooltipText =
;Read info for each menu item
loop, %ContextMenCnt%
{
IsEnabled := GetContextMenuState(ControlHwnd, a_index-1)
{
CurrentText =
if IsEnabled = 0
CurrentText = %CurrentText% Enabled
if (IsEnabled & MFS_CHECKED)
CurrentText = %CurrentText% Checked
if (IsEnabled & MFS_DEFAULT)
CurrentText = %CurrentText% Default
if (IsEnabled & MFS_DISABLED)
CurrentText = %CurrentText% Disabled
if (IsEnabled & MFS_GRAYED)
CurrentText = %CurrentText% Grayed
if (IsEnabled & MFS_HILITE)
CurrentText = %CurrentText% Highlight
TooltipText = %TooltipText%%a_index%:%CurrentText%`n
}
}
TextText =
loop, %ContextMenCnt%
{
StrSize := GetContextMenuText(ControlHwnd, a_index-1)
TextText = %TextText%%a_index%:%StrSize%`n
}
CoordMode, Tooltip, Screen
Tooltip, %TooltipText%---`n%TextText%, 0, 0
return
/***************************************************************
* returns the count of menu items
***************************************************************
*/
GetContextMenuCount(hWnd)
{
WinGetClass, WindowClass, ahk_id %hWnd%
;All popups should have the window class #32768
if WindowClass <> #32768
{
return 0
}
;Retrieve menu handle from window
SendMessage, 0x01E1, , , , ahk_id %hWnd%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
menuitemcount:=DllCall("GetMenuItemCount",UInt,hMenu)
Return, menuitemcount
}
/***************************************************************
* returns the state of a menu entry
***************************************************************
*/
GetContextMenuState(hWnd, Position)
{
WinGetClass, WindowClass, ahk_id %hWnd%
if WindowClass <> #32768
{
return -1
}
SendMessage, 0x01E1, , , , ahk_id %hWnd%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
;We need to allocate a struct
VarSetCapacity(MenuItemInfo, 60, 0)
;Set Size of Struct to the first member
InsertInteger(48, MenuItemInfo, 0, 4)
;Get only Flags from dllcall GetMenuItemInfo MIIM_TYPE = 1
InsertInteger(1, MenuItemInfo, 4, 4)
;GetMenuItemInfo: Handle to Menu, Index of Position, 0=Menu identifier / 1=Index
InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
InfoResError := errorlevel
LastErrorRes := DllCall("GetLastError")
if InfoResError <> 0
return -1
if LastErrorRes != 0
return -1
;Get Flag from struct
GetMenuItemInfoRes := ExtractInteger(MenuItemInfo, 12, false, 4)
/*
IsEnabled = 1
if GetMenuItemInfoRes > 0
IsEnabled = 0
return IsEnabled
*/
return GetMenuItemInfoRes
}
/***************************************************************
* returns the text of a menu entry (standard windows context menus only!!!)
***************************************************************
*/
GetContextMenuText(hWnd, Position)
{
WinGetClass, WindowClass, ahk_id %hWnd%
if WindowClass <> #32768
{
return -1
}
SendMessage, 0x01E1, , , , ahk_id %hWnd%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
;We need to allocate a struct
VarSetCapacity(MenuItemInfo, 200, 0)
;Set Size of Struct (48) to the first member
InsertInteger(48, MenuItemInfo, 0, 4)
;Retrieve string MIIM_STRING = 0x40 = 64 (/ MIIM_TYPE = 0x10 = 16)
InsertInteger(64, MenuItemInfo, 4, 4)
;Set type - Get only size of string we need to allocate
;InsertInteger(0, MenuItemInfo, 8, 4)
;GetMenuItemInfo: Handle to Menu, Index of Position, 0=Menu identifier / 1=Index
InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
if InfoRes = 0
return -1
InfoResError := errorlevel
LastErrorRes := DllCall("GetLastError")
if InfoResError <> 0
return -1
if LastErrorRes <> 0
return -1
;Get size of string from struct
GetMenuItemInfoRes := ExtractInteger(MenuItemInfo, 40, false, 4)
;If menu is empty return
If GetMenuItemInfoRes = 0
return "{Empty String}"
;+1 should be enough, we'll use 2
GetMenuItemInfoRes += 2
;Set capacity of string that will be filled by windows
VarSetCapacity(PopupText, GetMenuItemInfoRes, 0)
;Set Size plus 0 terminator + security ;-)
InsertInteger(GetMenuItemInfoRes, MenuItemInfo, 40, 4)
InsertInteger(&PopupText, MenuItemInfo, 36, 4)
InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
if InfoRes = 0
return -1
InfoResError := errorlevel
LastErrorRes := DllCall("GetLastError")
if InfoResError <> 0
return -1
if LastErrorRes <> 0
return -1
return PopupText
}
; *********************************
; *********************************
; Original versions of ExtractInteger and InsertInteger provided by Chris
; - from the AutoHotkey help file - Version 1.0.37.04
; *********************************
; *********************************
ExtractInteger(ByRef pSource, pOffset = 0, pIsSigned = false, pSize = 4)
; pSource is a string (buffer) whose memory area contains a raw/binary integer at pOffset.
; The caller should pass true for pSigned to interpret the result as signed vs. unsigned.
; pSize is the size of PSource's integer in bytes (e.g. 4 bytes for a DWORD or Int).
; pSource must be ByRef to avoid corruption during the formal-to-actual copying process
; (since pSource might contain valid data beyond its first binary zero).
{
SourceAddress := &pSource + pOffset ; Get address and apply the caller's offset.
result := 0 ; Init prior to accumulation in the loop.
Loop %pSize% ; For each byte in the integer:
{
result := result | (*SourceAddress << 8 * (A_Index - 1)) ; Build the integer from its bytes.
SourceAddress += 1 ; Move on to the next byte.
}
if (!pIsSigned OR pSize > 4 OR result < 0x80000000)
return result ; Signed vs. unsigned doesn't matter in these cases.
; Otherwise, convert the value (now known to be 32-bit) to its signed counterpart:
return -(0xFFFFFFFF - result + 1)
}
InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; To preserve any existing contents in pDest, only pSize number of bytes starting at pOffset
; are altered in it. The caller must ensure that pDest has sufficient capacity.
{
mask := 0xFF ; This serves to isolate each byte, one by one.
Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data.
{
DllCall("RtlFillMemory", UInt, &pDest + pOffset + A_Index - 1, UInt, 1 ; Write one byte.
, UChar, (pInteger & mask) >> 8 * (A_Index - 1)) ; This line is auto-merged with above at load-time.
mask := mask << 8 ; Set it up for isolation of the next byte.
}
}
; *********************************
; *********************************
PrintScreen::reload
|
|
|
| Back to top |
|
 |
daonlyfreez
Joined: 16 Mar 2005 Posts: 744 Location: Berlin
|
Posted: Wed Jul 25, 2007 12:12 pm Post subject: |
|
|
Great script! Very useful, thank you...  _________________ (sorry, homesite offline atm) |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Thu Jul 26, 2007 12:33 pm Post subject: |
|
|
This script is really useful!
Would it also be possible to add an entry to the context menu. This would really open up a lot of possibilities for seamless integration of Autohotkey scripts into various programs. |
|
| Back to top |
|
 |
Helpy Guest
|
Posted: Thu Jul 26, 2007 2:06 pm Post subject: |
|
|
Nice!
Note that ExtractInteger and InsertInteger are no longer needed since we have now NumGet and NumPut.
David Andersen, this has been asked some times, but I fear it could be hard to impossible (but impossible is often a void word here!): I guess additional menu items would still have to be processed by the target application which will just ignore them... Unless somebody know how to hook the corresponding messages, before the soft see them! |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Thu Jul 26, 2007 2:34 pm Post subject: |
|
|
| Yeah, hooking is a good idea. I saw a window hook somewhere here. It would be perfectly acceptable that new items only could be placed at the bottom of the list. Could you get the x/y coordinates of the frame around the context menu? This could be a step on the way, I believe. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2492 Location: Australia, Qld
|
Posted: Fri Jul 27, 2007 10:32 am Post subject: |
|
|
| David Andersen wrote: | | Could you get the x/y coordinates of the frame around the context menu? | It should be easy to get the coords of the menu window itself, if that's what you mean: | Code: | | WinGetPos, x, y,,, ahk_id %ControlHwnd% ; where ControlHwnd is the ID of the menu window... | I don't see what use this is, though. (I think it would be simple to add items to the end of a menu's list of items; the complicated bit would be intercepting messages to detect when the menu item is selected.)
Micha, if I may ask, what is the purpose of the WinGet,,ID call? | Code: | MouseGetPos, MouseScreenX, MouseScreenY, MouseWindowUID, MouseControlID
WinGet,ControlHwnd, ID,ahk_id %MouseWindowUID% | WinGet,,ID gets the unique identifier of a window, and
ahk_id %var% is used to target a window by its unique identifier,
so would not the result (ControlHwnd) always be equal to the input (MouseWindowUID)? |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Fri Jul 27, 2007 10:46 am Post subject: |
|
|
lexikos,
Thanks for your answer. If a menu item could be added to the context menu, then one could get the measurements of the whole context menu before and after we add an item. Then one could probably intercept all mouse clicks and see if they have the right x/y coordinates to hit the item we added.
This process would probably have to be gone through each time a context menu is displayed, in case that some of the items have changed. Visually it could simply look fancy that there is a delay of half a second from the context menu is displayed until the last item shows up.  |
|
| Back to top |
|
 |
Sean
Joined: 12 Feb 2007 Posts: 1281
|
Posted: Fri Jul 27, 2007 11:51 am Post subject: Re: Get info from context menu |
|
|
| Micha wrote: | | Hi, there are solutions how to get information from a standard menu (File, edit, help) but I haven't found a solution for context menus (i.e. right click in explorer) |
Thanks for sharing the script, however, it's been found already:
http://www.autohotkey.com/forum/topic17904.html
Here is the code I've been using since then. I think I combined just MN_GETHMENU with the script already existing in the forum, but I can't remember it. Just take it as a simplified alternative:
| Code: | #SingleInstance, Force
SetBatchLInes, -1
WinWait, ahk_class #32768
SendMessage, 0x1E1, 0, 0 ; MN_GETHMENU
hMenu := ErrorLevel
sContents := GetMenu(hMenu)
WinWaitClose
MsgBox, % sContents
GetMenu(hMenu)
{
Loop, % DllCall("GetMenuItemCount", "Uint", hMenu)
{
idx := A_Index - 1
idn := DllCall("GetMenuItemID", "Uint", hMenu, "int", idx)
nSize++ := DllCall("GetMenuString", "Uint", hMenu, "int", idx, "Uint", 0, "int", 0, "Uint", 0x400)
VarSetCapacity(sString, nSize)
DllCall("GetMenuString", "Uint", hMenu, "int", idx, "str", sString, "int", nSize, "Uint", 0x400) ;MF_BYPOSITION
If !sString
sString := "---------------------------------------"
sContents .= idx . " : " . idn . A_Tab . A_Tab . sString . "`n"
If (idn = -1) && (hSubMenu := DllCall("GetSubMenu", "Uint", hMenu, "int", idx))
sContents .= GetMenu(hSubMenu)
}
Return sContents
}
|
|
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2492 Location: Australia, Qld
|
Posted: Fri Jul 27, 2007 1:00 pm Post subject: |
|
|
| David Andersen wrote: | | Then one could probably intercept all mouse clicks and see if they have the right x/y coordinates to hit the item we added. | Ah, I hadn't thought of that. If you do take this approach, you might be interested in GetMenuItemRect(), which can be used to get the position and size of a menu item. Also, don't forget keyboard navigation (arrow keys and mnemonic characters.)  |
|
| Back to top |
|
 |
David Andersen
Joined: 15 Jul 2005 Posts: 85 Location: Denmark
|
Posted: Fri Jul 27, 2007 1:08 pm Post subject: |
|
|
lexikos,
Excellent! It is great to know of this possibility! In essense one could create seamless plug-ins for various programs (that don't even have support for plug-ins). This should have a big potensial for Autohotkey. |
|
| Back to top |
|
 |
jsmain
Joined: 11 Jul 2005 Posts: 81
|
Posted: Fri Jul 27, 2007 2:29 pm Post subject: |
|
|
This could indeed be usefull, but IMO, we need to be able to activate context menus in a hidden state, for a particular control, item in a control, or mouse coordinates for it to be completely useful.
Anyone familiar with Hidepopup from the MadCodeHook collection?
Used as a girder plugin, you could determine menu settings, and set, unset, or activate menu and context menu items without showing the actual menu.
Makes for a much cleaner solution. _________________ Jeff Main |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2492 Location: Australia, Qld
|
Posted: Sat Jul 28, 2007 11:07 am Post subject: |
|
|
| jsmain wrote: | | This could indeed be usefull, but IMO, we need to be able to activate context menus in a hidden state, for a particular control, item in a control, or mouse coordinates for it to be completely useful. | That should be mostly possible by simulating mouse input (i.e. ControlClick.) The hard part is having the context menu activate in a hidden state.
I've had some success using SetWinEventHook (hooking EVENT_SYSTEM_MENUPOPUPSTART) to move a menu off-screen as soon as it opens. At first I was trying to set the transparency level of any menu that pops up, but I found that the "fade in" animation (Windows XP) was overriding that. Fortunately, it seems the fade-in animation also prevents the menu from being visible for the split-second between when the menu is created and when it is moved (off-screen, using WinMove.) I tried using WinHide to hide the menu, but that left the drop-shadow permanently on my screen.
Interestingly, SetWinEventHook can also be used to track menu item selection (EVENT_OBJECT_FOCUS), which could assist in overriding/hooking menu items. (As far as I can tell, there is no event for activating/clicking an item, but mouse/keyboard hotkeys could be used in combinaton with EVENT_OBJECT_FOCUS.)
One possibly big problem I can see is in determining if the menu that popped up is the right one...
I think a CBT hook (SetWindowsHookEx, WH_CBT) could be used to hook creation of the menu window properly - i.e. without the menu appearing briefly - but it seems CBT hook callbacks must be compiled into DLL files. |
|
| Back to top |
|
 |
jsmain
Joined: 11 Jul 2005 Posts: 81
|
Posted: Fri Aug 03, 2007 4:26 pm Post subject: |
|
|
My primary use for this would be to get information from the menu....
for example, in IE, the setting of the Status bar.... View >Status Bar
If I can determine the state of the menu setting, I can adjust it as necessary with postmessage.
So this limits the amount of pop up menus to only that of information retrieval.
Is there a better way of opening a specific menu target.... As in my example, "View > Status Bar", than using mouse position? or without using the mouse and keyboard at all?
In the Madcodehook hidepopup, the configuration allowed you to call out the menus as "View" "Status Bar" _________________ Jeff Main |
|
| Back to top |
|
 |
contextxyz
Joined: 08 Aug 2007 Posts: 1
|
Posted: Wed Aug 08, 2007 1:53 am Post subject: conext menu keyboard shortcut |
|
|
I would also like to know how to create a keyboard shortcut, eg f12 to activate/select a context menu item. I in fact have a program where I need to select a context menu submenu, so its actually a bit more involved
It would be best as some others(lexikos) have mentioned that if this function could be hidden, ie not have to go through simulated mouse clicks to do this would be great, but I'll take anything. |
|
| Back to top |
|
 |
jsmain
Joined: 11 Jul 2005 Posts: 81
|
Posted: Wed Aug 08, 2007 12:14 pm Post subject: |
|
|
If you have windows spy utility, or wininspector, you can identify the Menu ID(wParam), and then just use postmessage to seend the message to the window directly, unless of course you need to determine the checkstate....
Rajat's got a great article on this here. _________________ Jeff Main |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|