Jump to content

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

Actions on taskbar's tray-icon via AHK possible?


  • Please log in to reply
51 replies to this topic
pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009

The following is my current version.
Eg. To safely remove J:, the hotkey will be Shift+Control+J

<snip>


Your current version works great, but I still prefer to see the pop-up menu first because half the time I don't remember what letter the computer assigned to the flash drive.

Actually, with multiple flash/external (hard) drives attached, picking the right one is a problem even with the tray menu because it doesn't give enough details. I fumble with the mouse to find the right tray icon just to see "Safely Remove USB Mass Storage Device - Drive I" and "Safely Remove USB Mass Storage Device - Drive J", for example, and then I have to open My Computer anyway to find out which one is the one I wanted to eject... And if I'm unlucky 'It's not safe to remove the device at the moment.'

Ideally, maybe as future AHK project, I want a USB Ejector hotkey that (1) gets a pop-up menu with details (size, label,...) to let up pick which drive to eject, and (2) if locked, automatically runs Unlocker, or otherwise looks up the open handle, lets you unlock/close/kill the offender, then tries to eject again.

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


SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005
Dear pajenn,
run the following script and left-click tray icon to see a detailed menu as sought by you.

#Persistent
#SingleInstance, Force
SetBatchLines -1
DetectHiddenWindows, On
CoordMode, Mouse, Screen
Menu, Tray, UseErrorLevel
Menu, Tray, Icon, Hotplug.dll, 2
Menu, Tray, Add,
Menu, Tray, Add, Show USB Devices, PopUSB
Menu, Tray, Default, Show USB Devices
Menu, Tray, Click, 1
Menu, USB, UseErrorLevel
Return

PopUSB:
 DriveGet, RDRV, List, REMOVABLE
 StringReplace, RDRV, RDRV, A
 StringReplace, RDRV, RDRV, B
 Menu, USB, DeleteAll
 Loop, Parse, RDRV
  {
   DriveGet, Label, Label, %A_LoopField%:
   DriveGet, Size, Capacity, %A_LoopField%:
   Capacity := DriveSpace( A_LoopField,2 ), VarSetCapacity( DiskSz,16,0 )
   DllCall( "shlwapi.dll\StrFormatByteSize64A", Int64,Capacity, Str,DiskSz, UInt,16 )
   Menu, USB, Add, &%A_LoopField%: %Label%`t%DiskSz%, EjectDrive
  }
 Menu, USB, Show
Return

EjectDrive:
 Critical
 Drv := SubStr( A_ThisMenuItem,2,1 ) ":", nStr := ""
 hWnd := WinExist( "ahk_class SystemTray_Main" )
 MouseGetPos, X, Y
 MouseMove, A_ScreenWidth, A_ScreenHeight, 0
 PostMessage, 1226, 1226, 0x201,, ahk_id %hWnd% ; Left Click down
 PostMessage, 1226, 1226, 0x202,, ahk_id %hWnd% ; Left Click Up
 WinWaitActive, ahk_id %hWnd%,,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
 Loop, % DllCall( "GetMenuItemCount", UInt,hMenu ) {
 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 %hWnd%
    Break
   }
 } MouseMove, X, Y, 0
Return

DriveSpace(Drv="", Free=1) { ; www.autohotkey.com/forum/viewtopic.php?p=92483#92483
 Drv := 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+
}

kWo4Lk1.png

pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009
Damn! This is very very nice. Thank you.

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


pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009

Dear pajenn,
run the following script and left-click tray icon to see a detailed menu as sought by you.


I liked the script so much I added it to my main (always-on) hotkey script. Few notes:

1. PopUSB sub gets USB flash drives with
"DriveGet, RDRV, List, Removable", but not external hard drives, so I added the following lines to it:

DriveGet, FDRV, List, Fixed   ; to get external and virtual hard drives
RDRV.=FDRV

; to add drives with mounted virtual cd images
DriveGet, CDRV, List, CDROM
Loop, Parse, CDRV,,
{ DriveGet,status,StatusCD,%A_LoopField%
  if status = stopped   ; options: not ready,open,playing,paused,seeking,stopped
    RDRV.=A_LoopField
}
StringReplace, RDRV, RDRV, D   ; to exclude real cd-drive

StringReplace, RDRV, RDRV, C ; to remove fixed partitions
StringReplace, RDRV, RDRV, Z ; as above

2. I also added mounted virtual cd-images to the menu, but I haven't modified the EjectDrive sub yet to dismount images on command.

3. "DriveGet, FDRV, List, Fixed" command also adds virtual hard drives to the menu. In particular, I mounted an Acronis True Image backup copy (.tib-image file) of another computer's C: partition on to my N: drive. I did it to see what would happen if I chose to dismount it - nothing happened.

4. If feasible, I plan to remove the "Safely Remove Hardware" icon now that my AHK icon covers that (or reload/unhide it only OnExit of the AHK script). Of course that means scrapping EjectDrive sub, but I need to rework it anyway to support dismounting of virtual drives/images.

5. I still need to add 'run Unlocker [on locked drive]' or another subroutine to check for open handles if there's a problem ejecting a drive.

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


pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009
Here's my new USB/Image Drive ejection script based on the script SKAN posted earlier.

Here's what it does:

1. PopUSB retrieves Removable, "Fixed" and "CD" drive letters for a popup menu that appears when you left-click the script's tray icon. It also retrieves the label and size so you know which drive you are ejecting.

-"Fixed" drives are for external USB and virtual HDD only
-CD drives are to pick up virtual CD/DVD images

NOTE: Requires adding your actual fixed drives/partitions to the list of "StringReplace, RDRV, RDRV, A" to exclude them (A,B,C,D already excluded)

2. EjectDrive moves mouse to lower-left corner, left-clicks "Safely Remove Hardware" icon, and looks for the drive letter you chose to eject from the tray menu. If found, it clicks the item to safely remove. goes to EjectUSB for cleanup. If the letter is not there, the script assumes the drive is virtual, and goes to Unmount Image.

3. EjectUSB just runs a loop to check if the USB drive is gone. If the "Problem Ejecting USB Mass Storage Device" window pops up, it goes to UnlockIt. (If you don't have unlocker installed, this won't be of much use).

4. Unmount Image opens My Computer, left clicks the window, sends {UP} commands to make sure you get focus - this could use cleaning up. It then goes down the list of drives until the one you chose is found, opens a context menu for it, selects "Unmount image" (based on status bar text).

NOTE: I'm using DaemonTools Pro, and the "Unmount image" refers to its context menu text. For other programs, you may need to just use Unmount or Dismount, or whatever they say in the status bar. A command line method would be even better if applicable.

If unmounted is clicked, the script checks to see if the image is gone using FileExist command. You could also use GetDrive status for more options. If there's a problem it goes to UnlockIt.

5. UnlockIt asks you whether to run Unlocker on the drive. It's an awesome piece of freeware that can usually tell what the problem is, and let's you unlock/kill it. However, if it's an open document or such, then just close it manually.

NOTE: If unlocker finds no problem, then the offending program may have left the drive - try ejecting again. For me the usual suspects are some Adobe Acrobat process or Explorer.exe - I think Virus scanners and indexing services can also be the culprits, but they may be trickier to detect or unlock.

This is still a work in progress, and meant to be customized by individual users (if any besides me).

Note: Sean posted a nice script called ShellContextMenu, which can activate the context menu for the selected drive anywhere (or at the lower-right corner in this case). That would seem to be a more elegant solution than opening my computer, etc., but I had trouble getting it to work reliably - menu wouldn't activate, or it would, but item selection didn't work, plus it required using SetTimer - I can get it to work, for example, by leaving the corner, opening an empty dummy folder, lots of sleep,... etc. but by that point I figured just opening My Computer and clicking on the drive was simpler. Note: I'm not blaming the ShellContextMenu script - using it here would be an improvement, but I just don't know how to apply it well-enough.

#SingleInstance, Force
#Persistent
SetBatchLines -1
DetectHiddenWindows, On
Menu, Tray, UseErrorLevel
Menu, Tray, Icon, Hotplug.dll, 2
Menu, Tray, Add,
Menu, Tray, Add, Show Removable Devices, PopUSB
Menu, Tray, Default, Show Removable Devices
Menu, Tray, Click, 1
Menu, USB, UseErrorLevel
Return

PopUSB:
 DriveGet, RDRV, List, Removable
 DriveGet, FDRV, List, Fixed   ; to get external and virtual hard drives
 RDRV .= FDRV
 DriveGet, CDRV, List, CDROM   ; to add drives with mounted virtual cd images
 Loop, Parse, CDRV,,
 { DriveGet, status, StatusCD, %A_LoopField%
   if (status = "stopped" or status = "not ready") ; options: not ready,open,playing,paused,seeking,stopped
     RDRV .= A_LoopField
 }
 StringReplace, RDRV, RDRV, A ; to remove fixed partitions
 StringReplace, RDRV, RDRV, B
 StringReplace, RDRV, RDRV, C ; to remove fixed partitions
 StringReplace, RDRV, RDRV, D   ; to exclude real cd-drive
 StringReplace, RDRV, RDRV, Z ; as above
 Menu, USB, DeleteAll
 Loop, Parse, RDRV
 { DriveGet, Label, Label, %A_LoopField%:
   DriveGet, Size, Capacity, %A_LoopField%:
   Capacity := DriveSpace( A_LoopField,2 ), VarSetCapacity( DiskSz,16,0 )
   DllCall( "shlwapi.dll\StrFormatByteSize64A", Int64,Capacity, Str,DiskSz, UInt,16 )
   Menu, USB, Add, &%A_LoopField%: %Label%`t%DiskSz%`t, EjectDrive
  }
 Menu, USB, Show
Return

EjectDrive:
 Critical
 Drv := SubStr( A_ThisMenuItem,2,1 ) ":", nStr := ""
 Drv1 := Drv "\"
 hWnd := WinExist( "ahk_class SystemTray_Main" )
 CoordMode, Mouse, Screen  
 MouseGetPos, oX, oY
 MouseMove, A_ScreenWidth, A_ScreenHeight, 0
 PostMessage, 1226, 1226, 0x201,, ahk_id %hWnd% ; Left Click down
 PostMessage, 1226, 1226, 0x202,, ahk_id %hWnd% ; Left Click Up
 WinWaitActive, ahk_id %hWnd%,,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 %hWnd%
     Sleep, 100 
     Goto, EjectUSB
   }
   else if A_INDEX = %USBcount%
     Goto, UnmountImage
 }
return

; 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+
}

EjectUSB:
 Sleep, 100
 Loop, 
 { sleep, 5000
   IfWinExist, Problem Ejecting USB Mass Storage Device
     Goto, UnlockIt
   else if !FileExist(Drv1)
     break
   else if A_INDEX > 40
   { MsgBox, Mystery USB Removal error
     break
   }
 }
 MouseMove, oX, oY, 0
return

UnmountImage:
 ControlSend,,{ESC}, ahk_id %hWnd%  ; dismiss tray menu
 Run, ::{20d04fe0-3aea-1069-a2d8-08002b30309d}
 WinWait, My Computer ahk_class CabinetWClass
 IfWinNotActive, My Computer ahk_class CabinetWClass
   WinActivate, My Computer ahk_class CabinetWClass
 WinWaitActive, My Computer ahk_class CabinetWClass
 Sleep, 100
 WinMove, My Computer ahk_class CabinetWClass,,0,0,250,600
 MouseClick,left,50,200
 Send, {UP}
 ControlGet, MyRows, List, Count, SysListView321, My Computer ahk_class CabinetWClass
 ControlSend, SysListView321,{UP %MyRows%}, My Computer ahk_class CabinetWClass
 loop, %MyRows%
 { ControlGet, driv, List, Selected, SysListView321, My Computer ahk_class CabinetWClass
   Sleep, 100
   IfInString, driv, %Drv%
     break
   ControlSend, SysListView321,{DOWN}, My Computer ahk_class CabinetWClass
 }
 GetMenuItemBySBar2("Unmount image")
 Sleep, 1000
 WinClose, My Computer ahk_class CabinetWClass
 Sleep, 1000
 if FileExist(Drv1)
   Goto, UnlockIt
 TrayTip, Image Unmounted Successfully, Drive %Drv% is free., 10, 1
return

UnlockIt:
 MsgBox, 4, , Something is locking the drive`nDo you want to run Unlocker? (Press YES or NO)
 IfMsgBox No
   return
 IfMsgBox Timeout
   return
 IfWinExist, Problem Ejecting USB Mass Storage Device ahk_class #32770
   ControlClick, OK, Problem Ejecting USB Mass Storage Device ahk_class #32770
 Run, "C:\Program Files\Unlocker\Unlocker.exe" %Drv1%
return
 
; for Windows Explorer folders (not desktop). Uses IfInString for status bar text match. if submenu used, TopSubText must contain exact status bar text of first submenu item. if no submenu, just use first argument.
GetMenuItemBySBar2(SBar_text, TopSubText="TripleZero")
{ 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
  }
}

p.s. I'm using this script with my main AHK script that I load at start-up. I've set the 'Remove Hardware Safely' tray icon to be 'always (semi)-hidden' since I don't need it now.

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
If the menu item to be executed appears in the main-menu, not in the submenu(s), then no need to bother to bring up the context menu. Just use InvokeVerb(Ex).
sPath:= "C:\"   ; Folder Path
sVerb:= "&Open"	; specify exact one shown in the main-menu.

COM_Init()
psh :=	COM_CreateObject("Shell.Application")
psf :=	COM_Invoke(psh, "NameSpace", sPath)
psi :=	COM_Invoke(psf, "Self")	; COM_Invoke(psf, "ParseName", sFile)
COM_Invoke(psi, "InvokeVerb", sVerb)
COM_Release(psi)
COM_Release(psf)
COM_Release(psh)
COM_Term()


pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009

If the menu item to be executed appears in the main-menu, not in the submenu(s), then no need to bother to bring up the context menu. Just use InvokeVerb(Ex).

sPath:= "C:"   ; Folder Path
sVerb:= "&Open"	; specify exact one shown in the main-menu.

COM_Init()
psh :=	COM_CreateObject("Shell.Application")
psf :=	COM_Invoke(psh, "NameSpace", sPath)
psi :=	COM_Invoke(psf, "Self")	; COM_Invoke(psf, "ParseName", sFile)
COM_Invoke(psi, "InvokeVerb", sVerb)
COM_Release(psi)
COM_Release(psf)
COM_Release(psh)
COM_Term()


Thank you once again. The code works perfectly with my Daemon Tools menu item (sVerb:= "Unmount image"), plus it'll save me a lot of time in the future.

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


pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009

Dear pajenn,
run the following script and left-click tray icon to see a detailed menu as sought by you.

#Persistent
#SingleInstance, Force
SetBatchLines -1
DetectHiddenWindows, On
CoordMode, Mouse, Screen
Menu, Tray, UseErrorLevel
Menu, Tray, Icon, Hotplug.dll, 2
Menu, Tray, Add,
Menu, Tray, Add, Show USB Devices, PopUSB
Menu, Tray, Default, Show USB Devices
Menu, Tray, Click, 1
Menu, USB, UseErrorLevel
Return

PopUSB:
 DriveGet, RDRV, List, REMOVABLE
 StringReplace, RDRV, RDRV, A
 StringReplace, RDRV, RDRV, B
 Menu, USB, DeleteAll
 Loop, Parse, RDRV
  {
   DriveGet, Label, Label, %A_LoopField%:
   DriveGet, Size, Capacity, %A_LoopField%:
   Capacity := DriveSpace( A_LoopField,2 ), VarSetCapacity( DiskSz,16,0 )
   DllCall( "shlwapi.dll\StrFormatByteSize64A", Int64,Capacity, Str,DiskSz, UInt,16 )
   Menu, USB, Add, &%A_LoopField%: %Label%`t%DiskSz%, EjectDrive
  }
 Menu, USB, Show
Return

EjectDrive:
 Critical
 Drv := SubStr( A_ThisMenuItem,2,1 ) ":", nStr := ""
 hWnd := WinExist( "ahk_class SystemTray_Main" )
 MouseGetPos, X, Y
 MouseMove, A_ScreenWidth, A_ScreenHeight, 0
 PostMessage, 1226, 1226, 0x201,, ahk_id %hWnd% ; Left Click down
 PostMessage, 1226, 1226, 0x202,, ahk_id %hWnd% ; Left Click Up
 WinWaitActive, ahk_id %hWnd%,,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
 Loop, % DllCall( "GetMenuItemCount", UInt,hMenu ) {
 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 %hWnd%
    Break
   }
 } MouseMove, X, Y, 0
Return

DriveSpace(Drv="", Free=1) { ; www.autohotkey.com/forum/viewtopic.php?p=92483#92483
 Drv := 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+
}


Tray menu question: I'd like to trigger the left-click tray menu (PopUSB) upon moving the mouse cursor over the tray icon. I'm trying to use the following script, but so far no luck:

Menu, Tray, Add, Show USB Devices, PopUSB
OnMessage(0x404, "AHK_NOTIFYICON")

AHK_NOTIFYICON(wParam, lParam)
{
    if (lParam = 0x202) ; WM_LBUTTONUP
    {
        SetTimer, ShowLbuttonMenu, -1
        return 0
    }
}

ShowLbuttonMenu:
    Goto, PopUSB
return

That's based on code posted by Lexikos here in reply to a similar problem.

Is that technique applicable to this situation? If yes, any advice on what I'm doing wrong?

On a separate matter, how to show icons in (PopUSB) menu? For example, suppose each drive has a custom DRIVE.ICO (and corresponding AUTORUN.INF). The icon path is easy enough to retrieve, but what's the command to add it to the menu?

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


pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009
I got the automatic pop-over menu working. It still needs refining to make the pop-ups smoother, but for now, here it is:

#SingleInstance, Force
SetBatchLines -1
DetectHiddenWindows, On
Menu, Tray, UseErrorLevel
Menu, Tray, Icon, Hotplug.dll, 2
Menu, Tray, Add,
Menu, Tray, Add, Show Removable Devices, PopUSB
Menu, USB, UseErrorLevel
OnMessage(0x404, "AutoHotkey_Notify")
return

AutoHotkey_Notify(wParam, lParam)
{ static ignorenext
  If !ignorenext
  { SetTimer, LeftClickTray, -1
    Sleep, 1000
    ignorenext := True
  }
  Else 
  { If (lParam = 0x205) ;Right click menu
    { Menu, Tray, Show
      ignorenext := False
    }
    Else If (lParam = 0x202) ; Left click menu
    { KeyWait, LButton, D T0.3
      If ErrorLevel
      { SetTimer, LeftClickTray, -1
        ignorenext := False
      }
    }
  }
  Return 0
}

LeftClickTray:
  Goto, PopUSB

#u::Goto, PopUSB

PopUSB:
 DriveGet, RDRV, List, Removable
 DriveGet, FDRV, List, Fixed   ; to get external and virtual hard drives
 RDRV .= FDRV
 DriveGet, CDRV, List, CDROM   ; to add drives with mounted virtual cd images
 Loop, Parse, CDRV,,
 { DriveGet, status, StatusCD, %A_LoopField%
   if (status = "stopped" or status = "not ready") ; options: not ready,open,playing,paused,seeking,stopped
     RDRV .= A_LoopField
 }
 StringReplace, RDRV, RDRV, A ; to remove fixed partitions
 StringReplace, RDRV, RDRV, B
 StringReplace, RDRV, RDRV, C ; to remove fixed partitions
 StringReplace, RDRV, RDRV, D   ; to exclude real cd-drive
 StringReplace, RDRV, RDRV, Z ; as above
 Menu, USB, DeleteAll
 Loop, Parse, RDRV
 { DriveGet, Label, Label, %A_LoopField%:
   DriveGet, Size, Capacity, %A_LoopField%:
   Capacity := DriveSpace( A_LoopField,2 ), VarSetCapacity( DiskSz,16,0 )
   DllCall( "shlwapi.dll\StrFormatByteSize64A", Int64,Capacity, Str,DiskSz, UInt,16 )
   Menu, USB, Add, &%A_LoopField%: %Label%`t%DiskSz%`t, EjectDrive
  }
 Menu, USB, Show
Return

EjectDrive:
 Critical
 Drv := SubStr( A_ThisMenuItem,2,1 ) ":", nStr := ""
 Drv1 := Drv "\"
 hWnd := WinExist( "ahk_class SystemTray_Main" )
 CoordMode, Mouse, Screen  
 MouseGetPos, oX, oY
 MouseMove, A_ScreenWidth, A_ScreenHeight, 0
 PostMessage, 1226, 1226, 0x201,, ahk_id %hWnd% ; Left Click down
 PostMessage, 1226, 1226, 0x202,, ahk_id %hWnd% ; Left Click Up
 WinWaitActive, ahk_id %hWnd%,,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 %hWnd%
     Sleep, 100 
     Goto, EjectUSB
   }
   else if A_INDEX = %USBcount%
     Goto, UnmountImage
 }
return

; 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+
}

EjectUSB:
 Sleep, 100
 Loop, 
 { sleep, 5000
   IfWinExist, Problem Ejecting USB Mass Storage Device
     Goto, UnlockIt
   else if !FileExist(Drv1)
     break
   else if A_INDEX > 40
   { MsgBox, Mystery USB Removal error
     break
   }
 }
 MouseMove, oX, oY, 0
return

UnmountImage:
 ControlSend,,{ESC}, ahk_id %hWnd%  ; dismiss tray menu
 sPath:= Drv1   ; Folder Path
 sVerb:= "Unmount image"   ; specify exact one shown in the main-menu.
 COM_Init()
 psh :=   COM_CreateObject("Shell.Application")
 psf :=   COM_Invoke(psh, "NameSpace", sPath)
 psi :=   COM_Invoke(psf, "Self")   ; COM_Invoke(psf, "ParseName", sFile)
 COM_Invoke(psi, "InvokeVerb", sVerb)
 COM_Release(psi)
 COM_Release(psf)
 COM_Release(psh)
 COM_Term() 
 Sleep, 2000
 if FileExist(Drv1)
   Goto, UnlockIt
 TrayTip, Image Unmounted Successfully, Drive %Drv% is free., 10, 1
return

UnlockIt:
 MsgBox, 4, , Something is locking the drive`nDo you want to run Unlocker? (Press YES or NO),10
 IfMsgBox No
   return
 IfMsgBox Timeout
   return
 IfWinExist, Problem Ejecting USB Mass Storage Device ahk_class #32770
   ControlClick, OK, Problem Ejecting USB Mass Storage Device ahk_class #32770
 Run, "C:\Program Files\Unlocker\Unlocker.exe" %Drv1%
return

Notes:
*Winkey+U brings up the eject menu.
*The "LeftClickTray:" subroutine is redunadant - I could just have used PopUSB, but I left it like that for readability. 'Menu, Tray, Show' is the RightClickTray equivalent.
*still need to:
- make the auto-pop menu close automatically if not selected in 5 sec
- improve pop-up reliability

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


SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005
@pajenn: Take a look at the following topic

Crazy Scripting : Safely Remove USB Flash Drive - 45L

pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009

@pajenn: Take a look at the following topic

Crazy Scripting : Safely Remove USB Flash Drive - 45L


I'd say it's an impressive script, but at a glance of the code I have no clue of the method it uses. Is it equivalent to clicking on the tray icon to eject a USB device? In any case, it looks very impressive and works well on my flash drives. I added it to my AHK library, and to the updated version of the "USB Safely Remove" imitation I'm trying to script with AHK.

Main script (normally it's part of my main AHK script, but I have separately too to work on it).

Dependencies:

-SKAN's USBD_SafelyRemove
-Unlocker (or sub in your favorite program to find out what's preventing a USB drive from ejecting...)
-Sean's trayicons functions (note: I changed "sProcess . "`n" . " | Tooltip:" on line 56 of original script to "sProcess . " | Tooltip:")

Hotkeys: Moving mouse over tray icon triggers pop-up menu to choose device to eject. But can trigger menu also with #u. In latter case, if there's only one removable device, the selection menu is skipped and the device is ejected automatically.

edit: fixed a few bugs, USBD_SafelyRemove now better integrated, and always used for "removable" (flash) drives.

#NoEnv
#SingleInstance, Force
SetBatchLines, -1
DetectHiddenWindows, On
SetTitleMatchMode, 2
SendMode, Input
CoordMode, Mouse, Screen

OnMessage(0x404, "AutoHotkey_Notify")
OnExit, ExitCleanUp

Menu, Tray, UseErrorLevel
Menu, Tray, Icon, Hotplug.dll, 2
Menu, Tray, Add,
Menu, Tray, Add, Show Removable Devices, PopUSB
Menu, USB, UseErrorLevel

Return

;opens tray menu/s when mouse is over icon and interprets clicks
AutoHotkey_Notify(wParam, lParam)
{
  static ignorenext       
  If ignorenext != True
  {  
    SetTimer, LeftClickTray, -1
    Sleep, 2000
    ignorenext:= False
  }
  Else If (lParam = 0x205) ;Right-click menu
    SetTimer, RightClickTray, -1
  Else If (lParam = 0x202) ;Left-click menu
  { 
    KeyWait, LButton, D T0.3  ; wait time for second left-click (i.e. double click)
    If ErrorLevel
      SetTimer, LeftClickTray, -1
  }
  Return 0
}

LeftClickTray:
  Goto, PopUSB
  
RightClickTray:
  Menu, Tray, Show
  Sleep, 2500
  ignorenext:= False
Return

#u::  ;hotkey to eject devices -> pop-up appears at mouse pointer
caller:= 1 ; auto-ejects device if only one present
Goto, PopUSB

PopUSB:
  DriveGet, RDRV, List, Removable ;note: includes floppy drives (A,B,...)
  ;StringReplace, RDRV, RDRV, A ; to remove floppy drives
  ;StringReplace, RDRV, RDRV, B
  ADRV:=RDRV
  
  DriveGet, FDRV, List, Fixed       ;get external and virtual hard drives
  StringReplace, FDRV, FDRV, C      ;remove fixed partitions
  StringReplace, FDRV, FDRV, X      ;exclude virtual drive
  StringReplace, FDRV, FDRV, Y      ;exclude network drive
  StringReplace, FDRV, FDRV, Z
  ADRV.=FDRV
  
  If StrLen(ADRV)=0
  {
    If WinExist("AutoPopCloser.ahk")
    PostMessage, 0x111, 65405,0,, %A_ScriptDir%\AutoPopCloser.ahk ahk_class AutoHotkey
    Return
  }
  Else
  {
    If (caller = 1) 
    {   ;skip menu if only one drive to eject and ejection was hotkey initiated
      If StrLen(ADRV) = 1
      {
        Drv:= ADRV ":", caller:= 2
        Goto, EjectUSB
      }
    }
  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 )
    DllCall( "shlwapi.dll\StrFormatByteSize64A", Int64,Capacity, Str,DiskSz, UInt,16 )
    Menu, USB, Add, &%A_LoopField%: %Label%`t%DiskSz%`t, EjectUSB
  }
    Menu, USB, Add, &Close Menu, MenuClose
    If !WinExist("AutoPopCloser.ahk")
      Run %A_ScriptDir%\AutoPopCloser.ahk
    Menu, USB, Show
  }
Return

; 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+
}

MenuClose:
  hWnd := WinExist( "ahk_class SystemTray_Main" )
  ControlSend,, {Esc}, ahk_id %hWnd%
  Sleep, 2000
  ignorenext:= True
Return

EjectUSB:
  Critical
  nStr := ""
  If caller != 2
    Drv:= SubStr( A_ThisMenuItem,2,1 ) ":"
    caller:= 0

  
  IfInString, RDRV, % SubStr( Drv,1,1 )
  {
    USBD_SafelyRemove( Drv )
    Return ;or Goto, EjectionCheck
  }
  
    hWnd := WinExist( "ahk_class SystemTray_Main" )
    CoordMode, Mouse, Screen  
    MouseGetPos, oX, oY
    MouseMove, %A_ScreenWidth%, %A_ScreenHeight%, 0
    PostMessage, 1226, 1226, 0x201,, ahk_id %hWnd% ; Left Click down
    PostMessage, 1226, 1226, 0x202,, ahk_id %hWnd% ; Left Click Up
    WinWaitActive, ahk_id %hWnd%,,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 %hWnd%
        Goto, EjectionCheck
      }
    }
Return

EjectionCheck:
Loop, 
{
  Sleep, 250
  If probID:= WinExist("Problem Ejecting")
    Goto, UnlockIt
  Else If !FileExist(Drv . "")
    Break
  If A_Index > 200
  { 
    MsgBox, Mystery USB Removal error
    Break
  }
}
try=
MouseMove, %oX%, %oY%, 0
Return

UnlockIt:
ControlClick, Button1, ahk_id %probID%
If try < 3 ;try again
{
  Sleep, 3000
  try++
  Goto, EjectUSB
}
try=
MsgBox,4,Device Locked, Do you want to run Unlocker? (Press YES or NO), 10
IfMsgBox Yes
  Run, %A_ProgramFiles%\Unlocker\Unlocker.exe %Drv%\
Return

ExitCleanUp:
If WinExist("AutoPopCloser.ahk")
  PostMessage, 0x111, 65405,0,, %A_ScriptDir%\AutoPopCloser.ahk ahk_class AutoHotkey
If IdnUSB:= getTrayIconIdn()
  HideTrayIcon( idnUSB, False )
ExitApp

Also requires AutoPopCloser.ahk in script directory to auto close the pop-up menu (like USB Safely Remove 4 does), and also removes the default 'Safely Remove Device' icon:

Note: Has no tray icon (default), but can exit it with ^Esc, and the main script closes it on exit or if no removable devices are attached.

#NoEnv
#SingleInstance force
#NoTrayIcon
;Menu, Tray, Icon, %A_ScriptDir%\icons\Ahk_Black.ico
DetectHiddenWindows, On		;Need it to detect/hide default 'Safely Remove Device' icon
SetTitleMatchMode, 2
CoordMode, Mouse, Screen
SetTimer, PopMenuCloser, 3000
Return

PopMenuCloser:
If hPop:= WinExist("ahk_class #32768")
{
	WinGet, procPop, processname, ahk_id %hPop%
	If (procPop = "AutoHotkey.exe")
	{
		MouseGetPos,,,mouseWin,,1
		If (hPop <> mouseWin)
			counter++
		Else counter=
		If (counter > 1)
			WinClose, ahk_id %hPop%
	}
}
If IdnUSB:= getTrayIconIdn()
  HideTrayIcon(idnUSB)
Return

~^Esc::
MsgBox, 4, Close AutoPopCloser, Click yes to exit, 5
IfMsgBox, Yes
	ExitApp
Return

Finally, this (getTrayIconIdn.ahk) needs to be in the library, or subbed in for getTrayIconIdn in the other scripts. It just gets the tray icons info from Sean's TrayIcons.ahk for a given process (explorer.exe in this case) and narrows it down to one based on tooltip text (in this Safely Remove Hardware):

getTrayIconIdn( exe="explorer.exe" ,tooltip="Safely Remove Hardware" )
{
  TI := TrayIcons( exe ), ttOffset:=-(StrLen(tooltip)-1)
  Loop, parse, TI,`n,`r
    If SubStr( TI:= A_LoopField, ttOffset) = tooltip
      RegExMatch(TI, "(?<=idn: )\d+", idn)
  Return idn
}

Note: Most of this script is from the code SKAN posted earlier. I just been tweaking and customizing it ever since. It works well for me, but if anyone else reading decides to try it, then you should specify which drives the script should ignore on your computer, and probably customize other aspects of it.
-I added menu-on-mouse-over feature.
-I added a subroutine to check if ejection was successful, and if it wasn't to try again several times, then ask if you want to run Unlocker.
-earlier I added the option to Unmount Virtual Images but dropped it because I thought it was better to do separately.

To do/bugs:
-The AutoPopCloser closes any ahk_class #32768 windows from AutoHotkey.exe process that aren't in use (mouse not over them). I haven't figured out how to nail it down to only tray menus from this specific AutoHotkey.exe process.
-Also, I cannot close the tray menu from within the parent script itself, hence the additional AutoPopCloser.ahk. I've been told it might be possible using 'pipes', but I haven't learned to use them yet. Also, Unlocker isn't able to detect all process that may be keeping the USB locked - maybe there's a way to use AHK to see all open handles or process on a USB device?

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


SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005

I'd say it's an impressive script, but at a glance of the code I have no clue of the method it uses.


I was happily using the command-line-utility DevEject in Windows XP until I found that it crashes in Windows Vista. I have taken ideas / adapted some of the code from USB Medium abmelden (sicheres Entfernen). You may also refer the C++ Source Code of DevEject. Neither of the code would work in Windows Vista as there is a difference in Registry Value stored at HKLM\SYSTEM\MountedDevices.

Is it equivalent to clicking on the tray icon to eject a USB device?


Yes!

In Windows XP, Safely Remove Hardware would power down the device.. i.e., the indicator LEDs on the device would go Off. That is case with my code too but not with DeviceIoControl() method described in AHK Documentation under Drive Command. CM_Request_Device_EjectA() is the documented method for Safe Removal, and I am calling it with least possible code I could ever code.

pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009

Is it equivalent to clicking on the tray icon to eject a USB device?


Yes!

In Windows XP, Safely Remove Hardware would power down the device.. i.e., the indicator LEDs on the device would go Off. That is case with my code too but not with DeviceIoControl() method described in AHK Documentation under Drive Command. CM_Request_Device_EjectA() is the documented method for Safe Removal, and I am calling it with least possible code I could ever code.


USBD_SafelyRemove is meant specifically for flash drives, right? Can it be extended to also cover external USB hard drives? Both are displayed in my 'Safely Remove Hardware' tray menu, and both can be ejected the same way from there, even though the latter are classified as Fixed per AHK's DriveGet command.

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


SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005

USBD_SafelyRemove is meant specifically for flash drives, right?


Yes! It is easier to enumerate Flash drive's DeviceID string from the registry instead walking through various DllCalls.

Can it be extended to also cover external USB hard drives? Both are displayed in my 'Safely Remove Hardware' tray menu, and both can be ejected the same way from there, even though the latter are classified as Fixed per AHK's DriveGet command.


The DeviceEject() function can Eject any Device ( not only drives ) if you provide the right DeviceID string. To enumerate and Identify the device involves a lot of trouble though.. and unfortunately, I do not have an USB HDD to test with. To understand DeviceID strings, you need Microsoft's DevCon Utility ( Devcon Direct Download )

If you run devcon from the command prompt like
C:\devcon find usb\* > devices.txt
then devices.txt would be like follows:

USB\ROOT_HUB\4&10C3B2A4&0                                   : USB Root Hub
USB\ROOT_HUB\4&26FCA6D4&0                                   : USB Root Hub
USB\ROOT_HUB\4&37690C5A&0                                   : USB Root Hub
USB\ROOT_HUB\4&574D1E&0                                     : USB Root Hub
USB\ROOT_HUB20\4&172B99F8&0                                 : USB Root Hub
USB\VID_04F3&PID_0212\5&192F622A&0&1                        : USB Human Interface Device
USB\VID_054C&PID_0010\5&21E739F&0&7                         : Sony DSC
USB\VID_058F&PID_6387\GDLL4HW4                              : USB Mass Storage Device
USB\VID_13FE&PID_1D00\5B6A17832696                          : USB Mass Storage Device
9 matching device(s) found.

"USB Mass Storage Device" are my two USB flash drives
"Sony DSC" is my digital camera
"USB Human Interface Device" is my USB Mouse

Say, if I wanted to Eject my Mouse, it is as simple as

DeviceEject( "[color=red]USB\VID_054C&PID_0010\5&21E739F&0&7[/color]" )

DeviceEject( DeviceID ) { ; www.autohotkey.com/forum/viewtopic.php?t=44873
 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 )
}
USB\VID_04F3&PID_0212\5&192F622A&0&1

I would have to physically pull-out and replug the USB mouse to get it working again. Likewise, I could Eject the "USB Root Hub" for safety reasons so that no USB devices work in my computer.

It will take me sometime, But I will try to exactly duplicate the "Safely Remove Hardware" menu

pajenn
  • Members
  • 391 posts
  • Last active: Feb 06 2015 07:57 AM
  • Joined: 07 Feb 2009

The DeviceEject() function can Eject any Device ( not only drives ) if you provide the right DeviceID string. To enumerate and Identify the device involves a lot of trouble though.. and unfortunately, I do not have an USB HDD to test with.

<snip>

It will take me sometime, But I will try to exactly duplicate the "Safely Remove Hardware" menu


I'm not sure if this applies to your ejection method, but as far as the technique of programmatically clicking on Safely Remove Hardware tray icon goes, I found that automatically dismissing any 'problem ejecting' messages and repeating the ejection attempt several times greatly improved its reliability.

I also noticed your function automatically closed a minimized explorer window within a flash drive I ejected (whereas the Safely Remove Hardware would have reported the drive locked [ -- edit: Safely Remove Hardware also closes minimized explorer windows]). With an open (active?) text file to boot it reported ejection was not possible. If it can see the process that's preventing ejection, I'd suggest to include the process name in the traytip message (so user knows what to close).

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