Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

commandline utility - send keys to target window

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • commandline utility - send keys to target window

    I wrote a commandline utility that makes the specified window active and then sends keystrokes to it. The commandline string specifies the target window and the sequence of keyboard keys (text and keyboard commands) to be typed to that window, and the keystroke rate.

    Special keys are supported, such as ALT and CONTROL, which means that commands can be sent to the target application, such as selecting menu items.

    This allows your calling program or bat file to control a windows GUI application in the same way that the user can from the keyboard (of course, without the functions that require a mouse), similar to macro/windows automation packages.

    The commandline syntax and examples are given in the comments in the code.

    This program is based on the work of Pierre Bellisle and Gosta H. Lovgren for enumerating windows to find the desired window's handle (http://www.powerbasic.com/support/pb...ad.php?t=23634) and Eddy Van Esch for the SendInput API (http://www.powerbasic.com/support/pb...ad.php?t=40567)

    Note the program does not currently work on child window names specified in the commandline. (If you wish to add support for child windows, please refer to Pierre Bellisle's post cited above.) Note that if the child window is already active and you specify * in the commandline to type into the active window, then it should work.

    I included the code in-line as part of this post and also as an attachment.

    Code:
    ' May 28, 2009 11 pm Eastern
    
    ' this program takes the text and key commands specified in the commandline and types them out to the window specified in the commandline
    ' your keys are outpulsed to the target window as if they were typed from the keyboard (that is, they are not pasted via the clipboard)
    ' and thus you are able to send commands to the target application (e.g., alt key combinations, control key combinations, etc.)
    
    ' This program is based on the examples for enumerating windows by Pierre Bellisle and Gosta H. Lovgren
    ' http://www.powerbasic.com/support/pbforums/showthread.php?t=23634
    
    ' and on the SendInput API code written by Eddy Van Esch
    ' Edy's SendInput.inc code has been integrated into my source code so you don't need to compile the separate inc file
    ' http://www.powerbasic.com/support/pbforums/showthread.php?t=40567
    
    ' commandline specification
    
    'PROGRAM.EXE |window_name|match_type|keystroke_delay|keystrokes
    'OR
    'PROGRAM.EXE "|window_name|match_type|keystroke_delay|keystrokes"
    
    'where PROGRAM.EXE is the name of this program
    
    'Quotation marks surrounding the commandline arguments are optional, but they are necessary if there are trailing spaces to be included at the end of the keystrokes
    
    ' the first character in the commandline argument is the delimiter.
    'The pipe | character is the delimiter in the examples above.  However the delimiter must be unique
    ' so if the | is one of your keystrokes, then it cannot be the delimiter
    ' if you want to use | as a keystroke, then change the delimiter in the commandline argument to something else like #
    'PROGRAM.EXE #window_name#match_type#keystroke_delay#keystrokes
    'PROGRAM.EXE "#window_name#match_type#keystroke_delay#keystrokes"
    
    ' you do not need to alter the program code if changing the delimiter
    ' the program automatically determines your delimiter from the first position in the commandline argument
    
    ' you cannot use "{" or "}" as a delimiter, as these are reserved characters for special keystrokes
    
    ' the window name is the title of the window that should receive the keystrokes
    ' you can enter the full window name or a partial string
    ' if you want the keystrokes to go to the active window, regardless of the active window's title, then use an asterisk *
    ' for the window name
    
    ' match_type, one of the following (single letter or the whole word)
    ' E or EXACT = the program selects a window where the title exactly matches the window name in the commandline argument
    ' S or START = the program selects a window where the first X characters of the selected window name match the window name specified in the commandline argument
    ' A or ANYWHERE = the window name in the commandline argument appears anywhere within the title of the selected window
    ' any of these values can be used if the window name = *.  They will give the same result in this case.
    
    ' keystroke_delay = any integer, time delay, units in ms
    ' time delay may be needed so that keystrokes are not sent to the target window too quickly
    ' eg, {ALT_D}f{ALT_U}n   opens the file menu and then types n. You may need a keystroke delay to ensure the application has enough time to open its file menu before it receives the n.
    ' Values
    ' <0 = the keystrokes you specify are sent to the sendinput API as a single block
    ' 0 = the keystrokes you specify are typed out one at a time (i.e., the program sends the keys to the sendinput API one at a time), with no extra delay between keystrokes
    ' >0 = the program sends the keys to the sendinput API one at a time.  The program inserts a delay between each sendinput API call for the specified number of milliseconds, in order to slow down keystroke rate.
    
    ' keystrokes
    ' these are the keys you want typed out
    ' most special keys, like enter, backspace, escape, page up can all be included in the sequence
    ' using the syntax shown below after the examples
    ' note that the program does not currently allow you to distinguish in the keystrokes between numpad keys and their equivalents (e.g, 1 on the regular keyboard versus 1 on the numpad)
    ' you should only use "{" or "}" as part of the special keys
    ' the program does not currently allow you to type out "{" or "}" as literal keystrokes
    
    ' examples
    ' 
    'go to the notepad and type two lines of text with all keys sent as one block
    'program.exe "|notepad|a|-1|line 1 - this is a test{ENTER}line 2 testing"
    
    'in whichever window happens to be active, type ALT-f
    'program.exe "|*|a|0|{ALT_D}f{ALT_U}"
    
    ' go to the file manager window and type Alt-f (open file menu), c (c to select copy item from menu), shift-tab, control-c (copy), tab, control-v (paste), 5 left arrows, shift-home (to select/highlight text), shift-right arrow (to modify selection)
    'program.exe "|file manager|start|60|{ALT_D}f{ALT_U}c{SHIFT_D}{TAB}{SHIFT_U}{CTRL_D}c{CTRL_U}{TAB}{CTRL_D}v{CTRL_U}{LEFT}{LEFT}{LEFT}{LEFT}{LEFT}{SHIFT_D}{HOME}{RIGHT}{SHIFT_U}"
    
    'Syntax for special keys
    '
    'note that control, shift, and alt have separate keystrokes for pressing the key down and releasing the key. e.g., alt-f, which means pressing f while alt is down, is represented as {ALT_D}f{ALT_U}
    '
    '{BACKSPACE}
    '{ENTER}
    '{CTRL_D}
    '{CTRL_U}
    '{ALT_D}
    '{ALT_U}
    '{SHIFT_D}
    '{SHIFT_U}
    '{TAB}
    '{ESCAPE}
    '{PGUP}
    '{PGDN}
    'arrow keys
    '{LEFT}
    '{RIGHT}
    '{UP}
    '{DOWN}
    ' arrow keys - alternate syntax
    '{ARROWRIGHT}
    '{ARROWLEFT}
    '{ARROWUP}
    '{ARROWDOWN}
    '{END}
    '{HOME}
    '{F1}
    '{F2}
    '{F3}
    '{F4}
    '{F5}
    '{F6}
    '{F7}
    '{F8}
    '{F9}
    '{F10}
    '{F11}
    '{F12}
    '{INSERT}
    '{DELETE}
    '{APPS}
    '{NUMLOCK}
    'caps lock
    '{CAPITAL}
    
    #COMPILE EXE
    #DIM ALL
    #INCLUDE "win32api.inc"
    
    GLOBAL typeofmatch AS STRING
    
    'We are going to use a pointer to a variable of this type.
    TYPE EnumType
        hndl       AS DWORD
        zClass     AS ASCIIZ * %Max_Path
        zCaption   AS ASCIIZ * %Max_Path
        ID         AS DWORD
        hParent    AS DWORD
        COUNT      AS DWORD
    END TYPE
    
    FUNCTION PBMAIN () AS LONG
    
    LOCAL hProc     AS LONG
    LOCAL hControl  AS LONG
    LOCAL idControl AS LONG
    LOCAL SomeText  AS ASCIIZ * %Max_Path '** Pierre had it set at 50
    LOCAL EnumTry1  AS EnumType               'I changed it for "playing" purposes
    LOCAL EnumTry2  AS EnumType
    
    DIM windowname AS STRING
    DIM delimiterchar  AS STRING
    DIM matchtype  AS STRING
    
    
    DIM keystrokedelay AS INTEGER ' sleep time between keystrokes (units=ms)
    DIM keystrokedelaystr  AS STRING ' the keystrokedelay when it is a string extracted from the commandline
    
    DIM keystrokes AS STRING 'the keystroke or special key as sent to .inc routine
    DIM singlekey AS STRING ' character by character extract from keysequence. may be a single key or subset of special key
    DIM keysequence AS STRING 'sequence of keys sent by user or other app
    DIM keysequencepos AS INTEGER
    DIM specialkeyflag AS STRING
    
    DIM originalinput AS STRING
    DIM modifiedinput AS STRING
    
    originalinput=LTRIM$(COMMAND$)
    
    ' if you need leading or trailing spaces, then surrounded command line with quotes, which will be stripped off here
    IF LEFT$(originalinput,1)=$DQ AND RIGHT$(originalinput,1)=$DQ THEN
        originalinput=MID$(originalinput,2,LEN(originalinput)-2)
    END IF
    
    delimiterchar = LEFT$(originalinput,1) ' extract delimiter
    IF delimiterchar="{" OR delimiterchar="}" THEN
        MSGBOX "INVALID DELIMITER"
        EXIT FUNCTION
    END IF
    
    modifiedinput=RIGHT$(originalinput, LEN(originalinput)-1) ' remove delimiter from start of command line
    
    IF RIGHT$(originalinput,1)=delimiterchar THEN
        modifiedinput=LEFT$(modifiedinput, LEN(modifiedinput)-1) ' remove delimiter from end of command line if it is there
    END IF
    
    ' for texting
    'msgbox modifiedinput+$CRLF+delimiterchar+$CRLF+str$(PARSECOUNT ( modifiedinput, delimiterchar))
    
    IF PARSECOUNT ( modifiedinput, delimiterchar)<>4 THEN
        MSGBOX "INVALID DELIMITER STRUCTURE IN COMMAND LINE - NOT EQUAL TO 4 FIELDS"
        EXIT FUNCTION
    END IF
    
    'window_name
    windowname=PARSE$(modifiedinput,delimiterchar,1)
    
    'match_type
    matchtype=PARSE$(modifiedinput,delimiterchar,2)
    SELECT CASE UCASE$(matchtype)
        CASE "S" , "START"
        typeofmatch="PARTIALFRONT"
        CASE "A" , "ANYWHERE" , "ANY"
        typeofmatch="PARTIALMIDDLE"
        CASE "E" , "EXACT"
        typeofmatch="EXACT"
        CASE ELSE
        typeofmatch="EXACT"
    END SELECT
    
    'keystroke_delay
    keystrokedelaystr=PARSE$(modifiedinput,delimiterchar,3)
    keystrokedelay=VAL(keystrokedelaystr)
    
    'keystrokes
    keysequence=PARSE$(modifiedinput,delimiterchar,4)
    
    IF windowname<>"*" THEN
        ' change active window ------------------------------------------
        EnumTry1.zCaption = windowname
    
        EnumTry1.hParent  = 0
    
        EnumWindows CODEPTR(EnumWindowProc), VARPTR(EnumTry1)  'If window exist EnumTry will be filled with window handle
    
        IF EnumTry1.hndl THEN ' if we have a matching window name
            hProc = EnumTry1.hndl
            SetForegroundWindow(hProc)
        ELSE
            MSGBOX "No matching window title"
            EXIT FUNCTION
        END IF
        ' -------------------------------------------------------------------------------
    END IF
    
    keystrokes=""
    singlekey=""
    specialkeyflag="N"
    
    IF keystrokedelay>=0 THEN ' send keys one at a time
    
        FOR keysequencepos=1 TO LEN(keysequence)
            singlekey=MID$(keysequence,keysequencepos,1)
            keystrokes=keystrokes+singlekey
    
            IF keystrokes="{" THEN  'start of special key
                specialkeyflag="Y"
                ELSEIF singlekey="}" AND specialkeyflag="Y" THEN 'end of special key
                    specialkeyflag="N"
                END IF
    
                IF specialkeyflag="N" THEN
                    SendString keystrokes
                    keystrokes=""
                    SLEEP keystrokedelay
                END IF
    
            NEXT  keysequencepos
    
        ELSE ' no keystroke delay, send all keystrokes as a block
    
            keystrokes=keysequence
            SendString keystrokes
        END IF
    
    END FUNCTION
    
    '______________________________________________________________________________
    
    'Retreive windows handle via caption
    FUNCTION EnumWindowProc(BYVAL hWindowProc AS LONG, BYVAL lParam AS LONG) AS LONG
    LOCAL zCaption    AS ASCIIZ * %Max_Path
    LOCAL zClass      AS ASCIIZ * %Max_Path
    LOCAL EnumTryPtr  AS EnumType POINTER
    LOCAL LenClass    AS LONG
    LOCAL LenCaption  AS LONG
    
    'Retrive a pointer to EnumTry so we can read and change it
    EnumTryPtr = lParam
    
    IF IsWindowVisible(hWindowProc) THEN                               'Get only visible windows
        IF GetParent(hWindowProc) = @EnumTryPtr.hParent THEN             'Under desktop if 0 or under parent
            IF IsWindowEnabled(hWindowProc) THEN                           'Get only enabled windows
                LenClass   = GetClassName (hWindowProc, zClass, %MAX_PATH)   'Get the class of the dialog
                LenCaption = GetWindowText(hWindowProc, zCaption, %MAX_PATH) 'Get the caption of the dialog
    
                'Did we found a dialog with our text?
    
                SELECT CASE typeofmatch
                    CASE "EXACT"
                    IF UCASE$(zCaption) = UCASE$(@EnumTryPtr.zCaption) THEN
                        @EnumTryPtr.hndl     = hWindowProc          'Put handle  in EnumTry1
                        @EnumTryPtr.zCaption = zCaption             'Put caption in EnumTry1
                        @EnumTryPtr.zClass   = zClass               'Put class   in EnumTry1
    FUNCTION = 0 : EXIT FUNCTION 'Stop enumeration and exit
    END IF
    
    CASE "PARTIALFRONT"
    
    ' UCASE$(@EnumTryPtr.zCaption) IS MY SPECIFIED TEXT
    
    IF LEFT$(UCASE$(zCaption),LEN(@EnumTryPtr.zCaption)) = UCASE$(@EnumTryPtr.zCaption) THEN
    @EnumTryPtr.hndl     = hWindowProc          'Put handle  in EnumTry1
    @EnumTryPtr.zCaption = zCaption             'Put caption in EnumTry1
    @EnumTryPtr.zClass   = zClass               'Put class   in EnumTry1
    FUNCTION = 0 : EXIT FUNCTION 'Stop enumeration and exit
    END IF
    
    CASE "PARTIALMIDDLE"
    IF TALLY (UCASE$(zCaption), UCASE$(@EnumTryPtr.zCaption))>0 THEN
    @EnumTryPtr.hndl     = hWindowProc          'Put handle  in EnumTry1
    @EnumTryPtr.zCaption = zCaption             'Put caption in EnumTry1
    @EnumTryPtr.zClass   = zClass               'Put class   in EnumTry1
    FUNCTION = 0 : EXIT FUNCTION 'Stop enumeration and exit
    END IF
    
    CASE ELSE
    END SELECT
    
    
    END IF
    END IF
    END IF
    
    FUNCTION = 1 'True, so Function will recall itself until last window found
    
    END FUNCTION
    '______________________________________________
    
    '===============================================================================
    ' SendInput.inc
    ' 2009 - Eddy Van Esch
    ' 
    ' History:
    ' --------
    ' V1.0: 14-May-2009 : Initial version
    ' 
    ' V1.1: 27-May-2009 : 
    '     - Fixed a bug where SHIFT_D with HOME or an arrow key combination produced
    '       incorrect results. The shift key appeared to be ignored.
    '       Thanks to S. Stamp for reporting the problem.
    '
    '       Apparently, the HOME and arrow keys (and a number of others) are extended keys and
    '       they require the extended key flag to be set.
    '       A forum thread about the problem:
    '       http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_21737163.html
    '     - Added more special keys (see list below)
    '     - SUB WaitForNoKeys slightly modified
    '
    ' ---------------------------------------------------------------------------
    ' This software allows to send keystrokes to the application that
    ' currently has focus.
    ' Rather than using KEYBD_EVENT, this routine uses SENDINPUT.
    ' The advantage is that when using SendInput, the sent keystrokes can not
    ' be accidently mixed with keystrokes that the user is entering on the keyboard.
    ' Also, there is no problem with timings between multiple Keybd_Event commands.
    ' This routine also handles characters with diacritics, like é,è,ê,ñ, etc. (See **)
    ' Writing this code, I took inspiration (and a few lines of code :-)) from
    ' William Burns' file 'SendKeys.inc'
    ' Not all possible special keys are implemented yet, but it is easy to add more.
    '
    'Sub WaitForNoKeys was reused from William Burns' file 'SendKeys.inc'. Thanks William!
    '
    '----------------------------
    '**  IMPORTANT !!!
    'Although SendString should handle strings with diacritics properly, other active programs
    'can mess this up. Especially programs that use a global keyboard hook, such
    'as AutoHotKey for example.
    'The reason is that these programs use the ToAscii(Ex) API function in their keyboard hook.
    'Using ToAscii(Ex) in a hook messes up diacritics handling.
    'What you then will notice is an inconsistent behaviour when sending characters with diacritics.
    'For example, sending "éééé" could result in "eeéé", "''eé" or some other inconsistent result.
    'Very annoying. The only solution is to terminate the application that is using the global hook. 
    '----------------------------
    '
    '===============================================================================
    
    
    '-------------------------------------------------------------------------
    'This sub places the characters in 'sString' in the keyboard buffer. Then, the characters are sent to
    'whatever application currently has focus.
    'So, this sub allows to simulate keyboard input.
    'Apart from normal ascii characters, SendString can also handle some special characters:
    '
    'The special characters currently implemented are:
    '{CTRL_D}, {CTRL_U}, {ALT_D}, {ALT_U}, {SHIFT_D}, {SHIFT_U}, {WIN_D}, {WIN_U}, {BACKSPACE}
    '{ENTER}, {INSERT}, {DELETE}, {RIGHT}, {LEFT}, {UP}, {DOWN}, {HOME}, {END}, {TAB}, {ESCAPE} 
    '{PGUP}, {PGDN}, {F1}, {F2}, {F3}, {F4}, {F5}, {F6}, {F7}, {F8}, {F9}, {F10}, {F11}, {F12}
    '{APPS}, {NUMLOCK}
    'The {.._D} are the 'key down' codes. The {.._U} are the 'key up' codes.
    ' 
    'Examples of use: 
    'SendString "This is a test"
    'SendString "{BACKSPACE}{BACKSPACE}abcdef{ENTER}"
    'SendString "{CTRL_D}c{CTRL_U}" simulates a CTRL-C key combination. In most applications this copies the selected data.
    '
    %TimeIncr = 0
    SUB SendString(BYVAL sString AS STRING)
        LOCAL i                    AS LONG    
        LOCAL lBufCnt              AS LONG    
        LOCAL lFetchingSpecialChar AS LONG    
        LOCAL sChar                AS STRING
        LOCAL sSpecialChar         AS STRING  
        LOCAL iRet                 AS INTEGER 
        LOCAL dKeybLayout          AS DWORD   
        LOCAL bVkCode              AS BYTE    
        LOCAL bShiftState          AS BYTE    
        LOCAL Time                 AS DWORD
        LOCAL inpAPI() AS INPUTAPI
        REDIM inpAPI(0:20)
             
        IF sString = "" THEN EXIT SUB
        
        dKeybLayout = GETKEYBOARDLAYOUT(0)
        lBufCnt = -1
        Time = 0
        
                'Replace some characters with diacritics by multiple characters. 
        REPLACE "è" WITH "`e" IN sString
        REPLACE "é" WITH "'e" IN sString
        REPLACE "ê" WITH "^e" IN sString
        REPLACE "ë" WITH $DQ + "e" IN sString
    
        REPLACE "È" WITH "`E" IN sString
        REPLACE "É" WITH "'E" IN sString
        REPLACE "Ê" WITH "^E" IN sString
        REPLACE "Ë" WITH $DQ + "E" IN sString
        
        REPLACE "ì" WITH "`i" IN sString
        REPLACE "í" WITH "'i" IN sString
        REPLACE "î" WITH "^i" IN sString
        REPLACE "ï" WITH $DQ + "i" IN sString
    
        REPLACE "Ì" WITH "`I" IN sString
        REPLACE "Í" WITH "'I" IN sString
        REPLACE "Î" WITH "^I" IN sString
        REPLACE "Ï" WITH $DQ + "I" IN sString
         
        REPLACE "à" WITH "`a" IN sString
        REPLACE "á" WITH "'a" IN sString
        REPLACE "â" WITH "^a" IN sString
        REPLACE "ä" WITH $DQ + "a" IN sString
        REPLACE "ã" WITH "~a" IN sString
        
        'REPLACE "å" WITH "°a" IN sString
        REPLACE "À" WITH "`A" IN sString
        REPLACE "Á" WITH "'A" IN sString
        REPLACE "Â" WITH "^A" IN sString
        REPLACE "Ã" WITH "~A" IN sString
        REPLACE "Ä" WITH $DQ + "A" IN sString
        
        REPLACE "ò" WITH "`o" IN sString
        REPLACE "ó" WITH "'o" IN sString
        REPLACE "ô" WITH "^o" IN sString
        REPLACE "ö" WITH $DQ + "o" IN sString
        REPLACE "õ" WITH "~o" IN sString
    
        REPLACE "Ò" WITH "`O" IN sString
        REPLACE "Ó" WITH "'O" IN sString
        REPLACE "Ô" WITH "^O" IN sString
        REPLACE "Ö" WITH $DQ + "O" IN sString
        REPLACE "Õ" WITH "~O" IN sString
    
        REPLACE "Ù" WITH "`U" IN sString
        REPLACE "Ú" WITH "'U" IN sString
        REPLACE "Û" WITH "^U" IN sString
        REPLACE "Ü" WITH $DQ + "U" IN sString
        
        REPLACE "ù" WITH "`u" IN sString
        REPLACE "ú" WITH "'u" IN sString
        REPLACE "û" WITH "^u" IN sString
        REPLACE "ü" WITH $DQ + "u" IN sString
             
        REPLACE "ñ" WITH "~n" IN sString
        REPLACE "Ñ" WITH "~N" IN sString
        
        REPLACE "ç" WITH "'c" IN sString
        REPLACE "Ç" WITH "'C" IN sString
    
        REPLACE "ý" WITH "'y" IN sString
        REPLACE "ÿ" WITH $DQ + "y" IN sString
        
        REPLACE "Ý" WITH "'Y" IN sString
        REPLACE "Ÿ" WITH $DQ + "Y" IN sString    
        
    
        lFetchingSpecialChar = 0
        FOR i = 1 TO LEN(sString)                           'Parse the input string character by character
            sChar = MID$(sString, i, 1)
            SELECT CASE sChar
                CASE "{"                                    'Start of a special-character sequence
                    lFetchingSpecialChar = 1
                    sSpecialChar = ""
                
                CASE "}"                                    'End of a special-character sequence
                    lFetchingSpecialChar = 0
                    GOSUB AddSpecialCharToBuffer
                    
                CASE ELSE
                    IF lFetchingSpecialChar THEN
                        sSpecialChar = sSpecialChar + sChar 'Compose the special character string
                    ELSE
                        GOSUB AddNormalCharToBuffer
                    END IF
                
            END SELECT
        NEXT i
    
        REDIM PRESERVE inpAPI(0 TO lBufCnt)
        i = SENDINPUT(lBufCnt+1, inpAPI(0), SIZEOF(inpAPI(0)))
        
    EXIT SUB
    
    AddSpecialCharToBuffer:
                'Special characters (such as ENTER, BACKSPACE, CTRL-down, etc) that can not be represented
                'by an ASCII character, are defined here. 
        sSpecialChar = UCASE$(sSpecialChar)
        SELECT CASE sSpecialChar
            CASE "CTRL_D"    : bVkCode = %VK_CONTROL : GOSUB bVkCode_Down                     
            CASE "CTRL_U"    : bVkCode = %VK_CONTROL : GOSUB bVkCode_Up                       
            CASE "ALT_D"     : bVkCode = %VK_MENU    : GOSUB bVkCode_Down                     
            CASE "ALT_U"     : bVkCode = %VK_MENU    : GOSUB bVkCode_Up                       
            CASE "SHIFT_D"   : bVkCode = %VK_SHIFT   : GOSUB bVkCode_Down                     
            CASE "SHIFT_U"   : bVkCode = %VK_SHIFT   : GOSUB bVkCode_Up                       
            CASE "WIN_D"     : bVkCode = %VK_LWIN    : GOSUB bVkCode_Down                     
            CASE "WIN_U"     : bVkCode = %VK_LWIN    : GOSUB bVkCode_Up 
                    
            CASE "BACKSPACE" : bVkCode = %VK_BACK    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "ENTER"     : bVkCode = %VK_RETURN  : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "INSERT"    : bVkCode = %VK_INSERT  : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "DELETE"    : bVkCode = %VK_DELETE  : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
    
    ' arrow keys        
            CASE "RIGHT"     : bVkCode = %VK_RIGHT   : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "LEFT"      : bVkCode = %VK_LEFT    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "UP"        : bVkCode = %VK_UP      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "DOWN"      : bVkCode = %VK_DOWN    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
    
    ' arrow keys - alternate syntax
            CASE "ARROWRIGHT"     : bVkCode = %VK_RIGHT   : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "ARROWLEFT"      : bVkCode = %VK_LEFT    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "ARROWUP"        : bVkCode = %VK_UP      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "ARROWDOWN"      : bVkCode = %VK_DOWN    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            
            CASE "HOME"      : bVkCode = %VK_HOME    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "END"       : bVkCode = %VK_END     : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            
            CASE "TAB"       : bVkCode = %VK_TAB     : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "ESCAPE"    : bVkCode = %VK_ESCAPE  : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "PGUP"      : bVkCode = %VK_PGUP    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "PGDN"      : bVkCode = %VK_PGDN    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            
            CASE "F1"        : bVkCode = %VK_F1      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F2"        : bVkCode = %VK_F2      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F3"        : bVkCode = %VK_F3      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F4"        : bVkCode = %VK_F4      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F5"        : bVkCode = %VK_F5      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F6"        : bVkCode = %VK_F6      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F7"        : bVkCode = %VK_F7      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F8"        : bVkCode = %VK_F8      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F9"        : bVkCode = %VK_F9      : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F10"       : bVkCode = %VK_F10     : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F11"       : bVkCode = %VK_F11     : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "F12"       : bVkCode = %VK_F12     : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            
            CASE "APPS"      : bVkCode = %VK_APPS    : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            CASE "NUMLOCK"   : bVkCode = %VK_NUMLOCK : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            
    'Caps lock
            CASE "CAPITAL"   : bVkCode = %VK_CAPITAL : GOSUB bVkCode_Down : GOSUB bVkCode_Up  
            
            CASE ELSE
        END SELECT
        
    RETURN
    
    bVkCode_Down:       'key 'bVkCode' down
        IF (UBOUND(inpAPI()) - lBufCnt) < 5 THEN REDIM PRESERVE inpAPI(lBufCnt + 16)
        INCR lBufCnt
        inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD  
        inpAPI(lBufCnt).m.ki.wVk         = bVkCode             
        inpAPI(lBufCnt).m.ki.time        = Time                
        inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(bVkCode, 0)
        
                'Intercept extended keys. They require the extended key flag.
                '
        SELECT CASE bVkCode
            CASE %VK_UP, %VK_DOWN, %VK_LEFT, %VK_RIGHT, %VK_HOME, %VK_END, %VK_INSERT, %VK_DELETE, %VK_PGUP, %VK_PGDN  
                inpAPI(lBufCnt).m.ki.dwFlags = %KEYEVENTF_EXTENDEDKEY
            CASE ELSE
                inpAPI(lBufCnt).m.ki.dwFlags = 0
        END SELECT    
    RETURN
    
    
    bVkCode_Up:         'key 'bVkCode' up
        IF (UBOUND(inpAPI()) - lBufCnt) < 5 THEN REDIM PRESERVE inpAPI(lBufCnt + 16)
        INCR lBufCnt
        inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD   
        inpAPI(lBufCnt).m.ki.wVk         = bVkCode             
        inpAPI(lBufCnt).m.ki.time        = Time
        inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(bVkCode, 0)
                
                'Intercept extended keys. They require the extended key flag.
        SELECT CASE bVkCode
            CASE %VK_UP, %VK_DOWN, %VK_LEFT, %VK_RIGHT, %VK_HOME, %VK_END, %VK_INSERT, %VK_DELETE, %VK_PGUP, %VK_PGDN  
                inpAPI(lBufCnt).m.ki.dwFlags = %KEYEVENTF_KEYUP OR %KEYEVENTF_EXTENDEDKEY
            CASE ELSE
                inpAPI(lBufCnt).m.ki.dwFlags = %KEYEVENTF_KEYUP
        END SELECT     
    RETURN
    
    
    
    AddNormalCharToBuffer:
        IF (UBOUND(inpAPI()) - lBufCnt) < 5 THEN REDIM PRESERVE inpAPI(lBufCnt + 16)
        
        iRet = VKKEYSCANEX(ASC(sChar), dKeybLayout)
        
        bVkCode     = LO(BYTE, iRet)
        bShiftState = HI(BYTE, iRet)
        IF (bVkCode = 255) AND (bShiftState = 255) THEN RETURN  'Cant do this char so skip
        
        IF BIT(bShiftState, 0) THEN     'Shift
            INCR lBufCnt
            inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD  
            inpAPI(lBufCnt).m.ki.wVk         = %VK_SHIFT             
            inpAPI(lBufCnt).m.ki.dwFlags     = 0                
            inpAPI(lBufCnt).m.ki.time        = Time
            inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(%VK_SHIFT, 0)         
        END IF
        
        IF BIT(bShiftState, 1) THEN     'Ctrl
            INCR lBufCnt
            inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD  
            inpAPI(lBufCnt).m.ki.wVk         = %VK_CONTROL             
            inpAPI(lBufCnt).m.ki.dwFlags     = 0
            inpAPI(lBufCnt).m.ki.time        = Time                
            inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(%VK_CONTROL, 0)        
        END IF
    
        IF BIT(bShiftState, 2) THEN     'Alt
            INCR lBufCnt
            inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD  
            inpAPI(lBufCnt).m.ki.wVk         = %VK_MENU             
            inpAPI(lBufCnt).m.ki.dwFlags     = 0
            inpAPI(lBufCnt).m.ki.time        = Time                
            inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(%VK_MENU, 0)        
        END IF
    
        INCR lBufCnt
        inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD  
        inpAPI(lBufCnt).m.ki.wVk         = bVkCode             
        inpAPI(lBufCnt).m.ki.dwFlags     = 0
        inpAPI(lBufCnt).m.ki.time        = Time                
        inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(bVkCode, 0)                
    
        INCR lBufCnt
        inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD   
        inpAPI(lBufCnt).m.ki.wVk         = bVkCode             
        inpAPI(lBufCnt).m.ki.dwFlags     = %KEYEVENTF_KEYUP
        inpAPI(lBufCnt).m.ki.time        = Time  
        inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(bVkCode, 0)
    
    
        IF BIT(bShiftState, 0) THEN     'Shift
            INCR lBufCnt
            inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD   
            inpAPI(lBufCnt).m.ki.wVk         = %VK_SHIFT             
            inpAPI(lBufCnt).m.ki.dwFlags     = %KEYEVENTF_KEYUP  
            inpAPI(lBufCnt).m.ki.time        = Time
            inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(%VK_SHIFT, 0)        
        END IF
        
        IF BIT(bShiftState, 1) THEN     'Ctrl
            INCR lBufCnt
            inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD   
            inpAPI(lBufCnt).m.ki.wVk         = %VK_CONTROL             
            inpAPI(lBufCnt).m.ki.dwFlags     = %KEYEVENTF_KEYUP  
            inpAPI(lBufCnt).m.ki.time        = Time
            inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(%VK_CONTROL, 0)         
        END IF
    
        IF BIT(bShiftState, 2) THEN     'Alt
            INCR lBufCnt
            inpAPI(lBufCnt).dtype            = %INPUT_KEYBOARD   
            inpAPI(lBufCnt).m.ki.wVk         = %VK_MENU             
            inpAPI(lBufCnt).m.ki.dwFlags     = %KEYEVENTF_KEYUP  
            inpAPI(lBufCnt).m.ki.time        = Time
            inpAPI(lBufCnt).m.ki.wScan       = MAPVIRTUALKEY(%VK_MENU, 0)        
        END IF            
    RETURN
        
    END SUB
    '-------------------------------------------------------------------------
    
    
    '-------------------------------------------------------------------------
    'Sub borrowed from SendKeys.inc - by William Burns
    'Wait until user lets go of the ctrl/Alt/Shift and any other keys
    SUB WaitForNoKeys    
        LOCAL KeyWasPressed  AS LONG
        LOCAL iKey           AS LONG
        DO                'loop here until user lets go of the ctrl/Alt/Shift and any other keys
            SLEEP 5                       '<-- Modified in V1.1
            KeyWasPressed = 0
            'FOR iKey = 19 TO 255         '0-9 and a-z
            FOR iKey = 0 TO 255           '<-- Modified in V1.1
                IF (GETASYNCKEYSTATE(iKey) AND &H8000) THEN KeyWasPressed = 1
            NEXT iKey
            IF (GETASYNCKEYSTATE(%VK_CONTROL) AND &H8000) OR (GETASYNCKEYSTATE(%VK_SHIFT) AND &H8000) OR (GETASYNCKEYSTATE(%VK_MENU) AND &H8000) THEN KeyWasPressed = 1  'note: %VK_MENU = %VK_ALT
        LOOP WHILE KeyWasPressed
    END SUB
    '-------------------------------------------------------------------------
    Attached Files
    Last edited by S Stamp; 28 May 2009, 11:47 PM. Reason: added another example to code comments

  • #2
    Here is an alternate version of the code. I extracted the core functionality into a separate .inc so that the main program only needs to call a single function. This version is not a commandline utility. It automatically outpulses the key sequence into the active window. The expectation is that the user would press a key combination that is recognized as a hotkey (by a utility like Auto Hotkey or Macro Express), which would trigger an auto hotkey macro which would run this PowerBASIC exe. (If you try to run the program by just launching the exe in windows explorer, you will get undesirable results because the program will outpulse its key sequence to the active window (which would be windows explorer.)

    I also added the ability to include a pause within the text sequence with the string {DELAYxxxx} where xxxx is the number of milliseconds.

    Note that you still may wish to refer to the code in the previous posting to see how to manage activating specified window names before outpulsing the keystrokes.
    Attached Files
    Last edited by S Stamp; 14 Feb 2011, 12:16 AM.

    Comment


    • #3
      Comunicating with Calculator Plus using AutoHotKey

      Not quite on topic but close. I was running a PB program that required numerous repetitions of Ctrl+V to send a string from the Clipboard to Microsoft's CalcPlus and then retrieve the result back into PB via the Clipboard with Ctrl+C. Lacking working knowledge of API and very new to AutoHotKey, I wrote this script for the latter named CalcPlus.ahk
      Code:
      #ErrorStdOut          ; Send error reports to StdOut
      ClipWait              ; Wait for clipboard to contain text
      SendInput %clipboard% ; Send clipboard contents to CalcPlus
      Clipboard =           ; Clear clipboard
      Send ^c               ; Send CalcPlus contents to clipboard
      ClipWait              ; Wait for clipboard to contain text
      Exit                  ; Terminate script
      and this little PBCC 6.02 code (here trimmed down to the basics)
      Code:
      Function CalcPlus( _
          ByVal s As String _         ' String to pass to CalcPlus
          ) As String                 ' Output will be the CalcPlus result
          ' For coding s, see CalcPlus help file topics:
          '     "Using keyboard equivalents of Calculator Plus buttons"
          '     "Using key sequences as functions"
          ' and experiment. Also see the AutoHotKey help file topic:
          '     "Send / SendRaw / SendInput / SendPlay / SendEvent: Send Keys & Clicks".
          Static ProcessId As Dword   ' Nonzero after first pass
          Local ClipResult As Long    ' Clipboard error if zero
          Local t As String           ' CalcPlust result string
          If ProcessId=0 Then         ' Do first time only
              ProcessId=Shell("C:\Program Files\Microsoft Calculator Plus\CalcPlus.exe")
              Sleep 50                ' Must wait at least ~30 msec (on my computer)
              ' Insert code below.
              If ProcessId<=32 Then ? "Shell function failed. Cause: "; Cause: GoTo ErrorHandler
          End If
          Clipboard Reset, ClipResult: If ClipResult=0 Then GoTo ErrorHandler
          Clipboard Set Text s, ClipResult: If ClipResult=0 Then GoTo ErrorHandler
          Shell "C:\Program Files\AutoHotKey\AutoHotKey.exe "+ _
              """C:\Program Files\AutoHotKey\CalcPlus.ahk"""
          Clipboard Get Text t, ClipResult: If ClipResult=0 Then GoTo ErrorHandler
          Function=UCase$(t) ' Change "e" to "E"
          Exit Function
          ErrorHandler:
              ? "Clipboard error": Close: WaitKey$: End ' Terminate
      End Function
      and it worked nicely. The program must run by itself which was just fine for my purposes.

      Edit: ProcessId contains the following documented codes if Shell failed, thanks to an MCM post that lead to:
      http://www.oehive.org/node/528.
      The following could be put after the Shell function:
      Code:
      Local Cause As String
      Select Case ProcessId 
      Case  0: Cause="The operating system is out of memory or resources."
      Case  2: Cause="The specified file was not found."
      Case  3: Cause="The specified path was not found."
      Case  5: Cause="Windows 95 only: The operating system denied access to the specified file."
      Case  8: Cause="Windows 95 only: There was not enough memory to complete the operation."
      Case 10: Cause="Wrong Windows version."
      Case 11: Cause="The .EXE file is invalid (non-Win32 .EXE or error in .EXE image)."
      Case 12: Cause="Application was designed for a different operating system."
      Case 13: Cause="Application was designed for MS-DOS 4.0."
      Case 15: Cause="Attempt to load a real-mode program."
      Case 16: Cause="Attempt to load a second instance of an application with non-readonly data segments."
      Case 19: Cause="Attempt to load a compressed application file."
      Case 20: Cause="Dynamic-link library (DLL) file failure."
      Case 26: Cause="A sharing violation occurred."
      Case 27: Cause="The filename association is incomplete or invalid."
      Case 28: Cause="The DDE transaction could not be completed because the request timed out."
      Case 29: Cause="The DDE transaction failed."
      Case 30: Cause="The DDE transaction could not be completed because other DDE transactions were being processed."
      Case 31: Cause="There is no application associated with the given filename extension."
      Case 32: Cause="Windows 95 only: The specified dynamic-link library was not found."
      Case <=32: Cause="Undocumented."
      End Select
      Also the following are the CalcPlus codes (excluding non-scientific views) with notes:
      Code:
      Button     Key           Notes
                 CalcPlus is
                 case-insensitive
       
      %          %             Standard view only; x?y%= is x?(x*y/100)= if ? is any op +,-,*,/ (so x/y%=100/y unless x*y=0)
      (          (  
      )          )  
      *          *  
      +          +             {+} in AutoHotKey
      +/-        F9            Alternatively, ilrl; no effect if display=zero; {F9} in AutoHotKey
      -          -             Subtraction, sometimes negation; may reset pending op; see +/-
      .          .             Alternatively, comma (,)
      /          /  
      0 to 9     0 to 9  
      1/x        r  
      =          ENTER         {Enter} in AutoHotKey  
      A to F     A to F        or a to f; Hex mode
      [Abs]      @ i @         Not a CalcPlus button  
      And        &  
      Ave        CTRL+A        Statistics mode; ^a in AutoHotKey  
      Backspace  BACKSPACE     {BS} in AutoHotKey  
      Bin        F8            {F8} in AutoHotKey  
      Byte       F4            {F4} in AutoHotKey  
      C          ESC           {Esc} in AutoHotKey; :q if cut and pasted  
      CE         DEL           {Del} in AutoHotKey  
      cos        o             Inverse is Arccos(); also Cosh() and Arccosh()  
      Dat        INS           {Ins} in AutoHotKey; \ if cut and pasted  
      Dec        F6            {F6} in AutoHotKey  
      Degrees    F2            {F2} in AutoHotKey  
      dms        m             Inverse is degrees <- ddd.mmsss...  
      Dword      F2            {F2} in AutoHotKey  
      Exp        x             :e if cut and pasted  
      F-E        v             Display toggle: fixed <-> exponential  
      Grads      F4            {F4} in AutoHotKey  
      Hex        F5            {F5} in AutoHotKey 
      Hyp        h             Toggles hyperbolic mode 
      Int        ;             Inverse is Frac() 
      Inv        i             Toggles inverse mode 
      ln         n             Log(); Inverse is Exp() 
      log        l             Log10(); Inverse is Exp10() 
      Lsh        <             x*2^y; Inverse is Rsh: x/2^y 
      M+         CTRL+P        ^p in AutoHotKey; :p if cut and pasted 
      [M-]       F9 CTRL+P F9  Not a CalcPlus button
      MC         CTRL+L        ^l in AutoHotKey; :c if cut and pasted 
      Mod        %             x Mod y  
      MR         CTRL+R        ^r in AutoHotKey; :r if cut and pasted 
      MS         CTRL+M        ^m in AutoHotKey; :m if cut and pasted 
      n!         !             Gam(x+1); doesn't use Simpson; {!} in AutoHotKey 
      Not        ~ 
      Oct        F7            {F7} in AutoHotKey 
      Or         | 
      pi         p             Inverse is 2*pi 
      Qword      F12           {F12} in AutoHotKey 
      Radians    F3            {F3} in AutoHotKey 
      s          CTRL+D        Statistics mode; ^d in AutoHotKey 
      sin        s             Inverse is Arcsin(); also Sinh() and Arcsinh() 
      sqrt       @             Standard view only; [email protected] in scientific view
      Sta        CTRL+S        ^s in AutoHotKey; opens statistics box 
      Sum        CTRL+T        Statistics mode; ^t in AutoHotKey 
      tan        t             Inverse is Atn(); also Tanh() and Arctanh() 
      Word       F3            {F3} in AutoHotKey 
      Xor        ^             {^} in AutoHotKey 
      x^2        @             Inverse is Sqr(x) 
      x^3        #             {#} in AutoHotKey; Inverse is cube root 
      x^y        y             Inverse is x^(1/y)
                 ALT+V s       !vs in AutoHotKey; put CalcPlus in scientific mode
                 CTRL+V        ^v in AutoHotKey; paste
                 CTRL+C        ^c in AutoHotKey; copy
      [X]        ALT+F4        !{F4} in AutoHotKey; close CalcPlus
      In Statistics box:
      CAD        a             Delete all values
      CD         c             Delete a specific value
      [click]    Down_arrow    {Down} in AutoHotKey; select next entry
      [click]    Up_arrow      {Up} in AutoHotKey; select prior entry
      LOAD       l             Copy from Statistics box to CalcPlus and return to CalcPlus
      RET        r             Return to CalcPlus; use {Ins} to send data
      [scroll]   End           {End} in AutoHotKey; select last entry
      [scroll]   Home          {Home} in AutoHotKey; select first entry
      [X]        ALT+F4        !{F4} in AutoHotKey; close statistics box
      Last edited by Mark Longley-Cook; 2 Aug 2011, 11:40 AM. Reason: Better test to see if shell worked.

      Comment

      Working...
      X