AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Can AHK detect external program menu selections
Goto page 1, 2  Next
 
Reply to topic    AutoHotkey Community Forum Index -> Ask for Help
View previous topic :: View next topic  
Author Message
Leef_me



Joined: 08 Apr 2009
Posts: 5335
Location: San Diego, California

PostPosted: Sat Jan 09, 2010 1:34 am    Post subject: Can AHK detect external program menu selections Reply with quote

We have mousegetpos, winget and a host of other commands.

mousegetpos can tell the window, x,y, control
Winget can tell the active window and a list of windows & controls
with these I can even detect the press of a button on n external program.

Is there a command that allows detecting when a menu item is selected?

We can create a menu in a gui, we can select a menu item, etc

But can we capture the selection, or must that be coded by hand ?

What I am really looking for is script recorder that captures menu and application button clicks .

Any ideas ? Thanks in advance

Leef_me
Back to top
View user's profile Send private message
Peter



Joined: 30 Dec 2005
Posts: 448

PostPosted: Sat Jan 09, 2010 6:43 am    Post subject: Reply with quote

There's a SetWinEventHook API. Have a look at [tool] WinEventHook Messages.
It doesn't seem to detect a selection though. I'm quite sure there will be a message sent with a selection, but to the program that owns the menu.
Back to top
View user's profile Send private message
Leef_me



Joined: 08 Apr 2009
Posts: 5335
Location: San Diego, California

PostPosted: Mon Jan 11, 2010 8:32 pm    Post subject: Reply with quote

Peter's answer doesn't address my question.

Perhaps a rewording of my question would help:

Does someone have an example that allows detecting when a menu item is selected, in an external program?
Back to top
View user's profile Send private message
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Mon Jan 11, 2010 10:16 pm    Post subject: Reply with quote

Leef_me wrote:
...in an external program?

I modified Micha's Get Info from Context Menu to do the job, but as Micha stated it works on...
Quote:
standard windows context menus only!!!

Code:
;constants
MFS_HILITE = 0x80

#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
#Persistent
Return

#IfWinExist ahk_class #32768

LButton::
Enter::
NumPadEnter::
WinGet, hwnd, ID, A
WinGet, hMenu, ID, ahk_class #32768

;Get count of menu items
ContextMenCnt := GetContextMenuCount(hMenu)
If ContextMenCnt < 1
{
   Tooltip
   Return
}
item =
Loop %ContextMenCnt%
{
   IsEnabled := GetContextMenuState(hMenu, A_Index-1)
      If (IsEnabled & MFS_HILITE)
         item := A_Index
}
If item
   Tooltip, % GetContextMenuText(hMenu, item - 1)
Else
   ToolTip

If A_ThisHotkey = LButton
   Click Down
Else
   ControlSend, , {%A_ThisHotkey%}, ahk_id %hwnd%
Return

~LButton Up:: Return

/***************************************************************
 * returns the count of menu items
 ***************************************************************
*/
GetContextMenuCount(hMenu)
{
  WinGetClass, WindowClass, ahk_id %hMenu%
  ;All popups should have the window class #32768
  if WindowClass <> #32768
  {
   return 0
  }
  ;Retrieve menu handle from window
  SendMessage, 0x01E1, , , , ahk_id %hMenu%
  ;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(hMenu, Position)
{
  WinGetClass, WindowClass, ahk_id %hMenu%
  if WindowClass <> #32768
  {
   return -1
  }
  SendMessage, 0x01E1, , , , ahk_id %hMenu%
  ;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
  NumPut(48, MenuItemInfo, 0, 4)
  ;Get only Flags from dllcall GetMenuItemInfo MIIM_TYPE = 1
  NumPut(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)
  ; GetMenuItemInfoRes0 := ExtractInteger(MenuItemInfo, 12, false, 4)
  GetMenuItemInfoRes := NumGet(MenuItemInfo, 12)
  GetMenuItemInfoRes0a := NumGet(MenuItemInfo, 12)
  /*
  IsEnabled = 1
  if GetMenuItemInfoRes > 0
     IsEnabled = 0
  return IsEnabled
  */
  return GetMenuItemInfoRes
}

/***************************************************************
 * returns the text of a menu entry (standard windows context menus only!!!)
 ***************************************************************
*/
GetContextMenuText(hMenu, Position)
{
  Global GetMenuItemInfoRes0
  Global GetMenuItemInfoRes0a
  Global GetMenuItemInfoRes1
  Global GetMenuItemInfoRes1a

  WinGetClass, WindowClass, ahk_id %hMenu%
  if WindowClass <> #32768
  {
   return -1
  }
  SendMessage, 0x01E1, , , , ahk_id %hMenu%
  ;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
  NumPut(48, MenuItemInfo, 0, 4)
  ;Retrieve string MIIM_STRING = 0x40 = 64 (/ MIIM_TYPE = 0x10 = 16)
  NumPut(64, MenuItemInfo, 4, 4)
  ;Set type - Get only size of string we need to allocate
  ;NumPut(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)
  ; GetMenuItemInfoRes1 := ExtractInteger(MenuItemInfo, 40, false, 4)
  GetMenuItemInfoRes := NumGet(MenuItemInfo, 40)
  GetMenuItemInfoRes1a := NumGet(MenuItemInfo, 40)
  ;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 ;-)
  NumPut(GetMenuItemInfoRes, MenuItemInfo, 40, 4)
  NumPut(&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
}

PrintScreen::reload

Also, it doesn't work on Explorer's / IE's favorites (it's really not a menu), and some submenus (like Explorer's File -> Send) show "{Empty String}".

It's not perfect: it works if the item is selected by a mouse or Enter key, but not when it's selected by a key, which is underlined in the menu (preceded by an ampersand in the tooltip).
_________________
Pekka Vartto


Last edited by svi on Tue Jan 12, 2010 10:19 am; edited 1 time in total
Back to top
View user's profile Send private message
Leef_me



Joined: 08 Apr 2009
Posts: 5335
Location: San Diego, California

PostPosted: Tue Jan 12, 2010 3:06 am    Post subject: Reply with quote

Thank you svi.

I'll have to study this for a while.
Back to top
View user's profile Send private message
Peter



Joined: 30 Dec 2005
Posts: 448

PostPosted: Tue Jan 12, 2010 7:14 am    Post subject: Reply with quote

Leef_me wrote:
Does someone have an example that allows detecting when a menu item is selected, in an external program?
External programs is not the problem. Perhaps a rewording of my answer Smile .
It detects when a standard menu opens and closes on any program, but not when/which a selection is made. But the latter can maybe solved with some hotkeys waiting after the menu opened?!
Back to top
View user's profile Send private message
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Tue Jan 12, 2010 5:37 pm    Post subject: Reply with quote

It can be done in much simplier way.
You can use the following script to test which menu item selections [EDIT: actually highlightings] are detected (beep if not) by pressing q (I use that as a hotkey on my test scripts, because it's not used in the Finnish language).
Esc to exit.
Code:
q::

SendMessage, 0x1E1, 0, 0, , ahk_class #32768
hMenu := ErrorLevel
Loop, % DllCall("GetMenuItemCount", "UInt", hMenu)
{
   idx := A_Index - 1
   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)
   VarSetCapacity(mii, 48, 0)
   NumPut(48, mii, 0)
   NumPut(1, mii, 4)
   DllCall("GetMenuItemInfo", "UInt", hMenu, "UInt", idx, "UInt", 1, "UInt", &mii)
   fState := NumGet(mii, 12)
   If (fState & 0x80 And sString)
   {
      ToolTip % sString
      Return
   }
   Else
      ToolTip
}
SoundBeep
Return

Esc:: ExitApp

Detecting mnemonic key usage seems too complicated Confused
_________________
Pekka Vartto
Back to top
View user's profile Send private message
TLM



Joined: 21 Aug 2006
Posts: 2926
Location: The Shell

PostPosted: Thu May 13, 2010 8:33 pm    Post subject: Reply with quote

Pekka, very nice way to simplify Wink

Errorlevel returns fail from the message sent.
Working on another approach to conjure the menu.

Iam I to assume that errorlevel should be 32768's handle?

Again, great work.
_________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞
Back to top
View user's profile Send private message
svi



Joined: 09 Oct 2006
Posts: 236
Location: Finland

PostPosted: Sun May 16, 2010 10:31 pm    Post subject: Reply with quote

TLM wrote:
Pekka, very nice way to simplify Wink

Unfortunately I simplified a little too much: the script doesn't show the last character of the item!
Maybe it's line 160 in my first script (originally Micha's):
Code:
159 ;+1 should be enough, we'll use 2
160 GetMenuItemInfoRes += 2
, which should have been left Rolling Eyes

Increasing nSize with 1 seems to work:
Code:
q::

SendMessage, 0x1E1, 0, 0, , ahk_class #32768
hMenu := ErrorLevel
Loop, % DllCall("GetMenuItemCount", "UInt", hMenu)
{
   idx := A_Index - 1
   nSize := DllCall("GetMenuString", "UInt", hMenu, "Int", idx, "UInt", 0, "Int", 0, "UInt", 0x400) + 1
   VarSetCapacity(sString, nSize)
   DllCall("GetMenuString", "Uint", hMenu, "Int", idx, "Str", sString, "Int", nSize, "UInt", 0x400)
   VarSetCapacity(mii, 48, 0)
   NumPut(48, mii, 0)
   NumPut(1, mii, 4)
   DllCall("GetMenuItemInfo", "UInt", hMenu, "UInt", idx, "UInt", 1, "UInt", &mii)
   fState := NumGet(mii, 12)
   If (fState & 0x80 And sString)
   {
      ToolTip % sString
      Return
   }
   Else
      ToolTip
}
SoundBeep
Return

Esc:: ExitApp

... but I should really update the script to use GetMenuItemInfo:
MSDN wrote:
[The GetMenuString function has been superseded. Use the GetMenuItemInfo function to retrieve the menu item text.]

I (and hopefully some others, too) tested my script (as all scripts I've posted), but not well enough.
Thanks to SifJar for reporting.
_________________
Pekka Vartto
Back to top
View user's profile Send private message
TLM



Joined: 21 Aug 2006
Posts: 2926
Location: The Shell

PostPosted: Tue May 18, 2010 4:30 pm    Post subject: Reply with quote

I have yet to get the initial message to not return false.
I think it has something to do with the class name.
For some reason the system I work on does not see #327 series classes.

I had to use GetDesktopWindow to get the upper most handle here.
Code:
setformat, integerFast, H
msgbox % dllCall("GetDesktopWindow")+0
Rather than just being able to call it from the classname 32769 Rolling Eyes

I'm assuming this is why the message is not working..
_________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞
Back to top
View user's profile Send private message
SifJar



Joined: 13 Feb 2010
Posts: 170

PostPosted: Tue May 18, 2010 5:55 pm    Post subject: Reply with quote

svi: Does this line:

Code:
SendMessage, 0x1E1, 0, 0, , ahk_class #32768


set error level to the current menu item's handle? I'm trying to understand your code...

EDIT: If you could also explain this line, it'd be handy:
Quote:

If (fState & 0x80 And sString)


I think I understand that it checks if fState and sString exist, but why the " & 0x80 "?

One last query: Why do you need to get the MenuInfo, if it seems it is not used? Why not just GetMenuString, then use it?


Last edited by SifJar on Tue May 18, 2010 6:14 pm; edited 1 time in total
Back to top
View user's profile Send private message
TLM



Joined: 21 Aug 2006
Posts: 2926
Location: The Shell

PostPosted: Tue May 18, 2010 6:12 pm    Post subject: Reply with quote

SifJar wrote:
svi: Does this line:

Code:
SendMessage, 0x1E1, 0, 0, , ahk_class #32768


set error level to the current menu item's handle? I'm trying to understand your code...

Yes it does if all goes well. It returns the handle for the class name.
If not it returns fail.
_________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞
Back to top
View user's profile Send private message
SifJar



Joined: 13 Feb 2010
Posts: 170

PostPosted: Tue May 18, 2010 6:18 pm    Post subject: Reply with quote

TLM wrote:
SifJar wrote:
svi: Does this line:

Code:
SendMessage, 0x1E1, 0, 0, , ahk_class #32768


set error level to the current menu item's handle? I'm trying to understand your code...

Yes it does if all goes well. It returns the handle for the class name.
If not it returns fail.


i.e. returns the handle for a menu, generally, not a specific item? OK. Not 100% sure what a handle is, but I think I get the idea. If you happen to know the answers to my other questions I added to my post, it'd be great if you could answer...Thanks for answering that one btw.

EDIT: Also, am I correct in thinking any variable in a struct like this one is 4 bytes long? I'm trying to understand DLL calls here too...
Back to top
View user's profile Send private message
TLM



Joined: 21 Aug 2006
Posts: 2926
Location: The Shell

PostPosted: Tue May 18, 2010 6:43 pm    Post subject: Reply with quote

SifJar wrote:
Not 100% sure what a handle is, but I think I get the idea.
A handle is a HEX ID for a window, menu etc in memory.

SifJar wrote:
EDIT: Also, am I correct in thinking any variable in a struct like this one is 4 bytes long? I'm trying to understand DLL calls here too...


To understand structs you should 1st understand pointers (text from C++ ebook).

To your question of adding variables to a struct, yes each member can be added per 4bytes like this:
Code:
; Rect struct
VarSetCapacity( R, 16, 0 )
NumPut( x1, &R+0 )
NumPut( y1, &R+4 )
NumPut( x2, &R+8 )
NumPut( y2, &R+12)

Just remember to call the pointer from memory with a preceding &.
_________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞
Back to top
View user's profile Send private message
SifJar



Joined: 13 Feb 2010
Posts: 170

PostPosted: Tue May 18, 2010 6:56 pm    Post subject: Reply with quote

TLM wrote:


To your question of adding variables to a struct, yes each member can be added per 4bytes like this:
Code:
; Rect struct
VarSetCapacity( R, 16, 0 )
NumPut( x1, &R+0 )
NumPut( y1, &R+4 )
NumPut( x2, &R+8 )
NumPut( y2, &R+12)

Just remember to call the pointer from memory with a preceding &.


Instead of writing say
Code:
NumPut( x1, &R+0 )
could you also use
Code:
NumPut( x1, &R, 0 )

Question This seems to be what svi did in his code, when adding members to "mii" (MenuItemInfo), are they pretty much the same thing, or is there an important difference?

EDIT: Also, he didn't have the preceding &, he just had the variable name, and according the to help file, that works better...

EDIT: I 'm fairly sure I understand pointers now, do you know of a good reference for structs?

EDIT: I found some stuff, I think I kinda get the idea.A struct contains a set of members and each member is a pointer to a variable, am I right?
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Ask for Help All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group