Posts: 1,239
Threads: 286
Joined: Jul 2022
08-17-2026, 11:11 PM
(This post was last modified: 08-18-2026, 04:43 AM by Davider.)
Previously I used a large number of "Shell menu" triggers. At first it was very convenient, but as the number grew, the following problems arose:
1. QM portable version does not support it;
2. Editing trigger functions is inconvenient;
3. One function corresponds to only one shell menu;
4. It can only be integrated into a certain position of the existing system menu, which is not intuitive enough, and the position of the menu items is inconvenient to modify.
5. And many more...
To solve the above problems, I wanted to implement in QM functionality similar to the LA trigger in the post below. I wrote the following code, but I ran into some problems:
Context Menu Trigger
1. How do I determine the currently selected file(s)? (I have seen similar code on the forum, but I haven't found it yet);
2. Right-clicking on a menu item does not bring up "Edit menu item" (I have seen it pop up on some menu items);
I found the source code of the ExplorerFolder function in LA. Is there a similar function in QM? I tried using AI to convert the code to QM, but had no luck.
https://github.com/qgindi/LibreAutomate/...rFolder.cs
How convert the following Get-ExplorerSelectedItems PowerShell function into QM code?
Thanks in advance for any suggestions and help.
PS: I also thought about using LA to re-implement those QM features, but there are too many...
Function Explorer_Menu
Trigger !v"" "#32768" /EXPLORER
int hwnd=TriggerWindow
out
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
;1.When one or more docx files are selected, The menu pops up
sub.docx
;2.When one or more txt files are selected, The menu pops up
;sub.txt
#sub docx
str docx=
;1 docx Action1
;-
;2 docx Action2
_i=ShowMenu(docx 0 0 0 8)
out _i
#sub txt
str txt=
;1 txt Action1
;-
;2 txt Action2
_i=ShowMenu(txt 0 0 0 8)
out _i
Powershell Get-ExplorerSelectedItems Code:
function Get-ExplorerSelectedItems {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
[int]$Hwnd = 0,
[switch]$OnlyFilesystem
)
$shell = New-Object -ComObject Shell.Application
$windows = $shell.Windows()
$results = [System.Collections.Generic.List[string]]::new()
foreach ($win in $windows) {
$winHwnd = [int]$win.HWND
if ($Hwnd -ne 0 -and $winHwnd -ne $Hwnd) { continue }
$doc = $win.Document
if ($null -eq $doc) { continue }
$selected = $doc.SelectedItems()
if ($null -eq $selected) { continue }
for ($i = 0; $i -lt $selected.Count; $i++) {
$item = $selected.Item($i)
if ($null -eq $item) { continue }
$path = $item.Path
if ([string]::IsNullOrEmpty($path)) { continue }
if ($OnlyFilesystem -and $path -notmatch '^[a-zA-Z]:[\\/]') {
continue
}
$results.Add($path)
}
}
return $results
}
Get-ExplorerSelectedItems -Hwnd 12345
Below is the QM code generated by ChatGPT, and it will cause an error.
Function Get_ExplorerSelectedItems
Trigger F1
out
int h=win
ARRAY(str) r
sub.Get_ExplorerSelectedItems r h
out r
#sub Get_ExplorerSelectedItems
function ARRAY(str)&results [int'Hwnd] [flags]
,if(getopt(nargs)=1) Hwnd=0
,Shell32.IShellDispatch5 sh._create(uuidof(Shell32.Shell))
,IShellWindows windows=sh.Windows
,int count; windows.get_Count(&count)
,int i
,for i 0 count
,,Shell32.IWebBrowserApp w=windows.Item(i)
,,if(!w) continue
,,int winHwnd=w.HWND
,,if(Hwnd and winHwnd!=Hwnd) continue
,,Shell32.IShellFolderViewDual2 doc=w.Document
,,if(!doc) continue
,,Shell32.FolderItems selected=doc.SelectedItems()
,,if(!selected) continue
,,int j
,,for j 0 selected.Count
,,,Shell32.FolderItem item=selected.Item(j)
,,,if(!item) continue
,,,str path=item.Path
,,,if(empty(path)) continue
,,,;;;flags 1 = OnlyFilesystem
,,,if(flags&1 and !matchw(path "[a-zA-Z]:\\*" 1)) continue
,,,results[] = F"{winHwnd}`t{path}"
ret
Posts: 12,300
Threads: 144
Joined: Dec 2002
Found this in Archive.
Macro GetPathsOfSelectedFiles
;Displays full paths of selected files and folders in Windows Explorer.
;find Windows Explorer window
int hwnd=win("" "CabinetWClass")
if(!hwnd) end "folder window not found"
;Get ShellBrowserWindow interface by enumerating shell windows
SHDocVw.ShellWindows sw._create
SHDocVw.ShellBrowserWindow b
foreach(b sw)
,int hwnd2=b.HWND; err continue
,if(hwnd=hwnd2) goto g1
ret
;g1
;get shell folder view document
Shell32.ShellFolderView sfw=b.Document
Shell32.FolderItem fi
;enumerate selected items
foreach fi sfw.SelectedItems
,out fi.Path
But it does not support multiple tabs in Explorer. May use wrong tab. The LA class supports it.
---
ShowMenu does not support "Edit menu item".
---
See also: Nilesoft Shell. Modify Explorer context menu
Posts: 1,239
Threads: 286
Joined: Jul 2022
Posts: 1,239
Threads: 286
Joined: Jul 2022
08-19-2026, 02:32 AM
(This post was last modified: 08-19-2026, 03:49 AM by Davider.)
How close the trigger menu after clicking an item in the main menu?
Function Explorer_Menu0
Trigger !v"" "#32768" /EXPLORER
int hwnd=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
int h=win
str ext f fs=sub.GetPathsOfSelectedFiles(h)
str docxFs; int hasDocx
foreach f fs
,ext.GetFilenameExt(f)
,if(ext="docx") hasDocx=1; docxFs.addline(f)
if hasDocx
,sub.docx(docxFs)
#sub docx
function str'docxFs
_s=
;1 open docx
_i=ShowMenu(_s 0 0 0 8)
sel _i
,case 1
,foreach _s docxFs
,,run _s
#sub GetPathsOfSelectedFiles
function~ int'hwnd
if(!hwnd) end "folder window not found"
SHDocVw.ShellWindows sw._create
SHDocVw.ShellBrowserWindow b
foreach(b sw)
,int hwnd2=b.HWND; err continue
,if(hwnd=hwnd2) goto g1
ret
;g1
Shell32.ShellFolderView sfw=b.Document ;;get shell folder view document
Shell32.FolderItem fi
str ss
foreach fi sfw.SelectedItems
,ss.addline(fi.Path 1)
ret ss
Posts: 12,300
Threads: 144
Joined: Dec 2002
Macro Macro3682
Trigger !v"" "#32768" /EXPLORER
int hwnd=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
int timer=SetTimer(0 0 100 &sub._Timer)
_s=
;1 open docx
_i=ShowMenu(_s 0 0 0 8)
#sub _Timer v
function g h j k
if !IsWindowVisible(a[0])
,KillTimer 0 timer
,EndMenu
Posts: 1,239
Threads: 286
Joined: Jul 2022
Posts: 1,239
Threads: 286
Joined: Jul 2022
08-19-2026, 05:19 AM
(This post was last modified: 08-19-2026, 05:21 AM by Davider.)
After adding the timer code, the trigger menu items can no longer be executed.
Test: Execute "Copy" in the main menu, then execute the menu item in the trigger menu.
Function AAA
Trigger !v"" "#32768" /EXPLORER
int hwnd=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
int timer=SetTimer(0 0 100 &sub._Timer)
int h=win
str ext f fs=sub.GetPathsOfSelectedFiles(h)
str docxFs; int hasDocx
foreach f fs
,ext.GetFilenameExt(f)
,if(ext="docx") hasDocx=1; docxFs.addline(f)
if hasDocx
,sub.docx(docxFs)
#sub docx
function str'docxFs
_s=
;1 open docx
_i=ShowMenu(_s 0 0 0 8)
sel _i
,case 1
,foreach _s docxFs
,,run _s
#sub GetPathsOfSelectedFiles
function~ int'hwnd
if(!hwnd) end "folder window not found"
SHDocVw.ShellWindows sw._create
SHDocVw.ShellBrowserWindow b
foreach(b sw)
,int hwnd2=b.HWND; err continue
,if(hwnd=hwnd2) goto g1
ret
;g1
Shell32.ShellFolderView sfw=b.Document ;;get shell folder view document
Shell32.FolderItem fi
str ss
foreach fi sfw.SelectedItems
,ss.addline(fi.Path 1)
ret ss
#sub _Timer v
function g h j k
if !IsWindowVisible(a[0])
,KillTimer 0 timer
,EndMenu
Posts: 12,300
Threads: 144
Joined: Dec 2002
Macro Macro3682
Trigger !v"" "#32768" /EXPLORER
int hwnd=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
SetTimer(0 0 100 &sub._Timer)
;
_s=
;1 open docx
;2 open other
_i=ShowMenu(_s 0 0 0 8)
out _i
#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu
Posts: 1,239
Threads: 286
Joined: Jul 2022
08-19-2026, 11:43 PM
(This post was last modified: 08-20-2026, 09:35 AM by Davider.)
Thanks again for your help!
I have now implemented almost all the functionality of the "shell menu" trigger.
There is a small issue: when the menu is displayed near a corner of the desktop, it overlaps with the main menu (as shown in the image below).
In LA, I used the code `"m.Show(PMFlags.AlignRight, excludeRect: wMenu.Rect, owner: wMenu);"` to control this effectively.
https://github.com/qgindi/LibreAutomate/...pupMenu.cs
Is there a more concise and effective way to achieve this in QM?
Posts: 12,300
Threads: 144
Joined: Dec 2002
Not exactly `excludeRect`, but this can be solved with `ShowMenu` parameters `where` + `tpmFlags`. Get shell menu window rectangle with `GetWindowRect`.
Posts: 1,239
Threads: 286
Joined: Jul 2022
08-20-2026, 11:48 AM
(This post was last modified: 08-20-2026, 11:48 AM by Davider.)
The following is code generated using DeepSeek, which solves the issue of overlapping between the custom menu and the system context menu.
However, when used at the bottom-left or bottom-right corner of the desktop, the custom menu always appears at the top-right or top-left corner of the system menu, leaving the mouse pointer a bit far from the custom menu.
Is there a more concise way to implement this?
Macro Macro51
Trigger !v"" "#32768" /EXPLORER
int hwnd=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
SetTimer(0 0 100 &sub._Timer)
;
_s=
;1 open docx
;2 open other
_i=sub.ShowMenuSmart(hwnd _s)
out _i
#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu
#sub CalcMenuWidth
function# str'menu
;; Estimate menu width (pixels), correctly calculate character display width
if(empty(menu)) ret 0
int maxDisplayWidth=0
str line
int i
foreach line menu
,int displayWidth=0
,int byteLen=line.len
,i=0
,rep
,,if(i>=byteLen) break
,,byte b=line[i]
,,if(b < 0x80) ;; ASCII character
,,,displayWidth=displayWidth+1
,,,i=i+1
,,else if(b >= 0xC0 and b < 0xE0) ;; 2-byte UTF-8 leading byte
,,,displayWidth=displayWidth+2
,,,i=i+2
,,else if(b >= 0xE0 and b < 0xF0) ;; 3-byte UTF-8 leading byte (Chinese, etc.)
,,,displayWidth=displayWidth+2
,,,i=i+3
,,else if(b >= 0xF0 and b < 0xF8) ;; 4-byte UTF-8
,,,displayWidth=displayWidth+2
,,,i=i+4
,,else
,,,;; Other cases, treat as 1
,,,displayWidth=displayWidth+1
,,,i=i+1
,if(displayWidth > maxDisplayWidth) maxDisplayWidth=displayWidth
;; Each unit width is about 8 pixels, plus left/right margin about 40
ret maxDisplayWidth*8+40
#sub NeedAvoidSystemMenu
function# int'hwnd str'menu
;; Check if system menu is too close to left/right screen edges and needs to be avoided
if(!hwnd) ret 0
if(empty(menu)) ret 0
RECT r
if(!GetWindowRect(hwnd &r)) ret 0
int desktop=GetDesktopWindow
int x y w h
if(!GetWinXY(desktop x y w h)) ret 0
int menuWidth=sub.CalcMenuWidth(menu)
if(menuWidth=0) menuWidth=300
;; System menu left edge too left, or right edge too right, and remaining space insufficient for custom menu width
if(r.left < menuWidth or w - r.right < menuWidth) ret 1
ret 0
#sub CalcMenuPos
function# int'hwnd str'menu POINT&p int&tpmFlags
;; Calculate menu popup position to avoid covering the system menu
;; Return 1 success, 0 failure
if(!hwnd) ret 0
if(empty(menu)) ret 0
RECT sysMenuRect
if(!GetWindowRect(hwnd &sysMenuRect)) ret 0
int desktop=GetDesktopWindow
int dx dy dw dh
if(!GetWinXY(desktop dx dy dw dh)) ret 0
;; Estimate menu size
int menuLines=numlines(menu)
int menuHeight=menuLines*22+10
int menuWidth=sub.CalcMenuWidth(menu)
if(menuWidth=0) menuWidth=300
;; Calculate available space
int rightSpace=dw-sysMenuRect.right
int leftSpace=sysMenuRect.left
int bottomSpace=dh-sysMenuRect.bottom
int topSpace=sysMenuRect.top
;; Prefer right side
if(rightSpace >= menuWidth)
,p.x=sysMenuRect.right
,p.y=sysMenuRect.top
,tpmFlags=0 ;; TPM_LEFTALIGN | TPM_TOPALIGN
,ret 1
;; Try left side
if(leftSpace >= menuWidth)
,p.x=sysMenuRect.left
,p.y=sysMenuRect.top
,tpmFlags=8 ;; TPM_RIGHTALIGN | TPM_TOPALIGN
,ret 1
;; Try bottom
if(bottomSpace >= menuHeight)
,p.x=sysMenuRect.left
,p.y=sysMenuRect.bottom
,tpmFlags=0 ;; TPM_LEFTALIGN | TPM_TOPALIGN
,ret 1
;; Try top
if(topSpace >= menuHeight)
,p.x=sysMenuRect.left
,p.y=sysMenuRect.top
,tpmFlags=32 ;; TPM_LEFTALIGN | TPM_BOTTOMALIGN
,ret 1
;; All directions insufficient, choose the direction with relatively more space
if(rightSpace > leftSpace)
,if(rightSpace > bottomSpace and rightSpace > topSpace)
,,p.x=sysMenuRect.right
,,p.y=sysMenuRect.top
,,tpmFlags=0
,,ret 1
,else
,,if(bottomSpace > topSpace)
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.bottom
,,,tpmFlags=0
,,,ret 1
,,else
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.top
,,,tpmFlags=32
,,,ret 1
else
,if(leftSpace > bottomSpace and leftSpace > topSpace)
,,p.x=sysMenuRect.left
,,p.y=sysMenuRect.top
,,tpmFlags=8
,,ret 1
,else
,,if(bottomSpace > topSpace)
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.bottom
,,,tpmFlags=0
,,,ret 1
,,else
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.top
,,,tpmFlags=32
,,,ret 1
#sub ShowMenuSmart
function# int'hwnd str'menu
;; Smart show menu: avoid when system menu near edge, otherwise use default
if(empty(menu)) ret 0
if(sub.NeedAvoidSystemMenu(hwnd menu))
,POINT p
,int tf
,if(sub.CalcMenuPos(hwnd menu p tf))
,,ret ShowMenu(menu 0 p 0 tf)
;; Default display: mouse position, right aligned
ret ShowMenu(menu 0 0 0 8)
Posts: 1,239
Threads: 286
Joined: Jul 2022
08-21-2026, 12:00 AM
(This post was last modified: 08-21-2026, 01:50 AM by Davider.)
I have tested the following code.
There are two cases:
Case A, when the distance from the mouse pointer to the left edge of the desktop is less than the custom menu width;
Case B, when the distance is greater than the custom menu width. The current difficulty is obtaining the width of the custom menu ( myMenuWidth).
In post #11, the width is obtained by counting the number of characters in the menu items.
Can create a member function of the MenuPopup class to implement this functionality?
Is there a more elegant way to retrieve it?
Macro Explorer_Menu16
Trigger !v"" "#32768" /EXPLORER
out
int hwnd=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
SetTimer(0 0 100 &sub._Timer)
_s=
;1 Test 1
;2 Test 2
;3 Test 3
;4 Test 4
;5 Test 5
;6 Test 6
;7 Test 7
;8 Test 8
;9 Test 9
int x w; GetWinXY(hwnd x 0 w); out F"x: {x}; w: {w}"
POINT p; p.y=ym
if x < myMenuWidth ;;How to get myMenuWidth?
,;A: When the mouse pointer is closer to the left edge of the desktop than the custom menu width
,p.x=w+xm; _i=ShowMenu(_s 0 p 0 0)
else
,;B: When the mouse pointer is farther from the left edge of the desktop than the custom menu width
,p.x=x; _i=ShowMenu(_s 0 p 0 8)
out _i
#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu
Posts: 1,239
Threads: 286
Joined: Jul 2022
The following code adds a submenu to a custom menu.
When the system menu is displayed on the right side of the desktop, the submenu in the custom menu overlaps the system menu. Clicking a menu item executes both the submenu item and the system menu item underneath it.
Is there a way to solve this?
Function Explorer_Menu_T
Trigger !v"" "#32768" /EXPLORER
out
int hwnd=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret
SetTimer(0 0 100 &sub._Timer)
_s=
;1 Test 1
;> Test
;3 Test 3
;4 Test 4
;<
;5 Test 5
;6 Test 6
;7 Test 7
;8 Test 8
;9 Test 9
int x w; GetWinXY(hwnd x 0 w); out F"x: {x}; w: {w}"
POINT p; p.y=ym
if x < 200
,p.x=w+xm; _i=ShowMenu(_s 0 p 0 0)
else
,p.x=x; _i=ShowMenu(_s 0 p 0 8)
out _i
#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu
Posts: 12,300
Threads: 144
Joined: Dec 2002
08-22-2026, 03:28 AM
(This post was last modified: 08-22-2026, 05:15 AM by Gintaras.)
Macro Macro3682
Trigger !v"" "#32768" /EXPLORER
int shellMenu=TriggerWindow
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len!=1) ret
SetTimer(0 0 100 &sub._Timer)
int hh=SetWindowsHookEx(WH_CBT &sub._Hook 0 GetCurrentThreadId)
;
_s=
;1 Test 1
;> Test
;3 Test 3
;4 Test 4
;<
;5 Test 5
;6 Test 6
;7 Test 7
;8 Test 8
;9 Test 9
_i=ShowMenu(_s 0 0 0 8)
UnhookWindowsHookEx hh
out _i
#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu
#sub _Hook v
function# nCode w CBT_CREATEWND&m
if nCode=HCBT_CREATEWND
,_i=m.lpcs.lpszClass
,if _i=32768
,,SetTimer(w 5497 1 &sub._Timer2)
ret CallNextHookEx(0 nCode w &m)
#sub _Timer2 v
function myMenu u2 timer u3
KillTimer myMenu timer
RECT rShell rMy
GetWindowRect shellMenu &rShell
GetWindowRect myMenu &rMy
;calculate myMenu position and move
POINT p; xm p
SIZE z; z.cx=rMy.right-rMy.left; z.cy=rMy.bottom-rMy.top
dll user32 #CalculatePopupWindowPosition POINT*anchorPoint SIZE*windowSize flags RECT*excludeRect RECT*popupWindowPosition
CalculatePopupWindowPosition &p &z TPM_RIGHTALIGN &rShell &rMy
mov rMy.left rMy.top myMenu
Posts: 1,239
Threads: 286
Joined: Jul 2022
Can you share the `CalculatePopupWindowPosition` function in the code? I couldn't find it.
Posts: 12,300
Threads: 144
Joined: Dec 2002
08-22-2026, 04:30 AM
(This post was last modified: 08-22-2026, 04:31 AM by Gintaras.)
It's a Windows function. Declaration in winapi.txt.
Posts: 1,239
Threads: 286
Joined: Jul 2022
Thank you, I found it in the file below.
C:\Program Files (x86)\Quick Macros 2\winapi7.txt
|