EDIT:-
Added some new commands:-
; "FIRST-PAGE" - Open a new Chrome browser tab page (CONTROL-1).
; "NEXT-PAGE" - Move to the next Chrome browser tab page (CONTROL-TAB).
; "CLOSE-PAGE" - Close the current Chrome browser tab page (CONTROL-W).
; "TAB-DELAY" - Sets the millisecond delay to use between multiple tabs being sent. Defaults to 100 if no parameter is supplied, or if the parameter is less than 100.
; "TAB-BACK-DELAY" - Sets the millisecond delay to use between multiple tabs being sent. Defaults to 100 if no parameter is supplied, or if the parameter is less than 100.
Also updated the 'OpenFileUploadDialog()' function with some additional filtering to ensure that it is grabbing the handle of the correct Open file requester window. It was giving me trouble when I tried using the app this library is for on my laptop.
Code: Select all
; - Send keyboard input to Google Chrome browser using SendInput.
; Use SendKeys( keys.s, handle = 0 ) to send a comma separated list of keystroke commands. Commands are described below. See the bottom of the page for more helper functions.
; COMMANDS:-
; - These should be separated by commas if they are placed together in a string (eg. 'SendKeys( "ADDRESS-BAR,PASTE" )' ).
; - TAB, TAB-BACK, and DELAY can have an optional asterisk followed by a number after the command (eg. "TAB*10,TAB-BACK*5,DELAY*500"). The number specifies the number of times that the action should be repeated, or how long ~
; ~ the millisecond delay is in the case of DELAY.
; - Command strings should not contain any spaces or other whitespace.
; -
; "CUT" - Cut selected text to the clipboard (CONTROL-X).
; "COPY" - Copy selected text to the clipboard (CONTROL-C).
; "PASTE" - Paste the contents of the clipboard into an editable element (CONTROL-V).
; "SELECT-ALL" - Select all the text in an editable element (CONTROL-A).
; "DELETE" - Delete any selected code in an editable element (DELETE).
; "ADDRESS-BAR" - Go to the Chrome browser's address bar (CONTROL-L).
; "TAB-BACK" - Press TAB key while holding down the SHIFT key (tab backwards on the page) (SHIFT-TAB).
; "NEW-PAGE" - Open a new Chrome browser tab page (CONTROL-T).
; "FIRST-PAGE" - Open a new Chrome browser tab page (CONTROL-1).
; "NEXT-PAGE" - Move to the next Chrome browser tab page (CONTROL-TAB).
; "CLOSE-PAGE" - Close the current Chrome browser tab page (CONTROL-W).
; "ENTER" - Press ENTER key.
; "TAB" - Press TAB key.
; "HOME" ;- Press HOME key.
; "END" - Press END key.
; "DELAY" - Delay for the specified number of milliseconds (default = 1 ) (eg. "DELAY*500" delays for half a second).
; "TAB-DELAY" - Sets the millisecond delay to use between multiple tabs being sent. Defaults to 100 if no parameter is supplied, or if the parameter is less than 100.
; "TAB-BACK-DELAY" - Sets the millisecond delay to use between multiple tabs being sent. Defaults to 100 if no parameter is supplied, or if the parameter is less than 100.
; NOTE: Make sure that the Google Chrome browser window is open and is not minimized before calling the functions in this library. Interacting with minimized applications can be a chancy proposition.
; EXAMPLE CODE:---
; ; Find out if the Chrome window is open.
; If FindChromeWindow() = 0 : ProcedureReturn 0 : EndIf ; Abort if the Chrome window could not be found.
; ; Open a new Chrome page tab. If an existing new tab is open and selected then that will be used.
; If OpenNewChromePageTab() = 0 : Error( "Unable to open a new blank Chrome tab." ) : ProcedureReturn 0 : EndIf ; Abort if the multiple attempts to open a tab failed.
; ; Open a webpage.
; If OpenChromePageURL( opensea_collection_url.s, G_opensea_tab_title.s, 1000 ) = 0 : Error( "OpenSea window not found." ) : ProcedureReturn 0 : EndIf ; Abort if the multiple attempts failed.
; SendKeys( "ADDRESS-BAR" ) : Delay( 300 ) ; Select the Chrome search/address bar. This ensures that we are starting from a known point.
; SendKeys( "TAB*12" ) : Delay( 300 ) ; Move to the file upload field.
; ; Open the file upload dialog.
; handle = OpenFileUploadDialog() : If handle = 0 : Error( "Unable to get valid window handle for Chrome open file window." ) : ProcedureReturn 0 : EndIf
; ; Submit our upload. Note that we are specifying the handle of the file upload dialog window here, not the Chrome window handle stored in the 'G_target_window_handle' global that is used by default.
; SetClipboardText( item_filepath.s ) : SendKeys( "PASTE", handle ) : Delay( 300 ) : SendKeys( "ENTER", handle ) ; Enter the filepath of our item into the open file dialog and submit it. It's a good idea to use an absolute filepath here.
; Delay( 300 ) : SendKeys( "TAB*2" ) ; Move to the item name field. Note that some file upload buttons may add additional elements when a file is submitted, so we may need extra tabs to get past them.
; Delay( 300 ) : SetClipboardText( item_display_name.s ) : SendKeys( "PASTE" ) ; Enter the item name.
; Delay( 300 ) : SendKeys( "TAB" ) : If item_external_link.s : SetClipboardText( item_external_link.s ) : SendKeys( "PASTE" ) : EndIf ; Enter the external link, if one has been set.
; Delay( 300 ) : SendKeys( "TAB*2" ) : SetClipboardText( item_description.s ) : SendKeys( "PASTE" ) ; Enter the item description.
; Delay( 300 ) : SendKeys( "ADDRESS-BAR,DELAY*300,TAB-BACK*3" ) ; Go to the address bar and then shift-tab backwards to the 'Create' button. This gets us past the jungle of dropdowns after the description field. ~
; ; ~ Note that the clutter of file download buttons that Chrome puts on the bottom of the browser window will catch the back-tabbing. Close the download notification bar first to prevent this.
; ---
; - RESEARCH LINKS:-
; https://stackoverflow.com/questions/18662637/sendinput-vs-keybd-event
; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
; https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage
; https://docs.microsoft.com/en-us/windows/win32/inputdev/keyboard-input-notifications
; https://batchloaf.wordpress.com/2012/10/18/simulating-a-ctrl-v-keystroke-in-win32-c-or-c-using-sendinput/
; https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getmessageextrainfo
Global G_target_window_handle
Global G_multi_tab_delay = 100
Global G_multi_tab_back_delay = 100
Global NewList WindowList() ; Used to store a list of window handles.
Global Dim KeyArray.INPUT( 0 )
Global *G_key.INPUT = @KeyArray( 0 )
*G_key\type = #INPUT_KEYBOARD
Procedure SendNormalKey( keycode )
*G_key\ki\wVk = keycode
*G_key\ki\dwFlags = 0 ; 0 for key press.
SendInput_( 1, KeyArray(), 40 )
*G_key\ki\wVk = keycode
*G_key\ki\dwFlags = #KEYEVENTF_KEYUP ; #KEYEVENTF_KEYUP for key release.
SendInput_( 1, KeyArray(), 40 )
EndProcedure
Procedure SendSpecialKey( keycode, special_keycode )
*G_key\ki\wVk = special_keycode
*G_key\ki\dwFlags = 0 ; 0 for key press.
SendInput_( 1, KeyArray(), 40 )
*G_key\ki\wVk = keycode
*G_key\ki\dwFlags = 0 ; 0 for key press.
SendInput_( 1, KeyArray(), 40 )
*G_key\ki\wVk = keycode
*G_key\ki\dwFlags = #KEYEVENTF_KEYUP ; #KEYEVENTF_KEYUP for key release.
SendInput_( 1, KeyArray(), 40 )
*G_key\ki\wVk = special_keycode
*G_key\ki\dwFlags = #KEYEVENTF_KEYUP ; #KEYEVENTF_KEYUP for key release.
SendInput_( 1, KeyArray(), 40 )
EndProcedure
Procedure SendKeys( keys.s, handle = 0 )
; Send the specified keypresses to the window with the specified handle.
; G_target_window_handle - Holds the default window handle to use if the 'handle' parameter is zero.
; The 'keys.s' parameter should contain a comma-separated list of key command fields. Both TAB and TAB-BACK can have an optional asterisk followed by a number after the command (eg. "TAB*10,TAB-BACK*5"). ~
; ~ The number specifies the number of times that the key action should be repeated. Command strings should not contain any spaces or other whitespace.
If handle = 0 : handle = G_target_window_handle : EndIf : If handle = 0 : ProcedureReturn 0 : EndIf ; Use the default window handle if needed, and abort if the handle is still zero.
If IsWindow_( handle ) = 0 : ProcedureReturn 0 : EndIf ; If the target window doesn't exist then abort.
If keys.s = "" : ProcedureReturn 0 : EndIf ; If there are no fields then abort.
SetForegroundWindow_( handle ) ; Target window now has the focus for typing.
Delay( 125 ) ; 1/8 second pause before typing to prevent fast CPU problems.
num_fields = CountString( keys.s, "," ) + 1 ; Get the number of comma-separated fields.
For field_index = 1 To num_fields ; Sequence the number of fields.
field.s = StringField( keys.s, field_index, "," )
If FindString( field.s, "*" )
command.s = StringField( field.s, 1, "*" )
iterations = Val( StringField( field.s, 2, "*" ) )
Else
command.s = field.s
iterations = 1
EndIf
; https://batchloaf.wordpress.com/2012/10/18/simulating-a-ctrl-v-keystroke-in-win32-c-or-c-using-sendinput/
; SendInput_(
; [in] UINT cInputs, ; The number of structures in the pInputs array.
; [in] LPINPUT pInputs, ; An array of INPUT structures. Each structure represents an event to be inserted into the keyboard or mouse input stream.
; [in] int cbSize ) ; The size, in bytes, of an INPUT structure. If cbSize is not the size of an INPUT structure, the function fails. KEYBDINPUT = 24 bytes | INPUT = 40 bytes
; https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput
; typedef struct tagKEYBDINPUT {
; WORD wVk ; A virtual-key code. The code must be a value in the range 1 to 254. If the dwFlags member specifies KEYEVENTF_UNICODE, wVk must be 0.
; WORD wScan ; A hardware scan code for the key. If dwFlags specifies KEYEVENTF_UNICODE, wScan specifies a Unicode character which is to be sent to the foreground application.
; DWORD dwFlags ; Specifies various aspects of a keystroke. This member can be certain combinations of the following values. https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput
; DWORD time ; The time stamp for the event, in milliseconds. If this parameter is zero, the system will provide its own time stamp.
; ULONG_PTR dwExtraInfo ; An additional value associated with the keystroke. Use the GetMessageExtraInfo function to obtain this information. https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getmessageextrainfo
; } KEYBDINPUT, *PKEYBDINPUT, *LPKEYBDINPUT;
; Note that the #WM_KEYDOWN and #WM_KEYUP message types require a virtual key code in the wParam field. #WM_CHAR requires a UTF-16 character code (ASCII codes should work).
Select command.s
Case "CUT" ; Cut selected text to the clipboard (CONTROL-X).
SendSpecialKey( #VK_X, #VK_CONTROL )
Case "COPY" ; Copy selected text to the clipboard (CONTROL-C).
SendSpecialKey( #VK_C, #VK_CONTROL )
Case "PASTE" ; Paste the contents of the clipboard (CONTROL-V).
SendSpecialKey( #VK_V, #VK_CONTROL )
Case "SELECT-ALL" ; Select all the text in the element with the keyboard focus (CONTROL-A).
SendSpecialKey( #VK_A, #VK_CONTROL )
Case "DELETE" ; Delete selected text (DELETE).
SendNormalKey( #VK_DELETE )
Case "ADDRESS-BAR" ; Go to the Chrome browser's address bar (CONTROL-L).
SendSpecialKey( #VK_L, #VK_CONTROL )
Case "TAB-BACK" ; Press TAB key while holding down the SHIFT key (tab backwards on the page) (SHIFT-TAB).
For i = 1 To iterations : SendSpecialKey( #VK_TAB, #VK_SHIFT ) : Delay( G_multi_tab_back_delay ) : Next
Case "TAB" ; ( #VK_TAB ) Press TAB key.
For i = 1 To iterations : SendNormalKey( #VK_TAB ) : Delay( G_multi_tab_delay ) : Next
Case "NEW-PAGE" ; Open a new Chrome browser tab page (CONTROL-T).
SendSpecialKey( #VK_T, #VK_CONTROL )
Case "FIRST-PAGE" ; Open a new Chrome browser tab page (CONTROL-1).
SendSpecialKey( #VK_1, #VK_CONTROL )
Case "NEXT-PAGE" ; Move to the next Chrome browser tab page (CONTROL-TAB).
SendSpecialKey( #VK_TAB, #VK_CONTROL )
Case "CLOSE-PAGE" ; Close the current Chrome browser tab page (CONTROL-W).
SendSpecialKey( #VK_W, #VK_CONTROL )
Case "ENTER" ; ( #VK_RETURN ) Press ENTER key.
SendNormalKey( #VK_RETURN )
Case "HOME" ; ( #VK_HOME ) Press HOME key.
SendNormalKey( #VK_HOME )
Case "END" ; ( #VK_END ) Press END key.
SendNormalKey( #VK_END )
Case "DELAY"
Delay( iterations )
Case "TAB-DELAY"
G_multi_tab_delay = iterations
If G_multi_tab_delay < 100 : G_multi_tab_delay = 100 : EndIf
Case "TAB-BACK-DELAY"
G_multi_tab_back_delay = iterations
If G_multi_tab_back_delay < 100 : G_multi_tab_back_delay = 100 : EndIf
EndSelect
Next
ProcedureReturn 1
EndProcedure
Procedure BuildListOfWindows( handle, lParam )
; The callback function used with 'EnumWindows_'.
AddElement( WindowList() )
WindowList() = handle
ProcedureReturn 1
EndProcedure
; Procedure FetchTargetWindowHandle( window_classname.s )
; G_target_window_handle = FindWindowEx_( 0, 0, window_classname.s, 0 )
; If G_target_window_handle = 0
; Error( "Unable to get valid window handle to use with 'SendKeys'. From 'GetTargetWindowHandle' in 'send_key.pb'." )
; EndIf
; EndProcedure
Procedure.s GetWindowTitleText( handle )
window_title_len = GetWindowTextLength_( handle ) + 1 ; Get the title length of the tested tab title, plus one for a terminating null char.
window_title_text.s = Space( window_title_len )
GetWindowText_( handle, @window_title_text.s, window_title_len )
ProcedureReturn window_title_text.s
EndProcedure
Procedure FindChromeWindow()
; Try to find the Google Chrome window. Set 'G_target_window_handle' to that window's handle and bring the window to the foreground if it is found.
; Returns the window handle if successful, and 0 if failed.
G_target_window_handle = 0 ; Initialize this global. Note that this is declared at the top of 'send_key.pb', and is used in the functions from that library.
For i = 1 To 5 ; Make multiple attempts to find the window, but put a limit on it.
ClearList( WindowList() ) : EnumWindows_( @BuildListOfWindows(), 0 )
ForEach WindowList()
If FindString( GetWindowTitleText( WindowList() ), "Google Chrome" ) : G_target_window_handle = WindowList() : Break 2 : EndIf
Next
Delay( 300 ) ; Chrome should already be open, so put the delay here so we are not waiting for it to open when it already is.
Next
If G_target_window_handle = 0 : Error( "The Chrome window was not found in the list of top-level windows." ) : EndIf
ProcedureReturn G_target_window_handle; Return the window handle if successful, and 0 if failed.
EndProcedure
Procedure OpenNewChromePageTab()
; Attempt to open a new blank Chrome tab. Allows a limited number of attempts. This will also check if there's already a new blank Chrome tab open that can be used.
; Returns 1 if successful, and 0 if failed.
found = 0
For i = 1 To 6 ; Make multiple attempts to find the window, but put a limit on it.
ClearList( WindowList() ) : EnumWindows_( @BuildListOfWindows(), 0 )
ForEach WindowList()
If FindString( GetWindowTitleText( WindowList() ), "New Tab - Google Chrome" ) : found = 1 : Break 2 : EndIf
Next
SendKeys( "NEW-PAGE" ) ; Open a new blank Chrome tab.
Delay( 300 ) ; Delay before checking again in case the window is lagging.
Next
ProcedureReturn found
EndProcedure
Procedure OpenChromePageURL( url_to_open.s, page_title_text.s, delay_after_page_open = 1000 )
; Attempt to open the specified 'url_to_open.s' in the Chrome browser. Allows a limited number of attempts.
; page_title_text.s - This should hold a case-sensitive string that is expected to be found in the HTML title of the page being opened. Either look for the HTML '<title>' tag or mouse-over the Chrome taskbar button ~
; ~ to find the title text to use. Note that the entire title string is not needed, just a section of it.
; delay_after_page_open - (default 1000) The number of milliseconds to delay after the page has started to open. This gives it time to fully open.
; Returns 1 if successful, and 0 if failed.
SendKeys( "ADDRESS-BAR" ) ; Select the search/address bar.
SetClipboardText( url_to_open.s ) : SendKeys( "PASTE" ) : Delay( 300 ) : SendKeys( "ENTER" ) ; Open the item submit page for this project. If this fails then additional attempts to open it will be made below.
Delay( 300 )
; Wait until the page is open. Allow a limited number of additional attempts to open it if it is not open.
found = 0
For i = 1 To 5 ; Make multiple attempts to find the window, but put a limit on it.
ClearList( WindowList() ) : EnumWindows_( @BuildListOfWindows(), 0 )
Delay( 300 ) ; Delay before checking in case the window is just lagging.
ForEach WindowList()
If FindString( GetWindowTitleText( WindowList() ), page_title_text.s ) : found = 1 : Break 2 : EndIf
Next
Delay( 300 ) : SendKeys( "ADDRESS-BAR" ) ; Select the Chrome search/address bar. Note that this will also select the text in the address bar, allowing us to overwrite it.
Delay( 300 ) : SetClipboardText( url_to_open.s ) : SendKeys( "PASTE" ) : Delay( 300 ) : SendKeys( "ENTER" ) ; Open the item submit page for this project.
Delay( 300 )
Next
If found : Delay( delay_after_page_open ) : EndIf ; Delay to give the page time to fully open.
ProcedureReturn found
EndProcedure
Procedure OpenFileUploadDialog()
; Attempt to open a file upload dialog window on the opened web-page. Allows a limited number of attempts.
; SendKeys() should have been used to tab to the position of the button or other element that is normally clicked on to open the file dialog
; Returns the handle of the file dialog window if successful, and 0 if failed.
; -
; https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindowexa
; http://winapi.freetechsecrets.com/win32/WIN32FindWindowEx_Now_Supported_on_Wi.htm
; Attempt to get the window handle for the file upload dialog. Note that the class-name string for these dialogs is "#32770".
Protected handle
SendKeys( "ENTER" ) : Delay( 500 ) ; Open the Chrome open file dialog window using the ENTER keystroke.
For i = 1 To 5 ; Use a for loop to limit the number of attempts to find the window handle.
handle = FindWindowEx_( 0, 0, "#32770", "Open" ) ; Get the window handle for the open file dialog window in Chrome.
If handle
If GetParent_( handle ) = G_target_window_handle
Break
EndIf
EndIf
Delay( 500 )
Next
ProcedureReturn handle
EndProcedure