How to get COM object for embedded IE browser? - ruby

How to translate this function from AutoIt's IE.au3 UDF to Ruby? Intention is to use Watir with an Internet Explorer browser (embedded in another application).
The AutoIt function works fine but I prefer Watir (which is Ruby). I can get the handle of the embedded browser using ControlGetHandle(), which is not available from the AutoIt dll.
Below is the function to translate (also 2 others which I don't need).
;===============================================================================
;
; Function Name: __IEControlGetObjFromHWND()
; Description: Returns a COM Object Window reference to an embebedded Webbrowser control
; Parameter(s): $hWin - HWND of a Internet Explorer_Server1 control obtained for example:
; $hwnd = ControlGetHandle("MyApp","","Internet Explorer_Server1")
; Requirement(s): Windows XP, Windows 2003 or higher.
; Windows 2000; Windows 98; Windows ME; Windows NT may install the
; Microsoft Active Accessibility 2.0 Redistributable:
; http://www.microsoft.com/downloads/details.aspx?FamilyId=9B14F6E1-888A-4F1D-B1A1-DA08EE4077DF&displaylang=en
; Return Value(s): On Success - Returns DOM Window object
; On Failure - 0 and sets #ERROR = 1
; Author(s): Larry with thanks to Valik
;
;===============================================================================
Func __IEControlGetObjFromHWND(ByRef $hWin)
DllCall("ole32.dll", "int", "CoInitialize", "ptr", 0)
Local Const $WM_HTML_GETOBJECT = __IERegisterWindowMessage("WM_HTML_GETOBJECT")
Local Const $SMTO_ABORTIFHUNG = 0x0002
Local $lResult, $typUUID, $aRet, $oIE
MsgBox(0, "msg", $WM_HTML_GETOBJECT)
__IESendMessageTimeout($hWin, $WM_HTML_GETOBJECT, 0, 0, $SMTO_ABORTIFHUNG, 1000, $lResult)
$typUUID = DllStructCreate("int;short;short;byte[8]")
DllStructSetData($typUUID, 1, 0x626FC520)
DllStructSetData($typUUID, 2, 0xA41E)
DllStructSetData($typUUID, 3, 0x11CF)
DllStructSetData($typUUID, 4, 0xA7, 1)
DllStructSetData($typUUID, 4, 0x31, 2)
DllStructSetData($typUUID, 4, 0x0, 3)
DllStructSetData($typUUID, 4, 0xA0, 4)
DllStructSetData($typUUID, 4, 0xC9, 5)
DllStructSetData($typUUID, 4, 0x8, 6)
DllStructSetData($typUUID, 4, 0x26, 7)
DllStructSetData($typUUID, 4, 0x37, 8)
MsgBox(0, "lResult", $lResult)
$aRet = DllCall("oleacc.dll", "long", "ObjectFromLresult", "lresult", $lResult, "ptr", DllStructGetPtr($typUUID), _
"wparam", 0, "idispatch*", 0)
MsgBox(0, "aRet4", $aRet[4])
If IsObj($aRet[4]) Then
$oIE = $aRet[4] .Script()
; $oIE is now a valid IDispatch object
Return $oIE.Document.parentwindow
Else
SetError(1)
Return 0
EndIf
EndFunc ;==>__IEControlGetObjFromHWND
;===============================================================================
; Function Name: __IERegisterWindowMessage()
; Description: Required by __IEControlGetObjFromHWND()
; Author(s): Larry with thanks to Valik
;===============================================================================
Func __IERegisterWindowMessage($sMsg)
Local $aRet = DllCall("user32.dll", "int", "RegisterWindowMessage", "str", $sMsg)
If #error Then Return SetError(#error, #extended, 0)
Return $aRet[0]
EndFunc ;==>__IERegisterWindowMessage
;===============================================================================
; Function Name: __IESendMessageTimeout()
; Description: Required by __IEControlGetObjFromHWND()
; Author(s): Larry with thanks to Valik
;===============================================================================
Func __IESendMessageTimeout($hWnd, $msg, $wParam, $lParam, $nFlags, $nTimeout, ByRef $vOut, $r = 0, $t1 = "int", $t2 = "int")
Local $aRet
$aRet = DllCall("user32.dll", "long", "SendMessageTimeout", "hwnd", $hWnd, "int", $msg, $t1, $wParam, _
$t2, $lParam, "int", $nFlags, "int", $nTimeout, "int*", "")
If #error Then
$vOut = 0
Return SetError(#error, #extended, 0)
EndIf
$vOut = $aRet[7]
If $r >= 0 And $r <= 4 Then Return $aRet[$r]
Return $aRet
EndFunc ;==>__IESendMessageTimeout
My code so far:
def get_control_from_hwnd(hnd)
Win32API.new("ole32", "CoInitialize", ['P'] , 'I').call(0)
reg_msg = Win32API.new("user32", "RegisterWindowMessage", ['P'] ,'I').call("WM_HTML_GETOBJECT")
puts "msg: " + reg_msg.to_s
result=" "*16
aInt = [0xA7, 0x31, 0x0, 0xA0, 0xC9, 0x8, 0x26, 0x37].pack 'I*'
a = [0x626FC520, 0xA41E, 0x11CF, aInt].pack 'IIIP'
sendMessagetimeout = Win32API.new("user32", "SendMessageTimeout", ['L','I','I','I','I','I','P'] , 'L')
sendMessagetimeout.call(hnd.hex, reg_msg, 0, 0, SMTO_ABORTIFHUNG, 1000, result)
puts "result unpacked: " + result.unpack("L").to_s #i can confirm this is the same as the lResult from the autoit functioin
idisp=0
#the problem is likely either here or the next line afterwards
oIE = Win32API.new("oleacc", "ObjectFromLresult", ['P','P','I','P'] , 'L')
oIE.call(result, a, 0, idisp)
puts "idisp: " + idisp.to_s
# returning zero
puts idisp.unpack("L")
end

Can't believe we are doing the same, albeit I'm trying to do it in Perl. I dunno Ruby but looking at the script, it almost looks like similar to Perl. Take a look at this thread at autoIt, where I am still trying to tackle the issue http://www.autoitscript.com/forum/index.php?showtopic=104894. But I think I might have gotten the idisp though. Two changes:
1) The way I am packing the struct is a bit different. My $iid looks like this:
pack('LSSC8',0x626FC520,0xA41E,0x11CF,0xA7,0x31,0x00,0xA0,0xC9,0x08,0x26,0x37). So in Ruby I guess it'd be [0x626FC520,0xA41E,0x11CF,0xA7,0x31,0x00,0xA0,0xC9,0x08,0x26,0x37].pack 'LSSC8'? I dunno the right syntax, but you get the idea.
2) I unpack the "result" from SendMessageTimeout & then pass it to ObjectFromLresult. If I pass the result directly, I get 0 as you were getting it.
But that is as far as I've gone.

Related

How can i style my GUI as Aero Glass GUI with AutoIt?

How can i get the Aero Glass effect for my Autoit GUI?
I am playing a bit with AutoIt to increase my knowledge about GUI stuff. Usually i just create scripts without the usage of a GUI, but i would like to have a nice looking areo glass GUI when i just start to work with.
I already experiment with WinSetTrans but this is not exactly what i want. It should look more like the image below.
My current code is:
#include <GUIConstants.au3>
$iWidthGui = 450
$iHeightGui = 300
$hGui = GUICreate("Glass GUI", $iWidthGui, $iHeightGui, -1, -1, -1, $WS_EX_TOPMOST)
$cExit = GUICtrlCreateButton("Exit", $iWidthGui / 2 - 50, $iHeightGui / 2 - 15, 100, 30)
GUISetState( #SW_SHOW )
WinSetTrans($hGui, "", 180)
While 1
Switch GUIGetMsg()
Case $GUI_EVENT_CLOSE, $cExit
GUIDelete($hGui)
ExitLoop
EndSwitch
WEnd
Is it possible with Autoit? How can i do that?
Yes it is possible. This should work at least for Windows 7. I couldn't test the script on a Windows 10 machine.
Improved code:
#include-once
#include <GUIConstants.au3>
Global $iWidthGui = 450
Global $iHeightGui = 300
Global $hGui = GUICreate("Glass GUI", $iWidthGui, $iHeightGui, -1, -1, -1, $WS_EX_TOPMOST)
Global $cExit = GUICtrlCreateButton("Exit", $iWidthGui / 2 - 50, $iHeightGui / 2 - 15, 100, 30)
GUISetState( #SW_SHOW, $hGui )
Func _aeroGlassEffect( $hWnd, $iLeft = #DesktopWidth, $iRight = #DesktopWidth, $iTop = #DesktopWidth, $iBottom = #DesktopWidth )
$hStruct = DllStructCreate( 'int left; int right; int height; int bottom;' )
DllStructSetData( $hStruct, 'left', $iLeft )
DllStructSetData( $hStruct, 'right', $iRight )
DllStructSetData( $hStruct, 'height', $iTop )
DllStructSetData( $hStruct, 'bottom', $iBottom )
GUISetBkColor( '0x000000' )
Return DllCall( 'dwmapi.dll', 'int', 'DwmExtendFrameIntoClientArea', 'hWnd', $hWnd, 'ptr', DllStructGetPtr( $hStruct ) )
EndFunc
_aeroGlassEffect( $hGui )
While 1
Switch GUIGetMsg()
Case $GUI_EVENT_CLOSE, $cExit
GUIDelete($hGui)
ExitLoop
EndSwitch
WEnd
I switched WinSetTrans() for _aeroGlassEffect(). You can change the function parameters $iLeft, $iRight, $iTop, $iBottom.

How do I move Windows desktop icons using python 2.7?

I am trying to write a python routine to save and restore desktop icon positions. I am using 32-bit python 2.7 on Windows 7 x64. Using information from here (stack exchange), I am able to read the icon names and positions from the foreign process list view that Windows uses to store this info, but I fail when using LVM_SETITEMPOSITION to set new positions (or restore the positions). All of the icons end up in the same exact spot on the desktop. 'Auto arrange' and 'align to grid' are off. The relevant code is towards the bottom. WARNING: if you run this code all of your icons will be in a pile :(
import ctypes
class LVITEMW(ctypes.Structure):
_fields_ = [
('mask', ctypes.c_uint32),
('iItem', ctypes.c_int32),
('iSubItem', ctypes.c_int32),
('state', ctypes.c_uint32),
('stateMask', ctypes.c_uint32),
('pszText', ctypes.c_uint64),
('cchTextMax', ctypes.c_int32),
('iImage', ctypes.c_int32),
('lParam', ctypes.c_uint64), # On 32 bit should be c_long
('iIndent',ctypes.c_int32),
('iGroupId', ctypes.c_int32),
('cColumns', ctypes.c_uint32),
('puColumns', ctypes.c_uint64),
('piColFmt', ctypes.c_int64),
('iGroup', ctypes.c_int32),
]
class POINT(ctypes.Structure):
_fields_ = [('x', ctypes.c_int), ('y', ctypes.c_int)]
def icon_save_restore(savedicons=None, restore=False):
import struct, commctrl, win32gui, win32con, win32api
dthwnd = win32gui.FindWindow(None, 'Program Manager')
ukhwnd = win32gui.GetWindow(dthwnd, win32con.GW_CHILD)
slvhwnd = win32gui.GetWindow(ukhwnd, win32con.GW_CHILD)
pid = ctypes.create_string_buffer(4)
p_pid = ctypes.addressof(pid)
ctypes.windll.user32.GetWindowThreadProcessId(slvhwnd, p_pid)
hProcHnd = ctypes.windll.kernel32.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, struct.unpack("i",pid)[0])
pBuffertxt = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, 4096, win32con.MEM_RESERVE|win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
copied = ctypes.create_string_buffer(4)
p_copied = ctypes.addressof(copied)
lvitem = LVITEMW()
lvitem.mask = ctypes.c_uint32(commctrl.LVIF_TEXT)
lvitem.pszText = ctypes.c_uint64(pBuffertxt)
lvitem.cchTextMax = ctypes.c_int32(4096)
lvitem.iSubItem = ctypes.c_int32(0)
pLVI = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, 4096, win32con.MEM_RESERVE| win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
win32api.SetLastError(0)
ctypes.windll.kernel32.WriteProcessMemory(hProcHnd, pLVI, ctypes.addressof(lvitem), ctypes.sizeof(lvitem), p_copied)
num_items = win32gui.SendMessage(slvhwnd, commctrl.LVM_GETITEMCOUNT)
if restore is False:
p = POINT()
pBufferpnt = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, ctypes.sizeof(p), win32con.MEM_RESERVE|win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
icons = {}
for i in xrange(num_items):
# Get icon text
win32gui.SendMessage(slvhwnd, commctrl.LVM_GETITEMTEXT, i, pLVI)
target_bufftxt = ctypes.create_string_buffer(4096)
ctypes.windll.kernel32.ReadProcessMemory(hProcHnd, pBuffertxt, ctypes.addressof(target_bufftxt), 4096, p_copied)
key = target_bufftxt.value
# Get icon position
win32api.SendMessage(slvhwnd, commctrl.LVM_GETITEMPOSITION, i, pBufferpnt)
p = POINT()
ctypes.windll.kernel32.ReadProcessMemory(hProcHnd, pBufferpnt, ctypes.addressof(p), ctypes.sizeof(p), p_copied)
icons[key] = (i,p)
ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pLVI, 0, win32con.MEM_RELEASE)
ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBuffertxt, 0, win32con.MEM_RELEASE)
ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBufferpnt, 0, win32con.MEM_RELEASE)
win32api.CloseHandle(hProcHnd)
return icons
else: # RESTORE ICON POSITIONS PROBLEM IS HERE SOMEWHERE!!!
win32gui.SendMessage(slvhwnd, win32con.WM_SETREDRAW, 0, 0)
for i in xrange(num_items):
# Get icon text
win32gui.SendMessage(slvhwnd, commctrl.LVM_GETITEMTEXT, i, pLVI)
target_bufftxt = ctypes.create_string_buffer(4096)
ctypes.windll.kernel32.ReadProcessMemory(hProcHnd, pBuffertxt, ctypes.addressof(target_bufftxt), 4096, p_copied)
key = target_bufftxt.value
if key in savedicons.keys():
# Set icon position
p = savedicons[key][1] # p is ctypes POINT
p_lng = point_to_long(p) # explicitly convert to HIWORD/LOWORD and c_long
# Reserve space for input variable in foreign process and get pointer to it the that memory space
pBufferpnt = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, ctypes.sizeof(p_lng), win32con.MEM_RESERVE|win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
# Write the desired coordinates in to the space just created
ret = ctypes.windll.kernel32.WriteProcessMemory(hProcHnd, pBufferpnt, ctypes.addressof(p_lng), ctypes.sizeof(p_lng), p_copied)
if ret == 0:
raise WindowsError
# Send the message to change the position for that item's index and the pointer to the new position
ret = win32gui.SendMessage(slvhwnd, commctrl.LVM_SETITEMPOSITION, i, pBufferpnt)
if ret == 0:
raise WindowsError
# Release the reserved memory for the variable (I recognize that I probably don't need to aLloc/free this within the loop)
ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBufferpnt, 0, win32con.MEM_RELEASE)
win32gui.SendMessage(slvhwnd, win32con.WM_SETREDRAW, 1, 0)
ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pLVI, 0, win32con.MEM_RELEASE)
ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBuffertxt, 0, win32con.MEM_RELEASE)
win32api.CloseHandle(hProcHnd)
return None
def point_to_long(p):
ret = (p.y * 0x10000) + (p.x & 0xFFFF)
return ctypes.c_long(ret)
if __name__ == '__main__':
mysavedicons = icon_save_restore(restore=False)
icon_save_restore(mysavedicons, restore=True)
I think there may be a problem with either 1) something to do with 32 bit and 64 bit memory address space, but the other components where I write the LVITEM structure or read the icon text work ok or 2) there is some issue in the way I am converting the coordinate information or calling SendMessage for GETITEMPOSITION. Any insight or help would be greatly appreciated.
Turns out that there is version that uses 32-bit addresses (LVM_SETITEMPOSITION32) that I wish MSDN would have cross-referenced in their documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/bb761194(v=vs.85).aspx
This accepts the POINT structure directly so there is no need to try to convert to HIWORD/LOWORD. Before posting, I did indeed try using a 32 bit shift and longlong (64 bit version of long) and that didn't work either with LVM_SETITEMPOSITION. In any case, with the change, everything works as expected.

Create new folder and highlight it for renaming

I was trying to create an AutoIt script to create a new folder and then highlight it for renaming like if we right click and create a new folder.
Here is my script. What I wanted to achieve is create the new folder, highlight it, then press F2.
#include <WinAPI.au3>
HotKeySet("#y", "NewFolder")
While 1
Sleep(200)
WEnd
Func NewFolder()
Local $hWnd = WinGetHandle("[ACTIVE]")
Local $class = _WinAPI_GetClassName($hWnd)
If StringCompare($class, "CabinetWClass") = 0 Then
Local $sSelected_Path = _GetWindowsExplorerPath($hWnd) & "\NewFolder" & $count
Local $iFileExists = FileExists($sSelected_Path)
If $iFileExists Then
$count += 1
Else
Local $success = DirCreate($sSelected_Path)
Sleep(500)
If $success = 1 Then
Local $Finditem = ControlListView($hWnd, "", "[CLASS:SysListView32; INSTANCE:1]", "Finditem", "NewFolder")
MsgBox(0, "", $Finditem)
Local $Select = ControlListView($hWnd, "", "[CLASS:SysListView32; INSTANCE:1]", "Select", $Finditem)
$count += 1
EndIf
EndIf
EndIf
EndFunc
Func _GetWindowsExplorerPath($hWnd)
Local $pv, $pidl, $return = "", $ret, $hMem, $pid, $folderPath = DllStructCreate("char[260]"), $className
Local $bPIDL = False
Local Const $CWM_GETPATH = 0x400 + 12;
; Check the classname of the window first
$className = DllCall("user32.dll", "int", "GetClassName", "hwnd", $hWnd, "str", "", "int", 4096)
If #error Then Return SetError(2, 0, "")
If ($className[2] <> "ExploreWClass" And $className[2] <> "CabinetWClass") Then Return SetError(1, 0, "")
; Retrieve the process ID for our process
$pid = DllCall("kernel32.dll", "int", "GetCurrentProcessId")
If #error Then Return SetError(2, 0, "")
; Send the CWM_GETPATH message to the window
$hMem = DllCall("user32.dll", "lparam", "SendMessage", "hwnd", $hWnd, "int", $CWM_GETPATH, "wparam", $pid[0], "lparam", 0)
If #error Then Return SetError(2, 0, "")
If $hMem[0] = 0 Then Return SetError(1, 0, "")
; Lock the shared memory
$pv = DllCall("shell32.dll", "ptr", "SHLockShared", "uint", $hMem[0], "uint", $pid[0])
If #error Then Return SetError(2, 0, "")
If $pv[0] Then
$pidl = DllCall("shell32.dll", "ptr", "ILClone", "uint", $pv[0]) ; Clone the PIDL
If #error Then Return SetError(2, 0, "")
$bPIDL = True
DllCall("shell32.dll", "int", "SHUnlockShared", "uint", $pv) ; Unlock the shared memory
EndIf
DllCall("shell32.dll", "int", "SHFreeShared", "uint", $hMem, "uint", $pid) ; Free the shared memory
If $bPIDL Then
; Retrieve the path from the PIDL
$ret = DllCall("shell32.dll", "int", "SHGetPathFromIDList", "ptr", $pidl[0], "ptr", DllStructGetPtr($folderPath))
If (#error = 0) And ($ret[0] <> 0) Then $return = DllStructGetData($folderPath, 1) ; Retrieve the value
DllCall("shell32.dll", "none", "ILFree", "ptr", $pidl[0]) ; Free up the PIDL that we cloned
Return SetError(0, 0, $return) ; Success
EndIf
Return SetError(2, 0, "") ; Failed a WinAPI call
EndFunc
This works for me in Win7 - should work in xp as long as address bar is turned on. MUCH simpler than using the DLL calls. I left you a little bit of work to do :)
HotKeySet("#y","_NewFolder")
HotKeySet("#n","_EXIT")
While 1
Sleep(250)
WEnd
FUNC _NewFolder() ; make all vars local
$TEXT = WinGetText("[active]")
; handle error here
$SPLIT = StringSplit($TEXT,#CR & #LF & #CRLF) ;split on new lines (idk which one of the three it uses)
IF IsArray($SPLIT) Then
; trigger = true
FOR $I = 1 to $SPLIT[0]
IF StringInStr($SPLIT[$i],"Address: ") Then
; trigger = false
$STRING = StringReplace($SPLIT[$i],"Address: ","")
$INPUT = InputBox("New Folder","Name your new folder")
; add some enforcement to input box, i like do until loops
$PATH = $STRING & "\" & $INPUT
$CREATE = DirCreate($PATH)
; handle error here
Return SetError(0,0,$PATH)
EndIf
Next
; if trigger then error
Else
Return SetError(1,0,0) ;if split is not an array - could add some more here in case address is the only line returned
EndIf
EndFunc
Func _Exit()
Exit
EndFunc
"create a new folder and then highlight it for renaming like its supposed to"
As per Documentation - Function Reference - DirCreate() :
Creates a directory/folder.
Example:
Global Const $g_sPathFolder = #DesktopDir & '\'
Global Const $g_sPromptText = 'Enter folder name :'
Global Const $g_sPromptExmp = 'NewFolder'
Global $g_sNameFolder = InputBox(#ScriptName, $g_sPromptText, $g_sPromptExmp)
DirCreate($g_sPathFolder & $g_sNameFolder)
ShellExecute($g_sPathFolder & $g_sNameFolder)

Using Perl, how can obtain information about the icons in Windows' taskbar notification area?

I am trying to write a script in Perl to read all the icons in the system tray, grab their co-ordinates & find out who owns them. I am pretty much trying to translate this code here.
Here is my code so far:
use strict;
use warnings;
use Win32::API;
use Win32::OLE qw(in);
use Data::Dumper;
use constant wbemFlagReturnImmediately => 0x10;
use constant wbemFlagForwardOnly => 0x20;
use constant SYNCHRONIZE => 0x00100000;
use constant STANDARD_RIGHTS_REQUIRED => 0x000F0000;
use constant PROCESS_ALL_ACCESS => (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF);
my $TB_BUTTONCOUNT = 0x0418;
my $TB_GETBUTTONTEXT = 0x041B;
my $TB_GETBUTTONINFO = 0x0441;
my $TB_GETITEMRECT = 0x041D;
my $TB_GETBUTTON = 0x0417;
sub get_windows_details {
my ($self) = #_;
my $ret;
my $objWMIService =
Win32::OLE->GetObject("winmgmts:\\\\localhost\\root\\CIMV2")
or die "WMI connection failed.\n";
my $colItems =
$objWMIService->ExecQuery("SELECT * FROM Win32_OperatingSystem",
"WQL",
wbemFlagReturnImmediately | wbemFlagForwardOnly);
my $objItem;
foreach $objItem (in $colItems) {
$ret->{'osname'} = $objItem->{Caption};
}
$colItems =
$objWMIService->ExecQuery("SELECT * FROM Win32_Processor",
"WQL",
wbemFlagReturnImmediately | wbemFlagForwardOnly);
foreach $objItem (in $colItems) {
$ret->{'osbit'} = $objItem->{AddressWidth};
}
return $ret;
}
sub get_autoit_tray_handle {
my $autoit = Win32::OLE->new("AutoItX3.Control")
or return 0;
my $tray_hwnd = $autoit->ControlGetHandle("[Class:Shell_TrayWnd]", "", "[Class:ToolbarWindow32;Instance:1]");
return hex $tray_hwnd;
}
sub get_tray_icon_count {
#my $hWnd = get_tray_handle();
my $hWnd = get_autoit_tray_handle();
my $send_message = Win32::API->new("user32", "SendMessage", "NNII", "I");
return $send_message->Call($hWnd, $TB_BUTTONCOUNT, 0, 0);
}
# Randomly chosen icon index.
my $iIndex = 6;
my $os = get_windows_details();
if ($os->{'osbit'} == 64) {
Win32::API::Struct->typedef('TBBUTTON', qw { int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
BYTE bReserved[6];
DWORD_PTR dwData;
INT_PTR iString;
}
) or die "Typedef error $!\n";
} else {
Win32::API::Struct->typedef('TBBUTTON', qw { int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
BYTE bReserved[2];
DWORD_PTR dwData;
INT_PTR iString;
}
) or die "Typedef error $!\n";
}
# Get tray handle & it's proc id
my $tb_button = Win32::API::Struct->new('TBBUTTON');
my $tray_hwnd = get_autoit_tray_handle();
print "tray hwnd: $tray_hwnd\n";
my $window_thread_proc_id = Win32::API->new('user32', "GetWindowThreadProcessId", 'LP', 'N');
my $lpdwPID = pack 'L', 0;
my $pid = $window_thread_proc_id->Call($tray_hwnd, $lpdwPID);
my $dwPID = unpack 'L', $lpdwPID;
print "proc id: $dwPID\n";
# read the tray process memory to get the tray button info
my $open_process = Win32::API->new('kernel32', 'OpenProcess', 'NIN', 'N') || die $!;
my $proc_hwnd = $open_process->Call(PROCESS_ALL_ACCESS, 0, $dwPID);
print "proc hwnd: $proc_hwnd\n";
my $virtual_alloc = Win32::API->new('kernel32', 'VirtualAllocEx', 'NNLNN', 'N');
my $lp_data = $virtual_alloc->Call($proc_hwnd, 0, $tb_button->sizeof(), 0x1000, 0x04);
print "Error allocating memory: $!\n" if $!;
print "Allocated addresss: $lp_data\n";
my $send_message = Win32::API->new('user32', 'SendMessage', 'NNIN','I');
my $get_button_status = $send_message->Call($tray_hwnd, $TB_GETBUTTON, $iIndex, $lp_data);
print "TB_GETBUTTON Status: $get_button_status\n";
my $read_process = Win32::API->new('kernel32', 'ReadProcessMemory', 'NNSNP','I');
my $bytes_read = pack 'L', 0;
$read_process->Call($proc_hwnd, $lp_data, $tb_button, $tb_button->sizeof(), $bytes_read);
print "dwData: $tb_button->{'dwData'} \n";
I am using autoit COM DLL to get the system tray handle. Once I have the have the tray handle, I try to get it's process id & then read the process memory to get the TBBUTTON structure, which is defined as follows:
if ($os->{'osbit'} == 64) {
Win32::API::Struct->typedef('TBBUTTON', qw { int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
BYTE bReserved[6];
DWORD_PTR dwData;
INT_PTR iString;
}
) or die "Typedef error $!\n";
} else {
Win32::API::Struct->typedef('TBBUTTON', qw { int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
BYTE bReserved[2];
DWORD_PTR dwData;
INT_PTR iString;
}
) or die "Typedef error $!\n";
}
When you execute the above code, at least on my system, here is the output I see:
tray hwnd: 401922
proc id: 11040
proc hwnd: 704
Allocated addresss: 32702464
TB_GETBUTTON Status: 1
dwData: 10293610267052867588
As you can see - the "dwData" seems to be wrong. Looks like I'm doing something wrong here:
my $read_process = Win32::API->new('kernel32', 'ReadProcessMemory', 'NNSNP','I');
my $bytes_read = pack 'L', 0;
$read_process->Call($proc_hwnd, $lp_data, $tb_button, $tb_button->sizeof(), $bytes_read);
print "dwData: $tb_button->{'dwData'} \n";
Any suggestions on what I'm doing wrong there? Thanks.
I decided to try to minimize the amount of uncertainty introduced by the various things you are pulling in, and just use the functionality provided by Win32::GuiTest. I also cheated with regard to bitness and structs so as to get something running on my 32-bit WinXP SP3 laptop. Here's something that runs and produces some output.
I am not sure if this is the right output, but it should at least point you in a simpler direction:
#!/usr/bin/env perl
use feature 'say';
use strict; use warnings;
use Const::Fast;
use Devel::CheckOS;
use Win32::GuiTest qw(
AllocateVirtualBuffer
FreeVirtualBuffer
ReadFromVirtualBuffer
FindWindowLike
SendMessage
);
use YAML;
const my %TB => (
BUTTONCOUNT => 0x0418,
GETBUTTONTEXT => 0x041B,
GETBUTTONINFO => 0x0441,
GETITEMRECT => 0x041D,
GETBUTTON => 0x0417,
);
const my %TBUTTON => (
32 => 'iiCCCCLL',
64 => 'iiCCCCCCCCLL',
);
my ($tray_handle) = FindWindowLike(undef, undef, 'TrayNotifyWnd');
my ($toolbar_handle) = FindWindowLike($tray_handle, undef, 'ToolbarWindow');
say for ($tray_handle, $toolbar_handle);
my $button_count = SendMessage($toolbar_handle, $TB{BUTTONCOUNT}, 0, 0);
unless (defined($button_count) and $button_count > 0) {
die "Can't find buttons\n"
}
my $buf = AllocateVirtualBuffer($toolbar_handle, 0x20);
print Dump $buf;
my $index = int(rand $button_count);
say "Trying button = $index\n";
my $status = SendMessage(
$toolbar_handle,
$TB{GETBUTTON},
$index,
$buf->{ptr}
);
say "TB_GETBUTTON status = $status";
my $result = ReadFromVirtualBuffer($buf, 0x20);
FreeVirtualBuffer($buf);
print Dump [ map sprintf('%X', $_), unpack $TBUTTON{32}, $result ];
Also, not that you shoud define things like Win32::API functions and structs in one place and only once.
Sample output:
655544
393294
---
process: 1920
ptr: 28835840
Trying button = 19
TB_GETBUTTON status = 1
---
- 7
- 9
- C
- 0
- 0
- 0
- 1DA23C8
- 2B70590

Best way to wrap rsync progress in a gui?

I use rsync to synchronize files to Windows clients in a server agnostic way. What methods are available to send the progress of rsync to the parent process for display in a gui progress bar?
I imagine two or three choices exist. (1) Watch STDOUT (2) Watch rsync.exe log file, similar to unix tail (3) Watch rsync console output in memory.
Which one is best/preferred?
For this type of tasks, I use my own AutoIt script (freeware, Windows only). The script redirects the standard output into a graphical window, displaying it with the ability to scroll back, etc (very useful in long processes like XCOPYs / PKZIPs to check if any error did happen).
I use AutoIt because it's free, very easy to use, and can compile quickly into an .EXE. I think it's an excellent alternative to a complete programming language for this type of tasks. The downside is that it's for Windows only.
$sCmd = "DIR E:\*.AU3 /S" ; Test command
$nAutoTimeout = 10 ; Time in seconds to close window after finish
$nDeskPct = 60 ; % of desktop size (if percent)
; $nHeight = 480 ; height/width of the main window (if fixed)
; $nWidth = 480
$sTitRun = "Executing process. Wait...." ;
$sTitDone = "Process done" ;
$sSound = #WindowsDir & "\Media\Ding.wav" ; End Sound
$sButRun = "Cancel" ; Caption of "Exec" button
$sButDone = "Close" ; Caption of "Close" button
#include <GUIConstants.au3>
#include <Constants.au3>
#Include <GuiList.au3>
Opt("GUIOnEventMode", 1)
if $nDeskPct > 0 Then
$nHeight = #DesktopHeight * ($nDeskPct / 100)
$nWidth = #DesktopWidth * ($nDeskPct / 100)
EndIf
If $CmdLine[0] > 0 Then
$sCmd = ""
For $nCmd = 1 To $CmdLine[0]
$sCmd = $sCmd & " " & $CmdLine[$nCmd]
Next
; MsgBox (1,"",$sCmd)
EndIf
; AutoItSetOption("GUIDataSeparatorChar", Chr(13)+Chr(10))
$nForm = GUICreate($sTitRun, $nWidth, $nHeight)
GUISetOnEvent($GUI_EVENT_CLOSE, "CloseForm")
$nList = GUICtrlCreateList ("", 10, 10, $nWidth - 20, $nHeight - 50, $WS_BORDER + $WS_VSCROLL)
GUICtrlSetFont (-1, 9, 0, 0, "Courier New")
$nClose = GUICtrlCreateButton ($sButRun, $nWidth - 100, $nHeight - 40, 80, 30)
GUICtrlSetOnEvent (-1, "CloseForm")
GUISetState(#SW_SHOW) ;, $nForm)
$nPID = Run(#ComSpec & " /C " & $sCmd, ".", #SW_HIDE, $STDOUT_CHILD)
; $nPID = Run(#ComSpec & " /C _RunErrl.bat " & $sCmd, ".", #SW_HIDE, $STDOUT_CHILD) ; # Con ésto devuelve el errorlevel en _ERRL.TMP
While 1
$sLine = StdoutRead($nPID)
If #error Then ExitLoop
If StringLen ($sLine) > 0 then
$sLine = StringReplace ($sLine, Chr(13), "|")
$sLine = StringReplace ($sLine, Chr(10), "")
if StringLeft($sLine, 1)="|" Then
$sLine = " " & $sLine
endif
GUICtrlSetData ($nList, $sLine)
_GUICtrlListSelectIndex ($nList, _GUICtrlListCount ($nList) - 1)
EndIf
Wend
$sLine = " ||"
GUICtrlSetData ($nList, $sLine)
_GUICtrlListSelectIndex ($nList, _GUICtrlListCount ($nList) - 1)
GUICtrlSetData ($nClose, $sButDone)
WinSetTitle ($sTitRun, "", $sTitDone)
If $sSound <> "" Then
SoundPlay ($sSound)
EndIf
$rInfo = DllStructCreate("uint;dword") ; # LASTINPUTINFO
DllStructSetData($rInfo, 1, DllStructGetSize($rInfo));
DllCall("user32.dll", "int", "GetLastInputInfo", "ptr", DllStructGetPtr($rInfo))
$nLastInput = DllStructGetData($rInfo, 2)
$nTime = TimerInit()
While 1
If $nAutoTimeout > 0 Then
DllCall("user32.dll", "int", "GetLastInputInfo", "ptr", DllStructGetPtr($rInfo))
If DllStructGetData($rInfo, 2) <> $nLastInput Then
; Tocó una tecla
$nAutoTimeout = 0
EndIf
EndIf
If $nAutoTimeout > 0 And TimerDiff ($nTime) > $nAutoTimeOut * 1000 Then
ExitLoop
EndIf
Sleep (100)
Wend
Func CloseForm()
Exit
EndFunc
.NET has a pretty straight forward way to read and watch STDOUT.
I guess this would be the cleanest way, since it is not dependent on any external files, just the path to rsync. I would not be too surprised if there is a wrapper library out there either. If not, write and open source it :)
I've built my own simple object for this, I get a lot of reuse out of it, I can wrap it with a cmdline, web page, webservice, write output to a file, etc---
The commented items contain some rsync examples--
what I'd like to do sometime is embed rsync (and cygwin) into a resource & make a single .net executable out of it--
Here you go:
Imports System.IO
Namespace cds
Public Class proc
Public _cmdString As String
Public _workingDir As String
Public _arg As String
Public Function basic() As String
Dim sOut As String = ""
Try
'Set start information.
'Dim startinfo As New ProcessStartInfo("C:\Program Files\cwRsync\bin\rsync", "-avzrbP 192.168.42.6::cdsERP /cygdrive/s/cdsERP_rsync/gwy")
'Dim startinfo As New ProcessStartInfo("C:\Program Files\cwRsync\bin\rsync", "-avzrbP 10.1.1.6::user /cygdrive/s/cdsERP_rsync/gws/user")
'Dim startinfo As New ProcessStartInfo("C:\windows\system32\cscript", "//NoLogo c:\windows\system32\prnmngr.vbs -l")
Dim si As New ProcessStartInfo(_cmdString, _arg)
si.UseShellExecute = False
si.CreateNoWindow = True
si.RedirectStandardOutput = True
si.RedirectStandardError = True
si.WorkingDirectory = _workingDir
' Make the process and set its start information.
Dim p As New Process()
p.StartInfo = si
' Start the process.
p.Start()
' Attach to stdout and stderr.
Dim stdout As StreamReader = p.StandardOutput()
Dim stderr As StreamReader = p.StandardError()
sOut = stdout.ReadToEnd() & ControlChars.NewLine & stderr.ReadToEnd()
'Dim writer As New StreamWriter("out.txt", FileMode.CreateNew)
'writer.Write(sOut)
'writer.Close()
stdout.Close()
stderr.Close()
p.Close()
Catch ex As Exception
sOut = ex.Message
End Try
Return sOut
End Function
End Class
End Namespace
Check out DeltaCopy. It is a Windows GUI for rsync.
Check NAsBackup Its open source software that give Windows user Rsync GUI using Watch STDOUT.

Resources