(short ver = just read bold)
I have a script that displays a pop-up menu on mouse-over. Since menu cannot be timed to self-terminate, the script loads an auxiliary script through a pipe to close the menu after a few seconds unless in use. In particular, here's the auxiliary script:
Code:
script =
(
;#NoTrayIcon
DetectHiddenWindows, On
WinWait, ahk_class #32768
Loop,
{
Sleep, 1000
IfWinNotExist, ahk_class #32768
ExitApp
MouseGetPos,,,,mPop,1
If mPop
Break
}
IfWinExist, ahk_class #32768
WinClose
ExitApp
)
I'd like to use ahk_ids, hPop := WinExist("ahk_class #32768"), to track the particular class #32768 window that pops up first (so a wrong one isn't closed), but %-signs don't appear to work in the piped script. i.e %hPop% comes up empty so I cannot track a particular menu.
Is there a workaround?complete script:
Code:
/*
WARNING: Still under construction - works, but has some bugs.
NOTE: You need to customize drives to ignore in PopUSB subroutine.
A lot of the code (the complicated parts) are based on code by SKAN including
his Safely Remove USB Flash Drive - 45L:
http://www.autohotkey.com/forum/viewtopic.php?t=44873
The script also heavily relies on TrayIcon.ahk by Sean:
http://www.autohotkey.com/forum/viewtopic.php?t=18652
And it uses a pipe script from HotkeyIt/Lexikos:
;Below from is from a post by HotkeyIt: http://www.autohotkey.com/forum/viewtopic.php?p=263671#263671
;He referenced Lexikos as the main source: http://www.autohotkey.com/forum/viewtopic.php?t=25867
To use the UnlockIt subroutine, Unlocker has to be installed:
http://ccollomb.free.fr/unlocker/
(this is a temporary solution - I want to ultimately use AHK to find the offending processes)
You may also want to customize the auto-pop-up behavior of the ejection box, which is loosely
based on bmcclure post here:
http://www.autohotkey.com/forum/viewtopic.php?p=226847#226847
*/
#NoEnv
#SingleInstance, Force
SetBatchLines, -1
DetectHiddenWindows, On
SetTitleMatchMode, 2
SendMode, Input
CoordMode, Mouse, Screen
Process,Priority,,A
SetWorkingDir % A_ScriptDir
OnExit,ExitCleanUp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Start of USB Remove initiation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
OnMessage(0x404,"AutoHotkey_Notify")
hSysTray := WinExist( "ahk_class SystemTray_Main" )
;hide the default "USB Safely Remove" tray icon (uID = 1226)
If RegExMatch(TrayIcons("explorer.exe"), "s)(?<=idn: )\d+(?= \| Pid: \d+ \| uID: 1226)", idn)
HideTrayIcon(idn)
script =
(
;#NoTrayIcon
DetectHiddenWindows, On
WinWait, ahk_class #32768
Loop,
{
Sleep, 1000
IfWinNotExist, ahk_class #32768
ExitApp
MouseGetPos,,,,mPop,1
If mPop
Break
}
IfWinExist, ahk_class #32768
WinClose
ExitApp
)
Menu, Tray, UseErrorLevel
Menu, Tray, Icon, Hotplug.dll, 2
Menu, Tray, Add,
Menu, Tray, Add, Show Removable Devices, PopUSB
Menu, USB, UseErrorLevel
Return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; End of USB Remove initiation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;opens tray menu/s when mouse is over icon and interprets clicks
AutoHotkey_Notify(wParam, lParam)
{
static ignoreNext
If (lParam = 0x205) ;Right-click menu
{
ignoreNext := A_Now
Menu, Tray, Show
}
Else If (lParam = 0x202) ;Left-click menu
{
ignoreNext := A_Now
KeyWait, LButton, D T0.2 ; wait to see if 2nd left-click follows
If ErrorLevel
Gosub, PopUSB ;no second click, execute default left-click action
Else ListVars ;double-click received
}
Else
{
ignoreWait := ignoreNext
EnvSub,ignoreWait,A_Now,S
If (ignoreWait < -4 || !ignoreWait)
{
ignoreNext := A_Now
Gosub, PopUSB
}
}
Return 0
}
#u::
;Specify drives to ignore below
PopUSB:
DriveGet, RDRV, List, Removable ;note: includes floppy drives (A,B,...)
StringReplace, RDRV, RDRV, A ; to remove floppy drives
;StringReplace, RDRV, RDRV, B
StringReplace, RDRV, RDRV, R ;remove RAM drive
ADRV := RDRV
DriveGet, FDRV, List, Fixed ;get external and virtual hard drives
StringReplace, FDRV, FDRV, C ;remove fixed partitions
StringReplace, FDRV, FDRV, Q ;remove RAM drive
StringReplace, FDRV, FDRV, R ;remove RAM drive
StringReplace, FDRV, FDRV, X ;exclude network drive
StringReplace, FDRV, FDRV, Y ;exclude network drive
StringReplace, FDRV, FDRV, Z
ADRV .= FDRV
;if no ejectable drives, no menu
If StrLen(ADRV)=0
Return
If (A_ThisHotkey == "#u" && A_TimeSinceThisHotkey < 100 && StrLen(ADRV) == 1)
{ ;skip menu if only one drive to eject and ejection was hotkey initiated
Drv := ADRV ":"
Gosub, EjectUSB
}
Else
{
Menu, USB, DeleteAll
Loop, Parse, ADRV
{
DriveGet, Label, Label, %A_LoopField%:
DriveGet, Size, Capacity, %A_LoopField%:
Capacity := DriveSpace( A_LoopField,2 ), VarSetCapacity( DiskSz,16,0 ), Drv := ""
DllCall( "shlwapi.dll\StrFormatByteSize64A", Int64,Capacity, Str,DiskSz, UInt,16 )
Menu, USB, Add, &%A_LoopField%: %Label%`t%DiskSz%`t, EjectUSB
}
RunScript(script)
Menu, USB, Show
}
Return
EjectUSB:
If !ejecTry
MouseGetPos,oX,oY
If !Drv
Drv := SubStr( A_ThisMenuItem,2,1 ) ":"
;If drive is "removable" (flash) use SKAN's USBD_SafelyRemove
IfInString, RDRV, % SubStr( Drv,1,1 )
RDRV := USBD_SafelyRemove( Drv )
;If drive not "removable", or USBD_SafelyRemove failed, try to eject drive from Windows "USB Safely Remove" tray menu
If RDRV
{
Critical
MouseMove,%A_ScreenWidth%,%A_ScreenHeight%,0
PostMessage, 1226, 1226, 0x201,, ahk_id %hSysTray% ; Left Click down
PostMessage, 1226, 1226, 0x202,, ahk_id %hSysTray% ; Left Click Up
WinWaitActive, ahk_id %hSysTray%,,5 ; Wait for SRH Tray left-click-Menu
; MN_GETHMENU : Code for retrieving popup menu text adapted from Sean's following post
; Get Info from Context Menu: www.autohotkey.com/forum/viewtopic.php?p=137692#137692
SendMessage,0x1E1,0,0,,ahk_class #32768
hMenu := ErrorLevel
USBcount := DllCall( "GetMenuItemCount",UInt,hMenu )
Loop, % USBcount
{
idx := A_Index-1, idn := DllCall( "GetMenuItemID", UInt,hMenu, Int,idx ), nSize := DllCall( "GetMenuString", UInt,hMenu, Int,idx, Int,0, Int,0, UInt,0x400 ) + 1
VarSetCapacity( mStr,nSize )
DllCall( "GetMenuString", UInt,hMenu, Int,idx, Str,mStr, Int,nSize, UInt,0x400 )
If InStr( mStr, Drv)
{
ControlSend,,{Down %A_Index%}{Enter},ahk_id %hSysTray%
Goto, EjectionCheck
}
}
}
Exit
EjectionCheck:
Sleep, 2000
Loop, 15
{
WinWait,Problem Ejecting,,1
If (!Errorlevel || !FileExist(Drv))
Break
}
If hPrbEject := WinExist("Problem Ejecting")
{
ControlClick,Button1,ahk_id %hPrbEject%
hPrbEject =
If (ejecTry < 3) ;try again
{
Sleep, 2000
ejecTry++
Goto, EjectUSB
}
MsgBox,4,Device (%Drv%) Locked, Do you want to run Unlocker?, 11
IfMsgBox Yes
UnlockIt(Drv)
}
Else IfExist, %Drv%\
TrayTip,Failed to eject %Drv%,Mystery USB Removal error,11,1
;cleanup
ejecTry =
If RegExMatch(TrayIcons("explorer.exe"), "s)(?<=idn: )\d+(?= \| Pid: \d+ \| uID: 1226)", idn)
HideTrayIcon(idn)
MouseMove,%oX%,%oY%,0
Return
;requires unlocker or another similar software (temp solution)
UnlockIt(Drv="")
{
;IfExist, %A_ProgramFiles%\Unlocker\Unlocker.exe
;Run, %A_ProgramFiles%\Unlocker\Unlocker.exe %Drv%\
Run,%A_ProgramFiles%\LockHunter\LockHunter.exe,,,pLH
WinWaitActive,ahk_pid %pLH%
ControlSetText,Edit1,%Drv%,ahk_pid %pLH%
ControlSend,,{Enter},ahk_pid %pLH%
}
ExitCleanUp:
RegExMatch(TrayIcons("explorer.exe"), "s)(?<=idn: )\d+(?= \| Pid: \d+ \| uID: 1226)", idn), HideTrayIcon(idn,False)
ExitApp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Functions below are 100% by other people and normally reside in my .\AutoHotkey\Lib direcorty:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; www.autohotkey.com/forum/viewtopic.php?p=92483#92483
DriveSpace(Drv="", Free=1)
{
Drv.= ":\", VarSetCapacity(SPC, 30, 0), VarSetCapacity(BPS, 30, 0)
VarSetCapacity(FC , 30, 0), VarSetCapacity(TC , 30, 0)
DllCall( "GetDiskFreeSpaceA",Str,Drv,UIntP,SPC,UIntP,BPS,UIntP,FC,UIntP,TC )
Return Free=1 ? (SPC*BPS*FC) : (SPC*BPS*TC) ; Ternary Operator requires 1.0.46+
}
;from TrayIcon.ahk by Sean: http://www.autohotkey.com/forum/viewtopic.php?t=18652
TrayIcons(sExeName = "")
{
WinGet, pidTaskbar, PID, ahk_class Shell_TrayWnd
hProc:= DllCall("OpenProcess", "Uint", 0x38, "int", 0, "Uint", pidTaskbar)
pProc:= DllCall("VirtualAllocEx", "Uint", hProc, "Uint", 0, "Uint", 32, "Uint", 0x1000, "Uint", 0x4)
idxTB:= GetTrayBar()
SendMessage, 0x418, 0, 0, ToolbarWindow32%idxTB%, ahk_class Shell_TrayWnd ; TB_BUTTONCOUNT
Loop, %ErrorLevel%
{
SendMessage, 0x417, A_Index-1, pProc, ToolbarWindow32%idxTB%, ahk_class Shell_TrayWnd ; TB_GETBUTTON
VarSetCapacity(btn,32,0), VarSetCapacity(nfo,32,0)
DllCall("ReadProcessMemory", "Uint", hProc, "Uint", pProc, "Uint", &btn, "Uint", 32, "Uint", 0)
iBitmap := NumGet(btn, 0)
idn := NumGet(btn, 4)
Statyle := NumGet(btn, 8)
If dwData := NumGet(btn,12)
iString := NumGet(btn,16)
Else dwData := NumGet(btn,16,"int64"), iString:=NumGet(btn,24,"int64")
DllCall("ReadProcessMemory", "Uint", hProc, "Uint", dwData, "Uint", &nfo, "Uint", 32, "Uint", 0)
If NumGet(btn,12)
hWnd := NumGet(nfo, 0)
, uID := NumGet(nfo, 4)
, nMsg := NumGet(nfo, 8)
, hIcon := NumGet(nfo,20)
Else hWnd := NumGet(nfo, 0,"int64"), uID:=NumGet(nfo, 8), nMsg:=NumGet(nfo,12)
WinGet, pid, PID, ahk_id %hWnd%
WinGet, sProcess, ProcessName, ahk_id %hWnd%
WinGetClass, sClass, ahk_id %hWnd%
If !sExeName || (sExeName = sProcess) || (sExeName = pid)
VarSetCapacity(sTooltip,128), VarSetCapacity(wTooltip,128*2)
, DllCall("ReadProcessMemory", "Uint", hProc, "Uint", iString, "Uint", &wTooltip, "Uint", 128*2, "Uint", 0)
, DllCall("WideCharToMultiByte", "Uint", 0, "Uint", 0, "str", wTooltip, "int", -1, "str", sTooltip, "int", 128, "Uint", 0, "Uint", 0)
, sTrayIcons .= "idx: " . A_Index-1 . " | idn: " . idn . " | Pid: " . pid . " | uID: " . uID . " | MessageID: " . nMsg . " | hWnd: " . hWnd . " | Class: " . sClass . " | Process: " . sProcess . "`n" . " | Tooltip: " . sTooltip . "`n"
}
DllCall("VirtualFreeEx", "Uint", hProc, "Uint", pProc, "Uint", 0, "Uint", 0x8000)
DllCall("CloseHandle", "Uint", hProc)
Return sTrayIcons
}
HideTrayIcon(idn, bHide = True)
{
idxTB := GetTrayBar()
SendMessage, 0x404, idn, bHide, ToolbarWindow32%idxTB%, ahk_class Shell_TrayWnd ; TB_HIDEBUTTON
SendMessage, 0x1A, 0, 0, , ahk_class Shell_TrayWnd
}
GetTrayBar()
{
ControlGet, hParent, hWnd,, TrayNotifyWnd1 , ahk_class Shell_TrayWnd
ControlGet, hChild , hWnd,, ToolbarWindow321, ahk_id %hParent%
Loop
{
ControlGet, hWnd, hWnd,, ToolbarWindow32%A_Index%, ahk_class Shell_TrayWnd
If Not hWnd
Break
Else If hWnd = %hChild%
{
idxTB := A_Index
Break
}
}
Return idxTB
}
;Script below is from a post by HotkeyIt: http://www.autohotkey.com/forum/viewtopic.php?p=263671#263671
;He referenced Lexikos as the main source: http://www.autohotkey.com/forum/viewtopic.php?t=25867
RunScript(TempScript, name="")
{
pipe_name := name="" ? A_TickCount : name
; Before reading the file, AutoHotkey calls GetFileAttributes(). This causes
; the pipe to close, so we must create a second pipe for the actual file contents.
; Open them both before starting AutoHotkey, or the second attempt to open the
; "file" will be very likely to fail. The first created instance of the pipe
; seems to reliably be "opened" first. Otherwise, WriteFile would fail.
pipe_ga := CreateNamedPipe(pipe_name, 2)
pipe := CreateNamedPipe(pipe_name, 2)
if (pipe=-1 or pipe_ga=-1)
{
MsgBox CreateNamedPipe failed.
ExitApp
}
Run, %A_AhkPath% "\\.\pipe\%pipe_name%",,,PID
; Wait for AutoHotkey to connect to pipe_ga via GetFileAttributes().
DllCall("ConnectNamedPipe","uint",pipe_ga,"uint",0)
; This pipe is not needed, so close it now. (The pipe instance will not be fully
; destroyed until AutoHotkey also closes its handle.)
DllCall("CloseHandle","uint",pipe_ga)
; Wait for AutoHotkey to connect to open the "file".
DllCall("ConnectNamedPipe","uint",pipe,"uint",0)
; AutoHotkey reads the first 3 bytes to check for the UTF-8 BOM "". If it is
; NOT present, AutoHotkey then attempts to "rewind", thus breaking the pipe.
Script:= chr(239) chr(187) chr(191) TempScript
if !DllCall("WriteFile","uint",pipe,"str",Script,"uint",StrLen(Script)+1,"uint*",0,"uint",0)
MsgBox WriteFile failed: %ErrorLevel%/%A_LastError%
DllCall("CloseHandle","uint",pipe)
Return PID
}
CreateNamedPipe(Name, OpenMode=3, PipeMode=0, MaxInstances=255)
{
return DllCall("CreateNamedPipe","str","\\.\pipe\" Name,"uint",OpenMode
,"uint",PipeMode,"uint",MaxInstances,"uint",0,"uint",0,"uint",0,"uint",0)
}
;By SKAN: http://www.autohotkey.com/forum/viewtopic.php?t=44873
;modifications noted by ;;
USBD_SafelyRemove( Drv ) {
;; If A_OSVersion not in WIN_VISTA,WIN_XP,WIN_2000
;; Return
If ! ( Serial := USBD_GetDeviceSerial( Drv ) )
Return 1 ;;
;; Return
DeviceID := USBD_GetDeviceID( Serial )
DeviceEject( DeviceID )
;; IfExist, %Drv%\, TrayTip, %DeviceID%, Drive %Drv% was not Ejected!, 10, 3
IfExist, %Drv%\ ;;
Return 1 ;;
Else, TrayTip, %DeviceID%, Drive %Drv% was safely Removed, 10, 1
}
USBD_GetDeviceSerial( Drv="" ) {
DriveGet, DriveType, Type, %Drv%
IfNotEqual,DriveType,Removable, Return
RegRead, Hex, HKLM, SYSTEM\MountedDevices, \DosDevices\%Drv%
VarSetCapacity(U,(Sz:=StrLen(Hex)//2)), VarSetCapacity(A,Sz+1)
Loop % Sz
NumPut( "0x" . SubStr(hex,2*A_Index-1,2), U, A_Index-1, "Char" )
DllCall( "WideCharToMultiByte", Int,0,Int,0, UInt,&U,UInt,Sz, Str,A,UInt,Sz, Int,0,Int,0)
StringSplit, Part, A, #
ParentIdPrefixCheck := SubStr( Part3,1,InStr(Part3,"&",0,0)-1 )
IfEqual,A_OSVersion,WIN_VISTA, Return,ParentIdPrefixCheck
Loop, HKLM, SYSTEM\CurrentControlSet\Enum\USBSTOR,1,0
{ Device := A_LoopRegName
Loop, HKLM, SYSTEM\CurrentControlSet\Enum\USBSTOR\%Device%,1,0
{ Serial := A_LoopRegName
RegRead, PIPrefix, HKLM, SYSTEM\CurrentControlSet\Enum\USBSTOR\%Device%\%Serial%
, ParentIdPrefix
If ( PIPrefix = ParentIdPrefixCheck )
Return, SubStr( Serial,1,InStr(Serial,"&",0,0)-1 )
}
}}
USBD_GetDeviceID( Serial ) {
Loop, HKLM, SYSTEM\CurrentControlSet\Enum\USB\,1,0
{ Device := A_LoopRegName
Loop, HKLM, SYSTEM\CurrentControlSet\Enum\USB\%Device%,1,0
If ( A_LoopRegName=Serial )
Return DllCall( "CharUpperA", Str, "USB\" Device "\" Serial, Str )
}}
DeviceEject( DeviceID ) {
hMod := DllCall( "LoadLibrary", Str,"SetupAPI.dll" ), VarSetCapacity(VE,255,0)
If ! DllCall( "SetupAPI\CM_Locate_DevNodeA", UIntP,DI, Str,DeviceID, Int,0 )
If ! DllCall( "SetupAPI\CM_Get_DevNode_Status", UIntP,STS, UIntP,PR, UInt,DI, Int,0)
DllCall( "SetupAPI\CM_Request_Device_EjectA", UInt,DI, UIntP,VT, Str,VE, UInt,255, Int,0)
DllCall( "FreeLibrary", UInt,hMod )
}