Jump to content

Can I extend a content sensitive menu?


Recommended Posts

Hello Forum,
The best way to utilise one of the autoit scripts I am about to write would be to add one or two extra options to the content sensitive menu that shows up when you right-click something. What I did before was to create suitable menu items for the toolbar icon of my autoit application. This time however, I would prefer not to always move my mouse to the bottom of the screen to click the toolbar icon and bring up the available menu items. I would prefer to just right-click something on my screen, and see my autoit option in that list.

Is this something doable with autoit? Can I add items to the right-click menu?
Pointers or suggestions are appreciated!

Regards,
Keve

Link to post
Share on other sites

Checking for a particular screen location may not be an efficient solution. The is no way to know in advance, where the object(s) I am interested in would show up on the screen, so there is no reference I could compare coordinates to. But the rest of your suggestion is promising. If I find no way to inject my menu items into the standard right-click menu, I can create my own little GUI menu, trigger it for a specific input combination (say Ctrl+Alt+RightClick) and display it at the current location of the mouse pointer. Regardless of what screen object is at that mouse pointer location at the time.

I still hope somebody will point me towards extending the right-click menu. But if that does not happen, this is a good reserve-solution. Thanks!

Link to post
Share on other sites

Lars, I believe you gave me exactly what I was after. I will play around with your example code.

Mikell, that is an option I did not consider. Thanks for pointing it out! In general, I prefer not to mess around in the registry, hence such a solution eluded my thoughts.

Link to post
Share on other sites
14 hours ago, LarsJ said:

It's not that hard.

for you to say lol, this is neat, thanks

@Keve just mashed up this to call some funcs concept, let me know what else you came up with. btw, so far it doesnt work on chrome, vlc

_context_menu_items_added.png

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7
#AutoIt3Wrapper_UseX64=y

; ----------------------------------------------------------
; concept:  Right-Click anywhere with popup Context Menu
;           it it known that not all context menus are
;           detectable and able to add new menu item.
;           (e.g chrome, vlc and perhaps other apps)
; ----------------------------------------------------------

#include <Misc.au3>
#include <GuiMenu.au3>
#include <WindowsConstants.au3>
#include <array.au3>

Opt( "MustDeclareVars", 1 )

HotKeySet('{F1}', '_Exit')
Func _Exit()
  Exit
EndFunc


Global $hMenu
Global $sItemNames = "Added_xNew|Added_xOpen|Added_xCopy|Added_xPaste|Added_xSelect|Added_xClose"
Global $aItemNames = StringSplit($sItemNames, '|', 2)

Func Added_xNew($str)
  MsgBox(0, @ScriptLineNumber&': Added_xNew', $str,1)
  MsgBox(0, @ScriptLineNumber&': Added_xNew', $str)
EndFunc
Func Added_xOpen($str)
  MsgBox(0, @ScriptLineNumber&': Added_xOpen', $str,1)
  MsgBox(0, @ScriptLineNumber&': Added_xOpen', $str)
EndFunc
Func Added_xCopy($str)
  MsgBox(0, @ScriptLineNumber&': Added_xCopy', $str,1)
  MsgBox(0, @ScriptLineNumber&': Added_xCopy', $str)
EndFunc
Func Added_xPaste($str)
  MsgBox(0, @ScriptLineNumber&': Added_xPaste', $str,1)
  MsgBox(0, @ScriptLineNumber&': Added_xPaste', $str)
EndFunc
Func Added_xSelect($str)
  MsgBox(0, @ScriptLineNumber&': Added_xSelect', $str,1)
  MsgBox(0, @ScriptLineNumber&': Added_xSelect', $str)
EndFunc
Func Added_xClose($str)
  MsgBox(0, @ScriptLineNumber&': Added_xSelect', $str,1)
  MsgBox(0, @ScriptLineNumber&': Added_xSelect', $str)
EndFunc

  
MsgBox(0, @ScriptLineNumber&': Ready?', 'Right Click anywhere with a Popup Context Menu')
While 1
    ; wait for Right-Click to occur
    While Sleep(10) And Not _IsPressed( "02", "user32.dll" )    ;01=primary, 02=secondary mouse button
    WEnd
    _popup_contextMenuDetectionAndSelection()
WEnd


; concept to call func based on newly added menu item selected
Func _popup_contextMenuDetectionAndSelection()
    Local $timeout = 3
    Local $init
    Local $iIsItem
    Local $iCount
    Local $jCount
    Local $iItem
    Local $jItem
    Local $sFuncName
    Local $aPos

    ConsoleWrite('!>!'&@ScriptLineNumber&': 1st Right-Clicked - Context Menu Pop-Up'&''&@CRLF) ;Read/Brown
    $aPos = MouseGetPos()
    ToolTip('Wait '&$timeout&'secs for New Context Menus to populate)', $aPos[0]-3, $aPos[1]-30)
    
    ; $timeout seconds to locate popup context menu existence
    $init = TimerInit()
    While TimerDiff($init) < ($timeout * 1000)
        $hMenu = GetMenuHandle()
        If $hMenu Then ExitLoop
        sleep(10)
    WEnd
    
    ConsoleWrite('+>+'&@ScriptLineNumber&': $hMenu - '&$hMenu&''&@CRLF) ;Green/Grey
  
    If Not $hMenu Then
        ToolTip('')
        Send('{ESC}')
        Local $sMsg = 'Note: doesnt work in Chrome/VLC'&@CRLF&@CRLF& _
                      'Press Ok then Right-Click to try again'
        MsgBox(0, @ScriptLineNumber&': Error', $sMsg, 1)
        MsgBox(0, @ScriptLineNumber&': Error', $sMsg, 1)
        MsgBox(0, @ScriptLineNumber&': Error', $sMsg, 1)
        MsgBox(0, @ScriptLineNumber&': Error', $sMsg, 1)
    Else
        ; $timeout seconds to get inital menu items
        $init = TimerInit()
        While TimerDiff($init) < ($timeout * 1000)
            $iCount = _GUICtrlMenu_GetItemCount($hMenu)
            If $iCount Then ExitLoop
            sleep(10)
        WEnd
    
        ConsoleWrite('-->'&@ScriptLineNumber&': $iCount '&$iCount&''&@CRLF) ;Yellow/Orange
        
        $iIsItem = _AddMenuItem_toRightClickContextMenu()
        ConsoleWrite('-->'&@ScriptLineNumber&': $iIsItem '&$iIsItem&''&@CRLF) ;Yellow/Orange
         
        If Not $iIsItem Then 
            ToolTip('>>> Fails to add NEW Menu - Click anywhere to continue then Right-Click to try again <<<', $aPos[0]-3, $aPos[1]-30)
        Else 
            ToolTip('>>> Now select a NEW Menu below if every added correctly <<<', $aPos[0]-3, $aPos[1]-30)
            
            ; waiting for Left-Click (click a menu item)
            While Sleep(10) And Not _IsPressed( "01", "user32.dll" )    ;01=primary, 02=secondary mouse button
            WEnd
            ConsoleWrite('!>!'&@ScriptLineNumber&': 2nd Left-Clicked - Item Selected'&''&@CRLF) ;Read/Brown
            ToolTip('')

            ; $timeout seconds get total menu items (old + newly added)
            $init = TimerInit()
            While TimerDiff($init) < ($timeout * 1000)
                $jCount = _GUICtrlMenu_GetItemCount($hMenu)
                If $jCount Then ExitLoop
                sleep(10)
            WEnd
            
            ConsoleWrite('!>!'&@ScriptLineNumber&': $jCount - '&$jCount&''&@CRLF) ;Read/Brown
            
            If $jCount > 0 Then 
                $iItem = _GUICtrlMenu_MenuItemFromPointEx( 0, $hMenu )
                $jItem = $jCount - $iItem - 1
                
                ConsoleWrite('-->'&@ScriptLineNumber&': $jItem '&$jItem&' = '&$jCount& ' - '& $iItem &@CRLF) ;Yellow/Orange
                
                ; only detect newly added menu items
                If $jItem <= UBound($aItemNames)-1 Then
                    _ArrayReverse($aItemNames)
                    $sFuncName = $aItemNames[$jCount - $iItem - 1]
                    Call($sFuncName, '(init: '&$iCount-1& ') + (new: '& UBound($aItemNames) &') + (dividers: 2) = [totalMenus: '& $jCount-1 &']' &@CRLF&@CRLF& _
                                'you selected item #'&$iItem &'/'& $jCount-1 &@CRLF&@CRLF& _ 
                                'which executes func: ' &$sFuncName & '() that clicked on'&@CRLF&@CRLF& _ 
                                'clicked: '& $jItem&' <= '& UBound($aItemNames)-1 &' :total custom menus')
                    _ArrayReverse($aItemNames)
                EndIf
            Else
                MsgBox(0, @ScriptLineNumber&': Fails', 'No menu item was Clicked', 1)
                MsgBox(0, @ScriptLineNumber&': Fails', 'No menu item was Clicked', 1)
            EndIf
        EndIf
    
    EndIf
EndFunc

Func _AddMenuItem_toRightClickContextMenu()
    If Not $hMenu Then Return False
    Local $iItemNot = True
    If $hMenu Then
        ; Add new menu item
        Local $iItem
        $iItem = _GUICtrlMenu_AddMenuItem( $hMenu, '') ;add new divider
        If Not $iItem Then $iItemNot = False
        $iItem = _GUICtrlMenu_AddMenuItem( $hMenu, '') ;add new divider
        If Not $iItem Then $iItemNot = False
        
        For $i=0 To UBound($aItemNames)-1
            $iItem = _GUICtrlMenu_AddMenuItem( $hMenu, StringReplace($aItemNames[$i], '_', ' '))
            If Not $iItem Then $iItemNot = False
        Next
    EndIf
    Return $iItemNot
EndFunc

Func GetMenuHandle()
  Local $hDesktop = _WinAPI_GetDesktopWindow(), $i = 0
  Local $hChild = _WinAPI_GetWindow( $hDesktop, $GW_CHILD )
  While $hChild And $i < 50
    If _WinAPI_GetClassName( $hChild ) = "#32768" Then ExitLoop
    $hChild = _WinAPI_GetWindow( $hChild, $GW_HWNDNEXT )
    $i += 1
  WEnd
  If $i = 50 Then Return 0
  Local $hMenu = _SendMessage( $hChild, $MN_GETHMENU )
  If $hMenu > 0 Then Return $hMenu
  Return 0
EndFunc

Func _GUICtrlMenu_MenuItemFromPointEx( $hWnd, $hMenu, $iX = -1, $iY = -1 )
  If $iX = -1 Then $iX = _WinAPI_GetMousePosX()
  If $iY = -1 Then $iY = _WinAPI_GetMousePosY()
  Local $tPOINT = DllStructCreate( "long;long" )
  DllStructSetData( $tPOINT, 1, $iX )
  DllStructSetData( $tPOINT, 2, $iY )
  Local $tPOINT64 = DllStructCreate( "int64", DllStructGetPtr( $tPOINT ) )
  Local $aResult = DllCall("user32.dll", "int", "MenuItemFromPoint", "hwnd", $hWnd, "handle", $hMenu, "int64", DllStructGetData( $tPOINT64, 1 ) )
  If @error Then Return SetError(@error, @extended, -1)
  Return $aResult[0]
EndFunc

 

here is an anomaly, right-click on SciTE title bar resulted in retaining added menu items. even after exited the code

 

menu_items_added_permanently_on_title_bar_contexmenu.png

Edited by zeenmakr
updated w/anomaly
Link to post
Share on other sites
Link to post
Share on other sites
5 hours ago, LarsJ said:

I had no errors.

it narrowed down to here, although class '#32768 is found' sometimes menu handle returns other time not, see screenshot

;
;...
;

While 1
    ; wait for Right-Click to occur
    _MouseWaitPressed(02)
    _MouseWaitReleased(02)
    
    $hMenu = GetMenuHandle()
    Global $aPos = MouseGetPos()
    ToolTip($hMenu > 0 ? 'OK - '&$hMenu : 'ERROR - '&$hMenu, $aPos[0]-3, $aPos[1]-30)
    Local $sPrefix = @ScriptLineNumber&': $hMenu = '&$hMenu&' - '
    ConsoleWrite($hMenu > 0 ? '>->'& $sPrefix &' OK' &@CRLF : '!>!'& $sPrefix &' ERROR' &@CRLF) ;Red/Brown
  
;~     _popup_contextMenuDetectionAndSelection()
WEnd


; wait for key pressed
Func _MouseWaitPressed($btn=01)
    ;01=primary, 02=secondary mouse button
    While Sleep(10) And Not _IsPressed($btn, "user32.dll" )
    WEnd
EndFunc
; wait for key to depress
Func _MouseWaitReleased($btn=01)
    ;01=primary, 02=secondary mouse button
    While Sleep(250) And _IsPressed($btn, "user32.dll")
    WEnd
EndFunc
 ;_EvenOrOdd
Func _IsEven($n)
    If NOT Mod($n ,2) Then
        Return 1    ;even
    Else
        Return 0    ;odd
    EndIf
EndFunc

;
;...
;

Func GetMenuHandle()
  ConsoleWrite('!>!'&@ScriptLineNumber&':'&' ------------------------------------ '&@CRLF) ;Read/Brown
  
  Local $hDesktop = _WinAPI_GetDesktopWindow(), $i = 0
  Local $hChild = _WinAPI_GetWindow( $hDesktop, $GW_CHILD )
  While $hChild And $i < 100
    
  Local $hMenuTmp = _SendMessage( $hChild, $MN_GETHMENU )
  
    Local $clChild = _WinAPI_GetClassName( $hChild )
    ;debuging
    If _IsEven($i) Then 
        ConsoleWrite('>->'&@ScriptLineNumber&': 3-'&$i&' | parent - '&$hDesktop& _
                      ' | $hChild - '&$hChild&' = '&$clChild& _
                      ' <---$hMenuTmp ['&$hMenuTmp&']'& @CRLF) ;Blue/Cyan
    Else
        ConsoleWrite('-->'&@ScriptLineNumber&': 3-'&$i&' | parent - '&$hDesktop& _
                      ' | $hChild - '&$hChild&' = '&$clChild& _
                      ' <---$hMenuTmp ['&$hMenuTmp&']'& @CRLF) ;Yellow/Orange
    EndIf

    ; is context menu?
    If _WinAPI_GetClassName( $hChild ) = "#32768" Then ExitLoop
    $hChild = _WinAPI_GetWindow( $hChild, $GW_HWNDNEXT )
    $i += 1
  WEnd
  If $i = 100 Then Return 0
  
  ; $timeout seconds - get $hMenu
  Local $timeout = 3
  Local $init = TimerInit()
  While TimerDiff($init) < ($timeout * 1000)
      Local $hMenu = _SendMessage( $hChild, $MN_GETHMENU )  ; <--- found menu class #32768 but couldn retrieve the handle
      If $hMenu > 0 Then ExitLoop
  WEnd
  
  Local $sPrefix = @ScriptLineNumber&': 4-'&$i&' | '&_WinAPI_GetClassName( $hChild )&' = '& $hMenu
  ConsoleWrite($hMenu > 0 ? '   '& $sPrefix &' <<<=== WINNER ===' &@CRLF : '!>!'& $sPrefix &' <<<=== NO CONTEXT MENU ===' &@CRLF) ;Red/Brown
  
  If $hMenu > 0 Then Return $hMenu
  Return 0
EndFunc

 

contextmenu_debug.png

Edited by zeenmakr
Link to post
Share on other sites

You can try if you can identify the menus with UI Automation code in Menus.7z in this post. The 7z file contains all necessary code. Run Example.au3 in SciTE with F5.

Link to post
Share on other sites
  • 3 weeks later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...