Send keyboard input to Google Chrome browser using SendInput.

Share your advanced PureBasic knowledge/code with the community.
Axeman
User
User
Posts: 91
Joined: Mon Nov 03, 2003 5:34 am

Send keyboard input to Google Chrome browser using SendInput.

Post by Axeman »

This library allows you to send keyboard input to a Google Chrome browser window in order to speed up and automate website submissions and HTTP file uploads. See the instructions in the code for more information. You can also find a usage example near the top of the code.

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






Last edited by Axeman on Wed Jan 12, 2022 12:22 pm, edited 1 time in total.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Send keyboard input to Google Chrome browser using SendInput.

Post by Kwai chang caine »

Hello Axeman

I have try your code
Error() is missing
I have adding a simple Error() procedure, but your code even not work on my open portable Chrome, but a "G_target_window_handle" is found :|

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).
; "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).


; 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 : Error( "The Google Chrome window was not found in the list of top-level windows." ) : 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( 200 ) ; Select the Chrome search/address bar. This ensures that we are starting from a known point.

; SendKeys( "TAB*12" ) : Delay( 200 ) ; 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( 200 ) : 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( 200 ) : 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( 200 ) : SetClipboardText( item_display_name.s ) : SendKeys( "PASTE" ) ; Enter the item name.

; Delay( 200 ) : 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( 200 ) : SendKeys( "TAB*2" ) : SetClipboardText( item_description.s ) : SendKeys( "PASTE" ) ; Enter the item description.

; Delay( 200 ) : 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 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 Error(Text$)
 MessageRequester("", Text$)
EndProcedure

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 ) : Next
			
		Case "TAB" ; ( #VK_TAB ) Press TAB key.
			For i = 1 To iterations : SendNormalKey( #VK_TAB ) : Next
		
		Case "NEW-PAGE" ; Open a new Chrome browser tab page (CONTROL-T).
			SendSpecialKey( #VK_T, #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 )
		
	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( 200 ) ; Chrome should already be open, so put the delay here so we are not waiting for it to open when it already is.
Next

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( 200 ) ; 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( 200 ) : SendKeys( "ENTER" ) ; Open the item submit page for this project. If this fails then additional attempts to open it will be made below.
Delay( 200 )

; 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( 200 ) ; 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( 200 ) : 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( 200 ) : SetClipboardText( url_to_open.s ) : SendKeys( "PASTE" ) : Delay( 200 ) : SendKeys( "ENTER" ) ; Open the item submit page for this project.
	Delay( 200 )
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.

; Attempt to get the window handle for the file upload dialog. Note that the class-name string for these dialogs is "#32770".
handle = 0 ; Null this to be on the safe side.
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", 0 ) ; Get the window handle for the open file dialog window in Chrome.
	If handle : Break : EndIf
	Delay( 500 )
Next

ProcedureReturn handle
EndProcedure

Procedure Main()

 ; Find out if the Chrome window is open.
 If FindChromeWindow() = 0 : Error( "The Google Chrome window was not found in the list of top-level windows." ) : 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( 200 ) ; Select the Chrome search/address bar. This ensures that we are starting from a known point.
 
 SendKeys( "TAB*12" ) : Delay( 200 ) ; 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( 200 ) : 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( 200 ) : 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( 200 ) : SetClipboardText( item_display_name.s ) : SendKeys( "PASTE" ) ; Enter the item name.
 
 Delay( 200 ) : 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( 200 ) : SendKeys( "TAB*2" ) : SetClipboardText( item_description.s ) : SendKeys( "PASTE" ) ; Enter the item description.
 
 Delay( 200 ) : 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. ~

EndProcedure

Main()
ImageThe happiness is a road...
Not a destination
Axeman
User
User
Posts: 91
Joined: Mon Nov 03, 2003 5:34 am

Re: Send keyboard input to Google Chrome browser using SendInput.

Post by Axeman »

The 'Error()' function only appears in the usage example code. That code just provides an example of how the library is intended to be used. I've included code for the error function I use below, to help you out.

Code: Select all

; Shows an error message. Double line-breaks can be added by including a caret '^' character in the message string.
Procedure Error( message.s )
MessageRequester( "Error", ReplaceString( message.s, "^", ~"\n\n" ) )
EndProcedure
I created this library to use with the Google Chrome browser on a Windows 10 desktop PC. If you want to use it on anything else then you'll need to adapt it. Basically, I needed the code for a project and decided to share it. It works fine for me for the most part, although failed submission due to the browser or the OS interrupting the process is a thing that happens. You need to approach how you use the library carefully, as the process of sending keystrokes to third-party app windows can go wrong in a variety of ways. Make sure the Chrome window is not minimized when you try to interact with it, as minimized apps may ignore certain types of input.

The likelihood is that the Chrome window that you are using uses a different title text string to the standard desktop version of Chrome. It may also use different strings in its title text for new page tabs, etc, which can also be a problem. Bear in mind that many browsers these days are built on the Chrome codebase and some use the same window class string (Opera, etc) which is why I had to switch to using strings in the title-text for detecting the Chrome window. The text strings I look for include hidden text that doesn't appear in the <title> tag. You can find this text by hovering your mouse over the taskbar button for Chrome. For example, if you look at the page source for a new Chrome tab page then you can see that the title text is <title>New Tab</title>. If you hover over the taskbar button then you see the actual window text is 'New Tab - Google Chrome'. This is the text I use for detecting if a new tab page is selected, and for detecting the Chrome window itself I use ' - Google Chrome', as it should appear in all the Chrome window titles for the version of Chrome that I use.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Send keyboard input to Google Chrome browser using SendInput.

Post by Kwai chang caine »

Thanks for your long explanation :wink:
And obviously, again for your sharing 8)
ImageThe happiness is a road...
Not a destination
AZJIO
Addict
Addict
Posts: 2171
Joined: Sun May 14, 2017 1:48 am

Re: Send keyboard input to Google Chrome browser using SendInput.

Post by AZJIO »

You may need to check the sticking of the keys, or send a command to release the buttons.

Code: Select all

While GetAsyncKeyState_(#VK_SHIFT) Or GetAsyncKeyState_(#VK_CONTROL) Or GetAsyncKeyState_(#VK_MENU)
	Delay(100)
Wend
.

Code: Select all

Procedure ReleaseKey()
	While GetAsyncKeyState_(#VK_SHIFT)
		*G_key\ki\wVk = #VK_SHIFT
		*G_key\ki\dwFlags = #KEYEVENTF_KEYUP
		SendInput_( 1, KeyArray(), 40 )
		Delay(100)
	Wend
	While GetAsyncKeyState_(#VK_CONTROL)
		*G_key\ki\wVk = #VK_CONTROL
		*G_key\ki\dwFlags = #KEYEVENTF_KEYUP
		SendInput_( 1, KeyArray(), 40 )
		Delay(100)
	Wend
	While GetAsyncKeyState_(#VK_MENU)
		*G_key\ki\wVk = #VK_MENU
		*G_key\ki\dwFlags = #KEYEVENTF_KEYUP
		SendInput_( 1, KeyArray(), 40 )
		Delay(100)
	Wend
EndProcedure

Procedure SendSpecialKey( keycode, special_keycode )
	ReleaseKey()
Thanks for the example, now I've managed to do a reliable key emulation
Axeman
User
User
Posts: 91
Joined: Mon Nov 03, 2003 5:34 am

Re: Send keyboard input to Google Chrome browser using SendInput.

Post by Axeman »

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.
ricardo
Addict
Addict
Posts: 2438
Joined: Fri Apr 25, 2003 7:06 pm
Location: Argentina

Re: Send keyboard input to Google Chrome browser using SendInput.

Post by ricardo »

Hi,

Interesting.
Can you share some examples of using the code please?
Per example, how can i download some image on the web page?

Im interested mainly in fill and submit forms.
ARGENTINA WORLD CHAMPION
Post Reply