Beginners help. Variouse way's for sending/detecting key strokes

Helpful script writing tricks and HowTo's
User avatar
megnatar
Posts: 92
Joined: 27 Oct 2014, 20:49
Location: The Netherlands
Contact:

Beginners help. Variouse way's for sending/detecting key strokes

10 Nov 2015, 19:24

-------------------------A word from the author, Uhmm that would be me--------------------------
I've written this tutorial for those who having trouble sending key's and detecting mouse clicks and or key status And it's derived from my personal notes. I don't know it all so I might be wrong, and If I am. Please don't hesitate to correct me :D. All examples are working and use space bar as the trigger key with a message box/tooltip to show what you did. I expect this kind of tutorial to be posted already. But it never hurts to read a different view on the subject, so here goes.

You basically have all that you need when combining this tutorial with my application launcher script. Which will allow you to start any game and send key's/input to it's window.
Jump to: General Program Launcher

The manual that comes with ahk is very well written, I strongly suggest you read it, read it again and again!
Different situations ask for a different approach so be creative when writing you're script.

btw this tutorial is still work in progress and it might contain typo's, weird grammar (my native language is Dutch not English) and inconsistency's . I will correct those in future release.

--------------------------At the moment these subjects are covered--------------------------
The send command and it's variations.
Something about timing while sending key's
The KeyWait command, in less detail then send I might add.
A thing or two about variables and how they are evaluated.

Working examples for:
Single, double, key down status.
Repeating keystrokes using a while loop.
Triggering actions based on mouse position.
Toggle keys
Sending a different key with each key stroke.

--------------------------Send and it's variations--------------------------
A thing or two to know before using the send command. Send has a few build in modes to choose from, those will be:

- Input. Input is ahk's default for the send command and it's the quickest. It ignores SetKeyDelay.
- Event. Is less quick then input but I prefer to use it since it gives control over press duration and delay between the key's to send.
- Play. Good for sending long strings because it uses a buffer. Unfortunate nine out of 10 times useless in windows 7,8 and 10 thanks to UAC, even in full admin mode.
- Raw. Raw will send exactly what's written after the command, thats why it's raw :D. I never use it.

You can specifie the send mode to use at the top of a script using command:
SendMode, Input|Event|Play|InputThenPlay

Or you can call send command and its mode directly by using:
SendInput
SendEvent
SendPlay
SendRaw

If you write a script and want it to send it's key's to the window even when it's minimized or not active. You should use the ControlSend command.
ControlSend will also interfere the less of all send methods with user input, but it's slow.

--------------------------Timing is very important when sending--------------------------
Actually timing is very important when writing scripts in general.
When sending a key to a window you might take the following in to consideration:

- The total time it takes to send the key.
- The time it takes for the program you're sending it to, to complete it's action.

Sounds simple enough right?

Lets sketch a situation.
You send the 'W' key to some program and the action it takes for whatever W does takes 500ms to complete.
You use SendEvent and have SetkeyDelay set to 100ms delay and press duration of 50ms.
When you want the W key to repeat its action twice, then the following code will most likely fail.

Code: Select all

SendEvent,{w down}{w up}{w down}{w up}
Why? Well PressDuration+TimeBetween sending key's takes a total of 150ms for one key stroke.
W key will be send twice to the program, but this SendEvent will take a minimum of 300ms to complete.
Since the second W down will be send a little after 150ms, the program you're sending it to is still busy completing it's first action.
You could solve this in two way's. The first is by sleeping between the send command.

Code: Select all

SendEvent,{w down}{w up}		;150ms
sleep, 350						;150+350=500ms
SendEvent,{w down}{w up}
The second method. You could temperately increase the keydelay and setting it back once the actions are done.

Code: Select all

SetKeyDelay, 450, 50
SendEvent,{w down}{w up}{w down}{w up}
SetKeyDelay, 100, 50
return
--------------------------Double, Single, keydown--------------------------
Many way's to acomplish this. Here is one of the way's I do that.
I strongly advice against using SetTimer for sending key's because it will always wait to timeout before doing anything else.
The beauty of KeyWait is that it will not wait to timeout when specified state is reached.
When that state is reached KeyWait will set errorlevel to 0.

This means keywait will wait forever for space to be released before continuing.
KeyWait, space

This means keywait will wait forever for space to be pressed down before continuing.
KeyWait, space , D

This means keywait will wait 1 second for space to be released before continuing.
KeyWait, space , T1

Thus the D stands for Down, the T stands for Period/Time. T is in seconds but you can use a float.

A double, Single or keydown.

Code: Select all

Space::                                         ;The moment you hit space keywait comes in action.
keywait, Space, T0.20							;Keywait waits 200ms for space to be released.
If errorlevel = 0								;When you release space keywait sets errorlevel to 0, the script will continue immediately and skips the timeout.
{												;So when errorlevel was 0, the actions in here are triggered. You could send a key here but it will always trigger.
	KeyWait, Space, D T0.10						;Instead, make the script wait again. This time checking if the space is pushed down again by using the 'D' parameter
	If errorlevel = 0							;Once pushed down again errorlevel again is set to zero and the thread wil continue
	{
		MsgBox Twice							;When key was down again errorlevel is set to zero and this action will trigger. This action is triggered by a double click.
	}
    else
    If errorlevel = 1							;And else the key was not down once more so its still up, meaning this was a single click. I actually advice not to override
    {											;the single click  wehen using this on eighter Lbutton or Rbutton. because it waits 100ms making it slower than the one used in the 
      MsgBox Once								;program you're sending to. See example below.
    }
    Return										;Returning here because we are finished with what we came here to do.
}
else
If errorlevel = 1								;Now! When the very first keywait in space:: timed-out, it will set errorlevel to 1! Meaning the key is still down.
{
    while GetKeyState("Space", "P")				;Checking if the key is still down and as long as it is, do whatever comes below.
	{													
        MsgBox Down
		Keywait, space
    }
}
return											;Returning here because we are finished with what we came here to do.
Using the above code on the left or right mouse button, would override the default action for the program you're sending to.
I advice against overriding the single click since it's probably slower than the programs native single click.
Instead you could use this code with the tilde ~ at the beginning of the Lbutton hotkey.
Tilde ~ tells the script not to block the native function of the key, thus retaining the programs original action.
The following code will be like a add-on.

Code: Select all

~Lbutton::
keywait, Lbutton, T0.20
If errorlevel = 0
{
	KeyWait, Lbutton, D T0.20					;But If you really need to override the Lbutton default action you could place the send command above this keywait.
	If errorlevel = 0							;It will always be send and would basically be the same as what this code does.
	{
		MsgBox Twice
	}
    Return
}
else
If errorlevel = 1
{
    while GetKeyState("Lbutton", "P")
	{													
        MsgBox Down
		Keywait, Lbutton
    }
}
return
--------------------------About the while loop!--------------------------
I use the while GetKeyState("Space", "P") function in two way's. One using sleep, and will repeat the key to send.
The other using keywait, and is used to simulate keydown.

Repeating a keystrokes/commands using a while loop.

Code: Select all

Space::
Seconds := -1
while GetKeyState("Space", "P")					;Checking if the key is down and as long as it is, do whatever comes below.
{
	Seconds++
	ToolTip % "The total number of seconds you're holding space down."  "`n"  
	. "Seconds = " Seconds						;Replacing the tooltip and seconds variable with a 'send, {Key down}{key up}' command will send that key once a second.
	sleep, 1000									;Keep in mind not to sleep a to short period. You might crash the window you're sending to or get flagged in a online game.
}
Tooltip
Return											;Return here so the while loop can iterate freshly. Iteration=the endless repetition of a calculation/code. I don't recommend 
												;returning from inside a while loop, due to the nature of while. While returns automatically that why while is 'as long as'. 
Holding space down to simmulate a other key down

Code: Select all

Space::
while GetKeyState("Space", "P")					;Checking if the key is down and as long as it is, do whatever comes below.
{	
	ToolTip % "You are holding space down"  "`n"  
	. "Here you could do something like" "`n`n"
	. "Send, {w down}" "`n"
	. "KeyWait w" "`n"
	. "Send, {w up}"
	Keywait, space								;Keywait wil wait infinite for the key to be released when there is no duration specified.
	Break										;Although its not really needed here, it's a good habit to break free from a while loop where ever you can.
}
Tooltip
Return
Last while loop example. This is something I actually use in RoboCraft.
I use it for my machine-gun(smg) bot and it gives me more accurate shooting.
The developers of that game simulated a recoil effect by lowering the accuracy when firing longer than +/- 1000ms(1second). Below code will pulsate firing.

If I explained everything well and you read my text properly then you should be able to understand which mouse click does what. I Use the Random command to randomize the sleep period between release and press key down again. This make the scripted hotkey harder to detect on the server side. You need to initiate the value's the first time the hotkey is fired, putting the Random commend below the keywait and its actions so it interferes the less waiting period.

Code: Select all

~Lbutton::
keywait, Lbutton, T0.20
If errorlevel = 0
{
	KeyWait, Lbutton, D T0.40
	If errorlevel = 0
	{
        while GetKeyState("Lbutton", "P")
        {
            SendEvent, {Lbutton down}
            Sleep, %vtToUp%
            SendEvent, {Lbutton up}
            Sleep, %vtToDwn%
        }
        Random, vtToUp, 974, 1033
        Random, vtToDwn, 492, 531
    }
}
Return
--------------------------Actions based on mouse position--------------------------
In this example a action is performed when moving the mouse along the X axes. There is lot of number joggling going on so I suggest switching SetBatchline, -1 on at the top
of you're script when using this. I used this to align camera's or getting pixel color and if red trigger some key. Here I replaced those things with a tooltip to display the numbers.

Code: Select all

SetBatchLines, -1			;We need maximum performance here. SetBatchLines, -1 gives maximum CPU clock.
CoordMode, Mouse, window

i_left=10					;integer holding positive number of pixel before a action is trigged.
i_right=-10					;integer holding negative number of pixel before a action is trigged.

~LButton::
MouseGetPos, Mouse_x				;Getting the X position from where we start calculating, lets say it's 10 (10 is a static number).
while GetKeyState("Lbutton", "P")
{
    MouseGetPos, Relative_x			;Getting Relative x position and is 'refreshed' each time the while loop is iterated. If the mouse moved 3 pixels right Relative_X holds 13
    i_pos:=Mouse_x-Relative_x		;Integer for checking against right or left. It's rounded to up zero if the mouse did not move, and holds a positive or negative total from the starting position. Mouse_X and 
									;Relative_x are joined together by the := operator and are joined from right to left. Reads like: Relative_x minus Mouse_X is the value stored in i_pos. 13-10=-3
    If(i_left <= i_pos)				;Each time while is iterated it reads the if statement, here if means. If i_left is smaller or equal to i_pos execute below code.
    {
        MouseGetPos, Mouse_x		;Re getting mouse_x so we have a new number to start calculating from.
        tl++						;Just a variable to show the total amount of times this if statement was executed while dragging the mouse left.
    }
    If(i_right >= i_pos)
    {
        MouseGetPos, Mouse_x
        tr++
    }
     ToolTip, % "i_pos = "i_pos "`n" 
    . "Min = " i_left "`n"
    . "Plus = " i_right "`n"
    .  "Total times left = " tl "`n"
    .  "Total times right = " tr "`n"
    . "Mousex = " Mouse_x "`n"
    . "Relative_x = " Relative_x "`n"
    sleep, 10
}
tl=
tr=
ToolTip
return
--------------------------Toggle key--------------------------
Again many way's to toggle key's on or off. I find this method the best way for doing so.

Code: Select all

Space::											;When space is pressed the first time, bAutorun is created and is empty.
If (bAutorun = "" ){							;If bAutorun is blank, so it's holding no value. Do whatever comes below. 
    Msgbox Space is presed for the first time.`nSend, {w down} here.`n`nVariable bAutorun = %bAutorun%
    bAutorun++									;The ++ operator will increment the value in bAutorun by 1. bAutorun now hold the number 1. bAutorun+=1 would do the same.
}Else{											;When above 'if' statement evaluates false, meaning the key was already pressed down once and that bAutorun holds 1.
	Msgbox Space is presed for the second time.`nSend, {w up} again.`n`nVariable bAutorun = %bAutorun%
	bAutorun =									;Delete whatever is in bAutorun. bAutorun is empty once more.
}
Return
--------------------------Looping through key's 1 by 1--------------------------
Sometimes its handy to have a single key pressing a different key with each key stroke.
Here with each press from space bar, key's 1, 2, 3, 4 ore 5 are send. Handy for triggering things like
skill one, skill two then skill three, etc, or Hitpoint Pot 1, 2, 3, 4 then 5 and so on.

Code: Select all

vKey := 1										;Starting out with a variable that holds the number 1.

space::
If(vKey >= 5)									;If variable vkey holds 5 or is greater then 5 (>=) do whatever comes below. Why greater and not equal?
{  												;Make it a habit to exclude all that is not needed, Don't allow unwanted exceptions. Now vKey can never surpass 5 so its solid.
    msgbox command to send would be:`nSend, {%vKey%}
    vKey := 1									;Setting the variable vkey to hold the number 1 again.	
    return										;Returning here because we are finished with what we came here to do.
}
else
If(vKey := vKey)								;As long as variable vkey matches it self (evaluates as true) do whatever comes below.
{												;Sending key 1, 2 ,3 or 4 here
   msgbox comand to send would be:`nSend, {%vKey%}			
   vKey++										;Adding 1 to variable vkey. vKey will holds the numbers 1, 2, 3, 4 and is only incremented to 5 here.
}
return											;Returning here because we are finished with what we came here to do. Goes for the if statement and space:: hotkey.
Last edited by megnatar on 07 Dec 2015, 13:59, edited 7 times in total.
Everything we call real is made of things that cannot be regarded as real!
N.Bohr.

Really, that probability is true!
User avatar
megnatar
Posts: 92
Joined: 27 Oct 2014, 20:49
Location: The Netherlands
Contact:

Re: Beginners help. Variouse way's for sending/detecting key strokes

20 Nov 2015, 10:24

Indeed, for those who don't want to learn. but that was not the aim this tut.

Nice tool i must say.

Return to “Tutorials (v1)”

Who is online

Users browsing this forum: No registered users and 21 guests