Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Invoking directly ContextMenu of Files and Folders


  • Please log in to reply
62 replies to this topic
otlaolap
  • Members
  • 43 posts
  • Last active: Sep 12 2013 02:33 AM
  • Joined: 15 Aug 2007

I don't know how you use it, however, you can obtain the status bar help text of menu item using
Code:
VarSetCapacity(sHelp,63)
DllCall(NumGet(NumGet(1*pcm)+20), "Uint", pcm, "Uint", idCommand, "Uint", GCS_HELPTEXTA:=1, "Uint", 0, "str", sHelp, "Uint", 64) ; GetCommandString


This sounds to me like it will deliver the text of the currently highlighted menu item as I traverse the menu while it is displayed, using keys or the mouse. Can you show me where to place this in your original ShellContextMenu.ahk file? I know that the WindowProc in that file gets lots of messages, many of them before the menu is displayed, but a couple as I move from menu item to menu item. I imagine that some of these latter messages are notifying me that the user has moved the mouse onto a new specific menu item.

In particular I am getting messages 287 (0x011F), which is WM_MENUSELECT, at reasonable times as I do this traversal. This message has a wParam; low-order word of wParam specifies the menu item or submenu index presumably of the selected menu item. In any case it is a reasonable number (22, say), and changes by one up or down as I move up or down in the menu.

But when I feed this into the above DllCall as the value for idCommand, I get completely null text for the result. I have also tried feeding it in with the GCS_VERBA:=0 argument, and get similarly blank results. Here is what I added to WindowProc

   Global pcm
   If (nMsg = 287) ; WM_MENUSELECT
   {
      MenuItem := wParam & 65535
      VarSetCapacity(sHelp,257,79)
      DllCall(NumGet(NumGet(1*pcm)+20), "Uint", pcm, "Uint", MenuItem, "Uint", GCS_HELPTEXTA:=1, "Uint", 0, "str", sHelp, "Uint", 256) ; GetCommandString
      outputdebug %ErrorLevel% * %A_LastError% * Help %MenuItem% * %sHelp%
   }
%ErrorLevel% is always -4, A_LastError is always 0, and sHelp is always unchanged.

Thanks.

(This post has been edited to add details.)

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
Good. Although I had no intention to maintain this script, as you actually tried to understand it I feel obliged to help your inquiry.

First you have to declare pcm to be Global also in the function where it's assigned, or I suppose you could use pcm2 or pcm3 instead.
And in my experience Unicode version worked better than Ansi version, so I will use GCS_HELPTEXTW ( := 5), however, you can try GCS_HELPTEXTA instead, or even both.
Finally we have to subtract the first idn specified, may be called idnFirst, which is hard-coded as 3 in the script.

So, the prototype would look like
If	nMsg = 287
{
	VarSetCapacity(sHelp,255,0)
	DllCall(NumGet(NumGet(1*pcm)+20), "Uint", pcm, "Uint", (wParam&0xFFFF)-[color=red]3[/color], "Uint", 5, "Uint", 0, "Uint", &sHelp, "Uint", 128)	; GetCommandString
	OutputDebug % COM_Ansi4Unicode(&sHelp)
}


otlaolap
  • Members
  • 43 posts
  • Last active: Sep 12 2013 02:33 AM
  • Joined: 15 Aug 2007
Ouch - all I had to do was declare pcm global in the other procedure (ShellContextMenu) and I began getting good feedback, and the -3 number made it accurate. There was no need for me to go to Unicode.

This is a very useful script, and I was glad to find it. I've been using the original for some while, and have been ferreting around for a way to find out what actually is in a context menu so that invoking an item can be automated. As with user pajenn, this stems from using FileMenu Tools, which does not support letter accellerators on its menu items. The alternate approach as been to interrogate the status bar text of Windows Explorer as you move through the items (using keydown sends and so on); I also extended this for Xplorer2 to interrogate its status bar. But when I popup a menu outside of these environments, using your script, there are no status bars to interrogate. Hence my need for this recent GetCommandString extension.

Now it's working nicely. Thanks again.

pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009
Sean, I'm trying to use the script to pick an item by status bar text as follows:

1. Run one script to wait for a Modified_ShellContentMenu window, then call the Modified_ShellContextMenu that includes the status bar text stuff, plus the desired status bar text as additional argument. For example:

Run, Crawl_ShellContentMenu.ahk
Sleep, 200
Modified_ShellContextMenu("C:\temp\testing.ahk","Open with Notepad2")

2. Here's the Modified_ShellContentMenu.ahk, but it's buggy... probably because I'm failing to unload some of the com stuff, (it starts off ok, but gets worse over time) - sorry to mutilate the original code; I'm just using trial and error to get from point A to B:

; Modified version of Sean's ShellContextMenu

Modified_ShellContextMenu(sPath,SbText)
{ CoInitialize()
  Global   pcm
  If   sPath Is Not Integer
    DllCall("shell32\SHParseDisplayName", "Uint", Unicode4Ansi(wPath,sPath), "Uint", 0, "UintP", pidl, "Uint", 0, "Uint", 0)
  Else DllCall("shell32\SHGetFolderLocation", "Uint", 0, "int", sPath, "Uint", 0, "Uint", 0, "UintP", pidl)
  DllCall("shell32\SHBindToParent", "Uint", pidl, "Uint", GUID4String(IID_IShellFolder,"{000214E6-0000-0000-C000-000000000046}"), "UintP", psf, "UintP", pidlChild)
  DllCall(NumGet(NumGet(1*psf)+40), "Uint", psf, "Uint", 0, "Uint", 1, "UintP", pidlChild, "Uint", GUID4String(IID_IContextMenu,"{000214E4-0000-0000-C000-000000000046}"), "Uint", 0, "UintP", pcm)
  Release(psf)
  CoTaskMemFree(pidl)

  hMenu := DllCall("CreatePopupMenu")
  DllCall(NumGet(NumGet(1*pcm)+12), "Uint", pcm, "Uint", hMenu, "Uint", 0, "Uint", 3, "Uint", 0x7FFF, "Uint", 0)  ; QueryContextMenu
  DetectHiddenWindows, On
  Process, Exist
  WinGet, hAHK, ID, ahk_pid %ErrorLevel%
  WinActivate, ahk_id %hAHK%
  Global   pcm2 := QueryInterface(pcm,IID_IContextMenu2:="{000214F4-0000-0000-C000-000000000046}")
  Global   pcm3 := QueryInterface(pcm,IID_IContextMenu3:="{BCFCE0A0-EC17-11D0-8D10-00A0C90F2719}")
  Global   WPOld:= DllCall("SetWindowLong", "Uint", hAHK, "int",-4, "int",RegisterCallback("WindowProc"))
  DllCall("GetCursorPos", "int64P", pt)
  DllCall("InsertMenu", "Uint", hMenu, "Uint", 0, "Uint", 0x0400|0x800, "Uint", 2, "Uint", 0)
  DllCall("InsertMenu", "Uint", hMenu, "Uint", 0, "Uint", 0x0400|0x002, "Uint", 1, "Uint", &sPath)
  idn := DllCall("TrackPopupMenu", "Uint", hMenu, "Uint", 0x0100, "int", pt << 32 >> 32, "int", pt >> 32, "Uint", 0, "Uint", hAHK, "Uint", 0)
  NumPut(VarSetCapacity(ici,64,0),ici), NumPut(0x4000|0x20000000,ici,4), NumPut(1,NumPut(hAHK,ici,8),12), NumPut(idn-3,NumPut(idn-3,ici,12),24), NumPut(pt,ici,56,"int64")
  DllCall(NumGet(NumGet(1*pcm)+16), "Uint", pcm, "Uint", &ici)   ; InvokeCommand
;   VarSetCapacity(sName,259), DllCall(NumGet(NumGet(1*pcm)+20), "Uint", pcm, "Uint", idn-3, "Uint", 1, "Uint", 0, "str", sName, "Uint", 260)   ; GetCommandString
  DllCall("GlobalFree", "Uint", DllCall("SetWindowLong", "Uint", hAHK, "int", -4, "int", WPOld))
  DllCall("DestroyMenu", "Uint", hMenu)
  Release(pcm3)
  Release(pcm2)
  Release(pcm)
  CoUninitialize()
  pcm:=pcm2:=pcm3:=WPOld:=0
}

WindowProc(hWnd, nMsg, wParam, lParam)
{ Critical
  Global   pcm, pcm2, pcm3, WPOld
  If (nMsg = 287) ; WM_MENUSELECT
  {  MenuItem := wParam & 65535
     VarSetCapacity(sHelp,257,79)
     DllCall(NumGet(NumGet(1*pcm)+20), "Uint", pcm, "Uint", MenuItem-3, "Uint", GCS_HELPTEXTA:=1, "Uint", 0, "str", sHelp, "Uint", 256) ; GetCommandString
     if sHelp = %SbText%
        MsgBox, Hey
  }
  If pcm3
  { If !DllCall(NumGet(NumGet(1*pcm3)+28), "Uint", pcm3, "Uint", nMsg, "Uint", wParam, "Uint", lParam, "UintP", lResult)
    Return lResult
  }
  Else If pcm2
  { If !DllCall(NumGet(NumGet(1*pcm2)+24), "Uint", pcm2, "Uint", nMsg, "Uint", wParam, "Uint", lParam)
      Return 0
  }
  Return   DllCall("user32.dll\CallWindowProcA", "Uint", WPOld, "Uint", hWnd, "Uint", nMsg, "Uint", wParam, "Uint", lParam,"Uint")
}

#Include CoHelper.ahk

It triggers a msgbox if the status bar text is found, but ultimately I'd like it to, Send {ENTER} or ControlClick to choose the menu item.

The Crawler script isn't ready yet (but that's not important yet) - it's just supposed to crawl through the context menu as quickly as ShellContextMenu allows. When it passes over the menu item we are trying to reach, Modified_ShellContextMenu is supposed to recognize the status bar text, and hit enter.

DetectHiddenWindows, on  ; Might allow detection of menu sooner.
WinWait, ahk_class #32768,,2
IfWinExist, ahk_class #32768
{   id := WinExist( ahk_class #32768 )
    ControlSend,,{Down 3}, ahk_id %id%
    loop, 100
    {   ControlSend,,{Down}, ahk_id %id%
        Sleep, 50
        Send, {Right}
        Sleep, 50
        Send, {Down}
        Sleep, 50
        ;Send, {Left}
        ;Sleep, 50
        IfWinNotExist, ahk_id %id%
            Break
    }
}
else
  msgbox, boo hoo
return

What do you think? If there's a slick way to do this then all the better, but right now I'm wondering if you see any obvious flaws in the Modified_ShellContextMenu, or whether I could just use the original, and instead have the Crawler script hit the menu with DllCalls to retrieve the status bar text? (the way I can use GetStatusBarText() to get it when at an explorer window)

FWIW, I've been using the following function for FileMenu Tools - it's okay most of the time because I have the target files selected in front of me in an active explorer window, but remote access would come in handy every now and then - actually it uses your ShellDesktop and SelectItem codes to get around the problem of no status bar at Desktop view:

#NoEnv

GetMenuItemBySBar(SBar_text, TopSubText="")
{ WinGetActiveTitle, Active_title
  if Active_title = Program Manager  ; switches to folder view if on desktop
    Goto, DesktopVer

  WinGet, WindowUniqueID, ID, A
  Send, {APPSKEY}
  loop, 70  ; number high enough to cover all menu items
  { SendInput {DOWN}
    StatusBarGetText, StatusBarText, 1, ahk_id %WindowUniqueID%
    IfInString, StatusBarText, %SBar_text%  ;if (StatusBarText = SBar_text)
    { SendInput {ENTER}
      break
    }
    else if (TopSubText != "TripleZero") and (StatusBarText = "") ; skips if no 2nd arg
    { SendInput {RIGHT}
      StatusBarGetText, StatusBarText, 1, ahk_id %WindowUniqueID%
      if (StatusBarText != TopSubText)
        SendInput {LEFT}
    }
    if (A_INDEX > 70)
      MsgBox Didn't find "%SBar_text%" nor no window with status bar available
  }
  return

  DesktopVer:
  sSelect := ShellDesktop()
  WinActivate, Desktop, FolderView
  SelectItem(sSelect)
  Sleep, 100
  Send, {APPSKEY}
  loop, 70  ; number high enough to cover all menu items
  { SendInput {DOWN}
    StatusBarGetText, StatusBarText, 1, Desktop, FolderView
    IfInString, StatusBarText, %SBar_text%
    { SendInput {ENTER}
      break
    }
    else if StatusBarText =
    { SendInput {RIGHT}
      StatusBarGetText, StatusBarText, 1, Desktop, FolderView
      if (StatusBarText != TopSubText)
        SendInput {LEFT}
    }
    if (A_INDEX > 70)
      MsgBox Didn't find "%SBar_text%" status bar text `nor no window with status bar available
  }
  WinClose, Desktop, , 2
  return
}

Hardware: fast laptop with SSD
Software: Win 7 Home Premium 64-bit, android for phone and tablet


Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
How are you using it? The script was written as the one-time job script. If you'd like to include it in a resident script and call the function repeatedly, there are a lot of design things to improve/consider.

First move out CoInitialize/CoUninitialize of the functions and call them only once at the start-up/exit stage of the resident script.
Second, I subclassed the main window of AHK to handle the menu message, however, it's not a good idea to do in a resident script. Better create your own window of the special purpose of handling menu message, maybe a message-only window is the ideal.
And you have to be always sure that this window is the foreground window before bring up the menu, otherwise the menu will be irresponsive to mouse/keyboard inputs. But you have to be prepared to handle the occasions to fail to bring the window up to the foreground.
Third, better avoid using global variables.
etc etc

Although certainly you can do those, however, you have to do them yourself. I'll not help for those, as that is a complete rewrite but I decided to not maintain the script. Anyway I recommend doing those, but if you feel the job rather involved, then I suggest to call it as an external script.

otlaolap
  • Members
  • 43 posts
  • Last active: Sep 12 2013 02:33 AM
  • Joined: 15 Aug 2007

First move out CoInitialize/CoUninitialize of the functions and call them only once at the start-up/exit stage of the resident script.

For a few months I've used your script successfully. It is modified to accept as an argument the name of the folder for menu popping (obviously). It is compiled, and has #Persistent and #SingleInstance Force specified in it. If I understand how these work correctly, then a single copy of the executable is loaded and thereafter that copy runs repeatedly as other programs call on it via Shell Execute or Run or other normal Windows mechanisms. Presumably it retains its memory between executions.

As it stands, the CoInitialize and CoUninitialize are performed for each menu popping. You suggest migrating these outward. So, I could put CoInitialize under control of a one-time switch in the script and execute it only on the first invocation of the compiled script module. Similarly, I could put CoUninitialize under a label triggered by an OnExit command (run when the script is terminated for any reason).

But I know COM not a bit, and OnExit runs in a new thread. Will this be OK? Is this CoInitialize-CoUninitialize a thread-specific thing or is it a program-specific thing? Possibly all the other by Shell Execute or Run and so on run in different threads, as I call on the menu popper over time.

Once again, this is a remarkably useful script you have made.

[EDIT] Turns out I'm wrong. Persistent programs start afresh on each Run or Shell Execute or whatever. It is programs that get started through a Windows hotkey that get brought forward -- not restarted -- when that same hotkey is used. So, since the menu popper is run afresh (in my usage) for each popup, I'll just leave CoInitialize and CoUninitialize where they are.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

But I know COM not a bit, and OnExit runs in a new thread. Will this be OK? Is this CoInitialize-CoUninitialize a thread-specific thing or is it a program-specific thing? Possibly all the other by Shell Execute or Run and so on run in different threads, as I call on the menu popper over time.

Thread, but you don't have to worry about it. All AHK threads are pseudo-threads.
<!-- m -->http://www.autohotke...isc/Threads.htm<!-- m -->

Once again, this is a remarkably useful script you have made.

Just in case, I decided to not maintain the script not because I don't think it's useful, but because I want to relieve some burden of maintenance.

Eucaly61
  • Members
  • 18 posts
  • Last active: Feb 03 2010 10:07 AM
  • Joined: 16 May 2009
Is it possible to get shellcontextmenu under certain CLSID?

When I righ click on desktop, such contextmenu (to access intel graphic setting) will be inclueded.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\CLSID\{280A8F40-E382-11D2-B561-00A0C92E6848}]
@="Software\\Intel\\Display\\igfxcui"

[HKEY_CLASSES_ROOT\CLSID\{280A8F40-E382-11D2-B561-00A0C92E6848}\shellex]

[HKEY_CLASSES_ROOT\CLSID\{280A8F40-E382-11D2-B561-00A0C92E6848}\shellex\PropertyPageHandlers]

[HKEY_CLASSES_ROOT\CLSID\{280A8F40-E382-11D2-B561-00A0C92E6848}\shellex\PropertyPageHandlers\igfxcfg]

[HKEY_CLASSES_ROOT\CLSID\{280A8F40-E382-11D2-B561-00A0C92E6848}\shellex\PropertyPageHandlers\igfxcfg\diagHandler]
@="{3AB167A5-CCFF-11D2-8B20-00A0C93CB1F4}"

But I've tried sPat as CSIDL (0x00 or 0x01) and C:\Documents and Settings\{user name}\{desktop} with Sean's ShellContextMenu.ahk ... neither one does not contain that section in contextmenu ...

also tried to replace pcm3's CLSID as quote above ({280... or {3AB...) , no helps ...

pcm3 := QueryInterface(pcm,IID_IContextMenu3:="{BCFCE0A0-EC17-11D0-8D10-00A0C90F2719}")

CSIDL_DESKTOP = &H0
   CSIDL_DESKTOPDIRECTORY = &H10

Eucaly61's DIY World (Mainly in Chinese)
http://eucaly61.blog...abel/AutoHotKey

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

Is it possible to get shellcontextmenu under certain CLSID? When I righ click on desktop, such contextmenu (to access intel graphic setting) will be inclueded.

FYI, those contextmenu items reside under the key:
HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\
I'm not sure of what you want to do, however, you may try to copy one of them under this key to appropriate ContextMenuHandlers key. There is a way to bring up the menu, but I leave it for you to figure out as I no longer maintain the script.

Eucaly61
  • Members
  • 18 posts
  • Last active: Feb 03 2010 10:07 AM
  • Joined: 16 May 2009

those contextmenu items reside under the key:

HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\


Sean:

You're right, such item is already there.

There is a way to bring up the menu (Background\shellex)


Could you direct me to some reference material (either AHK or non-AHK would be OK). I will try to figure out by myself. To bring up the Background\shellex menu ....
Eucaly61's DIY World (Mainly in Chinese)
http://eucaly61.blog...abel/AutoHotKey

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
Try after replacing the red marked lines in the script
CoInitialize()
; [color=red]lines to be replaced[/color]
Release(psf)
CoTaskMemFree(pidl)
with
DllCall("shell32\SHGetDesktopFolder", "UintP", psf)
DllCall(NumGet(NumGet(1*psf)+32), "Uint", psf, "Uint", 0, "Uint", GUID4String(IID_IContextMenu,"{000214E4-0000-0000-C000-000000000046}"), "UintP", pcm)


Eucaly61
  • Members
  • 18 posts
  • Last active: Feb 03 2010 10:07 AM
  • Joined: 16 May 2009

Try after replacing ....
with

DllCall("shell32\SHGetDesktopFolder", "UintP", psf)
DllCall(NumGet(NumGet(1*psf)+32), "Uint", psf, "Uint", 0, "Uint", GUID4String(IID_IContextMenu,"{000214E4-0000-0000-C000-000000000046}"), "UintP", pcm)


Dear Sean:

I already try above, but no amazing result as I expected

...... but after a long-long try, I made it success by below

* DllGetClassObject with IID_IClassFactory
* pcm := CreateInstance with "{000214E4-0000-0000-C000-000000000046}"	; IContextMenu
* hMenu := DllCall("CreatePopupMenu")
* DllCall(NumGet(NumGet(1*pcm)+12), "Uint", pcm, "Uint", hMenu, "Uint", 0, "Uint", 3, "Uint", 0x7FFF, "Uint", 0)   ; QueryContextMenu

* Loop, % DllCall( "GetMenuItemCount", UInt,hMenu ) { ....
;    to get menu items [color=red]without bring up popup menu[/color]

* invoke some menu items ....
Although the code is functional OK, but is too draft (&messy) to share ....
I should come back here after a clean-up
Eucaly61's DIY World (Mainly in Chinese)
http://eucaly61.blog...abel/AutoHotKey

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

I already try above, but no amazing result as I expected

What you meant by already tried? Notice that the function I used here is not the same one in the script: 32 vs 40.

...... but after a long-long try, I made it success by below

* DllGetClassObject with IID_IClassFactory
* pcm := CreateInstance with "{000214E4-0000-0000-C000-000000000046}"	; IContextMenu

Really? It's a surprise to me. What CLSID did you use?

Eucaly61
  • Members
  • 18 posts
  • Last active: Feb 03 2010 10:07 AM
  • Joined: 16 May 2009

the function I used here is not the same one in the script: 32 vs 40

Yes, you are right, func+32 does bring up intel graphic driver context menu !!

By the way, where can I found reference information about the meaning of +32, +40 and etc ...

Really? It's a surprise to me. What CLSID did you use?

below are some DLL & CLSID combination that has valid menu after QueryContextMenu

all under sIId := "{000214E4-0000-0000-C000-000000000046}" ; IContextMenu

sDll := "igfxpph.dll"
sClsId := "{3AB1675A-CCFF-11D2-8B20-00A0C93CB1F4}"	; igfx Shellext

sDll := "wmpshell.dll"
sClsId := "{F1B9284F-E9DC-4e68-9D7E-42362A59F0FD}"	; add to playlist

sDll := "shdocvw.dll"
sClsId := "{2559a1f0-21d7-11d4-bdaf-00c04f60b9f0}"	; findfiles

sDll := "shdocvw.dll"
sClsId := "{2559a1f1-21d7-11d4-bdaf-00c04f60b9f0}"	; help

sDll := "SHELL32.dll"
sClsId := "{D969A300-E7FF-11d0-A93B-00A0C90F2719}"	; New ...
; => not able to explore 2nd level, maybe it's context sensitive

Eucaly61's DIY World (Mainly in Chinese)
http://eucaly61.blog...abel/AutoHotKey

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

By the way, where can I found reference information about the meaning of +32, +40 and etc ...

You have to refer to header files for that. In this case of IShellFolder, for example, the infos are contained in ShObjIdl.h and/or ShObjIdl.idl.

below are some DLL & CLSID combination that has valid menu after QueryContextMenu

I see. You called directly the ContextMenu Handlers. Very interesting! Did it work even though you did not initialize the handlers? I mean, it seemd that you didn't call Initialize of IShellExtInit. So, looks like the ContextMenu Handlers need not be bound to a shell item. Intriguing...

BTW, I can see some are registered and some aren't in the registry, but when it's registered in the registry I suppose you don't have to call directly DllGetClassObject, just can create using CreateObject like:
pcm := COM_CreateObject("{3AB1675A-CCFF-11D2-8B20-00A0C93CB1F4}", "{000214E4-0000-0000-C000-000000000046}")