[Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

Post gaming related scripts
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

[Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

26 Dec 2016, 15:18

Many games (eg most FPSes) do not pay attention to the position of the mouse cursor, they take input from the mouse at a lower level.
This library is complimented by my MouseDelta library which can show you what the input looks like coming from a real mouse.
At this level, movement input from the mouse consists of a series of small "delta" packets (eg x:+10, y:0 for a moderate movement to the right) where the timing of the movements can hold meaning. If you use the sample script from MouseDelta, you will note that mouse move messages can happen as frequently as 1ms apart, but in AHK, the Sleep command is only accurate down to ~10ms.
Therefore, it can often be desirable to implement something capable of doing <10ms sleeps, like QPX. This presents a further layer of complexity for novices.

So I decided to wrap the whole thing up in a package that makes it easy for anyone to issue some basic commands, and let the code handle the complexities. If the rate is >= 10ms, it uses Sleep. If below 10ms, it uses QPX. All you need to do is tell it what you want to do, and optionally how many times and and what rate, and it works out the rest.

Currently, I have code in there for move and sending mouse wheel, but ultimately I guess it could wrap anything that mouse_event supports.

Code: Select all

; =========== Sample script ============================================================
#SingleInstance,Force

; Send 2x mouse wheel down rolls at rate of 100ms 
F11::
	LLMouse.Wheel(-1, 2, 100)
	return

; Send 100x 10 unit mouse moves for the x axis at a rate of 2ms
F12::
	LLMouse.Move(0, 10, 100, 2)
	return

; =======================================================================================
; LLMouse - A library to send Low Level Mouse input

; Note that many functions have time and rate parameters.
; These all work the same way:
; times	- How many times to send the requested action. Optional, default is 1
; rate	- The rate (in ms) to send the action at. Optional, default rate varies
; Note that if you use a value for rate of less than 10, special code will kick in.
; QPX is used for rates of <10ms as the AHK Sleep command does not support sleeps this short
; More CPU will be used in this mode.
class LLMouse {
	static MOUSEEVENTF_MOVE := 0x1
	static MOUSEEVENTF_WHEEL := 0x800
	
	; ======================= Functions for the user to call ============================
	; Move the mouse
	; All values are Signed Integers (Whole numbers, Positive or Negative)
	; x		- How much to move in the x axis. + is right, - is left
	; y		- How much to move in the y axis. + is down, - is up
	Move(x, y, times := 1, rate := 1){
		this._MouseEvent(times, rate, this.MOUSEEVENTF_MOVE, x, y)
	}
	
	; Move the wheel
	; dir	- Which direction to move the wheel. 1 is up, -1 is down
	Wheel(dir, times := 1, rate := 10){
		static WHEEL_DELTA := 120
		this._MouseEvent(times, rate, this.MOUSEEVENTF_WHEEL, , , dir * WHEEL_DELTA)
	}
	
	; ============ Internal functions not intended to be called by end-users ============
	_MouseEvent(times, rate, dwFlags := 0, dx := 0, dy := 0, dwData := 0){
		Loop % times {
			DllCall("mouse_event", uint, dwFlags, int, dx ,int, dy, uint, dwData, int, 0)
			if (A_Index != times){	; Do not delay after last send, or if rate is 0
				if (rate >= 10){
					Sleep % rate
				} else {
					this._Delay(rate * 0.001)
				}
			}
		}
	}
	
	_Delay( D=0.001 ) { ; High Resolution Delay ( High CPU Usage ) by SKAN | CD: 13/Jun/2009
		Static F ; www.autohotkey.com/forum/viewtopic.php?t=52083 | LM: 13/Jun/2009
		Critical
		F ? F : DllCall( "QueryPerformanceFrequency", Int64P,F )
		DllCall( "QueryPerformanceCounter", Int64P,pTick ), cTick := pTick
		While( ( (Tick:=(pTick-cTick)/F)) <D ) {
			DllCall( "QueryPerformanceCounter", Int64P,pTick )
			Sleep -1
		}
		Return Round( Tick,3 )
	}
}
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

29 Dec 2016, 12:31

Hello.
I have a few comments on this, I did something similar not so long ago when I did a simple mouse recorder. To make an accurate playback, this kind of accuracy is very important.
evilC wrote:Sleep command is only accurate down to ~10ms.
I think this is wrong, or I misunderstand the wording, I'd say, the sleep command is only accurate within (if you are lucky) 10 ms. Meaning, Sleep, t yields a sleep of t ± 10 ms. regardless of the value of t. There is no higher (absolute) accuracy for longer sleeps.
Hence, I thinke there is a flaw (or perhaps it is a choise I do not agree with) in this script, specifically this,

Code: Select all

if (rate >= 10){
	Sleep % rate
} else {
	this._Delay(rate * 0.001)
}
You can see why this is problematic by setting up this test (results may vary between systems):

Code: Select all

F12::
	SetBatchLines,-1
	if !freq
		DllCall( "QueryPerformanceFrequency", Int64P,freq )
	DllCall( "QueryPerformanceCounter", Int64P,t1 )
	LLMouse.Move(0, 1, 100, 9)
	DllCall( "QueryPerformanceCounter", Int64P,t2 )
	LLMouse.Move(0, -1, 100, 11)
	DllCall( "QueryPerformanceCounter", Int64P,t3 )
	MsgBox, % "Total time for LLMouse.Move(0, 1, 100, 9):`t`t" (t2-t1)*1000/freq " ms.`nTotal time for LLMouse.Move(0, -1, 100, 11):`t" (t3-t2)*1000/freq " ms."
	return
On my system the first move is highly accurate (w.r.t. expected time to finish), while the second isn't. It is due to the fact that (on my system) a Sleep, (t<=20) sleeps anywhere between near 0 and slightly over 20 ms, on average, it sleeps 15.6 ms. So for every move, there is an error of, on average 4.6 ms, which adds up to almost half a second for 100 moves.
To remedy this, for higher rates, you need to either always call _delay(.) with the full sleep time, which eats a lot of CPU-power, or, first do a regular (adjusted) sleep, measure how long it took, and then call _delay(.) with the remaining time, here is an example of the last option,

Code: Select all

; Conceptual.
SetBatchLines,-1
DllCall("QueryPerformanceFrequency", "Int64P" ,freq )
; Regular sleep, for reference:
1::
	sleepTime:=37 ; Test a few different ones.
	DllCall("QueryPerformanceCounter", "Int64P",t1 )
	sleep, % sleepTime
	DllCall("QueryPerformanceCounter", "Int64P",t2 )
	MsgBox, % "Slept for:`t" (t2-t1)*1000/freq
return
; Make an accurate sleep:
2::
	sleepTime:=37 ; Test a few different ones.
	DllCall("QueryPerformanceCounter", "Int64P",t1 )
	if (sleepTime>=25)	; Do inaccurate sleep first
	{
		DllCall("QueryPerformanceCounter", "Int64P",sT1)
		Sleep, % sleepTime-10
		DllCall("QueryPerformanceCounter", "Int64P",sT2)
		sleepTime-=(sT2-sT1)*1000/freq ; Subtract the time spent on the Sleep.
	}
	; Do accurate sleep - this is basically _delay()
	DllCall("QueryPerformanceCounter", "Int64P", loopStart)
	loopTic:=0
	while (loopTic-loopStart < sleepTime/1000*freq)
		DllCall("QueryPerformanceCounter", "Int64P", loopTic)
	; Total sleep done
	DllCall( "QueryPerformanceCounter", "Int64P",t2 )
	MsgBox, % "Slept for:`t" (t2-t1)*1000/freq
return
Note that here I have set if (sleepTime>=25) ; Do inaccurate sleep, I don't think you could get a way with setting this much lower, unfortunately, but the this set up has not been optimised.
guest3456
Posts: 3454
Joined: 09 Oct 2013, 10:31

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

29 Dec 2016, 12:43

ehhh

https://msdn.microsoft.com/en-us/librar ... s.85).aspx
The mouse_event function synthesizes mouse motion and button clicks.

Note This function has been superseded. Use SendInput instead.
?

User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

29 Dec 2016, 13:13

@guest3456: Yeah, I have proof of concept code for SendInput also, at some point I should make the switch, but for now mouse_event does the job.

@Helgef: Any ideas on how best to solve this? I could always add methods that control whether it uses QPX or not, but seeing as the goal was to simplify things down, it may put too much burden of knowledge on the end-user.
How about maybe a static function that gets called on startup, which then does some calibration? (eg issues a few sleep 10s and checks how long they actually took)
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

29 Dec 2016, 19:15

evilC wrote: @Helgef: Any ideas on how best to solve this?
Well, hotkey 2:: was my idea ;)
Anyhow, I did some testing, and I found that it is actually much better to use DllCall("Sleep", ...) for this, ahk's Sleep, ... gives me sleep times all over the place. And, apparently, one can get the current timer resolution (from a hidden API(?)), which seems to reflect the accuracy of the Sleep() function. Enough with the chitchat, this seems to work pretty perfect for me, w.r.t. accuracy and minimal time spent in the heavy cpu while-loop,

Code: Select all

class LLMouse {
	static MOUSEEVENTF_MOVE := 0x1
	static MOUSEEVENTF_WHEEL := 0x800
	; ======================= Functions for the user to call ============================
	; Move the mouse
	; All values are Signed Integers (Whole numbers, Positive or Negative)
	; x		- How much to move in the x axis. + is right, - is left
	; y		- How much to move in the y axis. + is down, - is up
	Move(x, y, times := 1, rate := 1){
		this._MouseEvent(times, rate, this.MOUSEEVENTF_MOVE, x, y)
	}
	
	; Move the wheel
	; dir	- Which direction to move the wheel. 1 is up, -1 is down
	Wheel(dir, times := 1, rate := 10){
		static WHEEL_DELTA := 120
		this._MouseEvent(times, rate, this.MOUSEEVENTF_WHEEL, , , dir * WHEEL_DELTA)
	}
	
	; ============ Internal functions not intended to be called by end-users ============
	_MouseEvent(times, rate, dwFlags := 0, dx := 0, dy := 0, dwData := 0){
		res:=LLMouse.getTimerResolution() ; Calling this every event since it may be changed by other applications, as far as I understand.
		Loop % times {
			dt:=0
			DllCall("mouse_event", uint, dwFlags, int, dx ,int, dy, uint, dwData, int, 0)
			if (A_Index != times && rate)	; Do not delay after last send, or if rate is 0
				LLMouse.accurateSleep(rate,res)
		}
	}
	accurateSleep(t,res)
	{
		static F := LLMouse.getQPF()
		Critical
		dt:=0
		if (t > res){
			DllCall("QueryPerformanceCounter", "Int64P", sT1)
			DllCall("Sleep", "Int", t-res)
			DllCall("QueryPerformanceCounter", "Int64P", sT2)
			dt:=(sT2-sT1)*1000/F
		}
		t-=dt
		DllCall( "QueryPerformanceCounter", Int64P,pTick ), cTick := pTick
		While( pTick-cTick <t*F/1000 ) {
			DllCall( "QueryPerformanceCounter", Int64P,pTick )
			Sleep -1 ; Not sure about this one.
		}
		Return 
	}
	
	getTimerResolution()
	{
		DllCall("ntdll.dll\NtQueryTimerResolution", "UPtr*", MinimumResolution, "UPtr*", MaximumResolution, "UPtr*", CurrentResolution)
		return Ceil(CurrentResolution/10000) ; Resolutions are reported as 100-nanoseconds
	}
	
	getQPF()
	{
		DllCall( "QueryPerformanceFrequency", Int64P,F)
		return F
	}
	
}
On with the chitchat again:
I moved around the code a bit, since I might want to have the accurateSleep() for other purposes.
I was a bit surprised that my CurrentResolution was 1 ms. I thought it'd be lower (that is a higher value). But brief testing suggests that DllCall("Sleep") is accurate within ~1 ms here. Not so with ahk Sleep, ... though.
Also note, that the timer resolution can be changed by other applications, at any time, so I wouldn't set it as static, at least not for the whole duration of the application's run time. Above, it is called once per _MouseEvent().
ground77
Posts: 12
Joined: 01 Mar 2016, 06:31

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

28 Mar 2017, 10:18

Excuse me but is there any license for this ?
UserX14
Posts: 1
Joined: 04 Sep 2020, 02:34

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

04 Sep 2020, 02:38

Hallo!
Very interesting, thanks. After the first trigger, I have a short pause, and then quite fast repetitions. Can you somehow remove this first pause?
Thank!
ryandh94
Posts: 3
Joined: 19 Aug 2020, 21:13

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

04 Oct 2020, 15:27

@evilC you got discord? you should add me if you could i want to ask some stuff if you got time
discord unknownrandy#9221
pilger
Posts: 4
Joined: 01 Apr 2016, 13:27

Re: [Library] LLMouse - Send low-level mouse input (Optionally at sub-10ms rates)

23 Dec 2020, 01:15

They @evilC!

First of all, thanks for all the contribution to the AHL community. I've been rescued by your replies in other people's threads more times than I can count.

I'm trying to use LLMouse and MouseDelta together, but without interfering with one another. For instance, I need the LLMouse to move the mouse without MouseDelta detecting that movement. I need MouseDelta only to monitor the real movements from the physical mouse.

Is that possible? Maybe sending the mouse_event to a different device ID?

I've done quite a few hours of research and found nothing but dead ends.

Return to “Gaming Scripts (v1)”

Who is online

Users browsing this forum: No registered users and 24 guests