How can I capture and send raw mouse input in Autohotkey? - autohotkey

I know how to capture mouse movement in general, relative to the screen, but what about in games where mouse movement is being used independently from the mouse's position on the screen?
For example, in a game your cursor is hidden but you can keep moving your mouse to the left infinitely and turn in circles, far further than there's room for your mouse to move on your screen. The game might lock your invisible mouse into the center, or let it move until it hits the edge of the window, but at that point any attempt to record the mouse's movement relative to the screen is useless.
So how can I capture / send raw mouse input. For example if I want to tell the player to turn 1000 degrees to the left via mouse input and record the mouse input from a player turning 1000 degrees to the left, how can I do both of those things?
My goal is ultimately to record a player's various control including mouse input to create one of those systems which record and play back user input. I've searched all over the ahk docs and Google and found nothing about capturing and sending raw mouse input.

This has been taken care of for you by a fantastic user on the AHK forums named evilC.
It's called mouse delta and tracks changes in raw mouse input.
I've posted one of the few different variants of his mouse delta script. Make sure you follow the link above to see all the different ones he has done.
Again, this is not my work.
; Instantiate this class and pass it a func name or a Function Object
; The specified function will be called with the delta move for the X and Y axes
; Normally, there is no windows message "mouse stopped", so one is simulated.
; After 10ms of no mouse movement, the callback is called with 0 for X and Y
Class MouseDelta {
State := 0
__New(callback){
;~ this.TimeoutFn := this.TimeoutFunc.Bind(this)
this.MouseMovedFn := this.MouseMoved.Bind(this)
this.Callback := callback
}
Start(){
static DevSize := 8 + A_PtrSize, RIDEV_INPUTSINK := 0x00000100
; Register mouse for WM_INPUT messages.
VarSetCapacity(RAWINPUTDEVICE, DevSize)
NumPut(1, RAWINPUTDEVICE, 0, "UShort")
NumPut(2, RAWINPUTDEVICE, 2, "UShort")
NumPut(RIDEV_INPUTSINK, RAWINPUTDEVICE, 4, "Uint")
; WM_INPUT needs a hwnd to route to, so get the hwnd of the AHK Gui.
; It doesn't matter if the GUI is showing, it still exists
Gui +hwndhwnd
NumPut(hwnd, RAWINPUTDEVICE, 8, "Uint")
this.RAWINPUTDEVICE := RAWINPUTDEVICE
DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
OnMessage(0x00FF, this.MouseMovedFn)
this.State := 1
return this ; allow chaining
}
Stop(){
static RIDEV_REMOVE := 0x00000001
static DevSize := 8 + A_PtrSize
OnMessage(0x00FF, this.MouseMovedFn, 0)
RAWINPUTDEVICE := this.RAWINPUTDEVICE
NumPut(RIDEV_REMOVE, RAWINPUTDEVICE, 4, "Uint")
DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
this.State := 0
return this ; allow chaining
}
SetState(state){
if (state && !this.State)
this.Start()
else if (!state && this.State)
this.Stop()
return this ; allow chaining
}
Delete(){
this.Stop()
;~ this.TimeoutFn := ""
this.MouseMovedFn := ""
}
; Called when the mouse moved.
; Messages tend to contain small (+/- 1) movements, and happen frequently (~20ms)
MouseMoved(wParam, lParam){
Critical
; RawInput statics
static DeviceSize := 2 * A_PtrSize, iSize := 0, sz := 0, pcbSize:=8+2*A_PtrSize, offsets := {x: (20+A_PtrSize*2), y: (24+A_PtrSize*2)}, uRawInput
static axes := {x: 1, y: 2}
; Get hDevice from RAWINPUTHEADER to identify which mouse this data came from
VarSetCapacity(header, pcbSize, 0)
If (!DllCall("GetRawInputData", "UPtr", lParam, "uint", 0x10000005, "UPtr", &header, "Uint*", pcbSize, "Uint", pcbSize) or ErrorLevel)
Return 0
ThisMouse := NumGet(header, 8, "UPtr")
; Find size of rawinput data - only needs to be run the first time.
if (!iSize){
r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", 0, "UInt*", iSize, "UInt", 8 + (A_PtrSize * 2))
VarSetCapacity(uRawInput, iSize)
}
sz := iSize ; param gets overwritten with # of bytes output, so preserve iSize
; Get RawInput data
r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", &uRawInput, "UInt*", sz, "UInt", 8 + (A_PtrSize * 2))
x := 0, y := 0 ; Ensure we always report a number for an axis. Needed?
x := NumGet(&uRawInput, offsets.x, "Int")
y := NumGet(&uRawInput, offsets.y, "Int")
this.Callback.(ThisMouse, x, y)
;~ ; There is no message for "Stopped", so simulate one
;~ fn := this.TimeoutFn
;~ SetTimer, % fn, -50
}
;~ TimeoutFunc(){
;~ this.Callback.("", 0, 0)
;~ }
}

Related

How to Teleport a mouse (not MouseMove) in ahk?

I am trying to play games using a remote desktop software called rustdesk and I have created a script to move mouse 360 degrees in horizontal manner but as soon as the mouse goes from left to right the character in the game also moves so how to MoveMouse without seeing those changes in rustdesk.
This is the code:
#Persistent
SetTimer, check, -100
return
check(){
If edge("left")
{
MouseMove, 1718, 490, 0
}
else if edge("right")
{
MouseMove, 0, 490, 0
}
; ...
SetTimer, check, -100
}
edge(pos){
SysGet, VirtualWidth, 78
SysGet, VirtualHeight, 79
T := 10
CoordMode, Mouse, Screen
MouseGetPos, mX, mY
left := (mX < T)
top := (mY < T)
right := (mX > VirtualWidth - T)
bottom := (mY > VirtualHeight - T)
If (pos = "left")
return left
else if (pos = "top")
return top
else if (pos = "right")
return right
else if (pos = "bottom")
return bottom
}
I tried using BlockInput but it completely stops the mouse.

AHK PixelSearch Loop

I'm trying to get my AHK Script to Work.
Basically i want to find a row of x Pixels with the Color 0x26FDFD (BGR). But i don't know the AHK Scripting language well wnough to think about a smart, clean and easy way to program that loop, that the starting point will be modified based on the last found coordinates.
However, here's what i got so far:
SysGet, VirtualWidth, 78
SysGet, VirtualHeight, 79
;Farbe nach der gesucht wird:
ColorVar := 0x26FDFD
i:=0
while i < 10
{
PixelSearch, FoundX, FoundY, 0, 0, VirtualWidth, VirtualHeight, %ColorVar%, 3, Fast
if (ErrorLevel = 2)
{
MsgBox Could not conduct the search.
return
}
else if (ErrorLevel = 1)
{
MsgBox Color could not be found on the screen.
return
}
else
{
MouseMove, %FoundX%, %FoundY%
MsgBox Found a Pixel at %FoundX%x%FoundY%.
;return
}
i++
}
Kinda stupid and basic question, but somehow i can't figure it out.
I just needed to store the X and Y coordinates at the end of each loop, then set the new startpoint accordingly.
here's the code:
i:=0
while i < 5
{
PixelSearch, FoundX, FoundY, startX%A_Index%, startY%A_Index%, VirtualWidth, VirtualHeight, %ColorVar%, 3, Fast
Switch ErrorLevel
{
Case 1:
MsgBox Website ueberpruefen!
return
Case 2:
MsgBox Makro ueberpruefen!
return
Default:
MouseMove, %FoundX%, %FoundY%
;MsgBox Found a Pixel at %FoundX%x%FoundY%.
nextLoop := A_Index + 1
startX%nextLoop% := FoundX + 1
startY%nextLoop% := FoundY
}
i++
}
;msgbox % "found 5 matches!, first at: " startX2 "x" startY2
return

In Autohotkey how to wait until screen is on?

I am using a script to switch between my TV and my monitor and also switching the audio (TV or speakers). My problem is, if the TV is not active the audio isn't in the speakers list, so I want to halt my script to wait until the TV is on. Is there a way to check this?
Found this code, gets all display devices. So all I needed to do is check if the device with the certain name exists...
CheckIfDisplayExists:
Gosub, GetDisplayDevices
For DeviceName, DeviceID in Devices
{
If (InStr(DeviceName, "SAMSUNG"))
{
; < display exists, run the rest of the code >
}
}
This is the code to get all display devices:
GetDisplayDevices:
; http://www.daveamenta.com/2011-05/programmatically-or-command-line-change-the-default-sound-playback-device-in-windows-7/
Devices := {}
IMMDeviceEnumerator := ComObjCreate("{BCDE0395-E52F-467C-8E3D-C4579291692E}", "{A95664D2-9614-4F35-A746-DE8DB63617E6}")
; IMMDeviceEnumerator::EnumAudioEndpoints
; eRender = 0, eCapture, eAll
; 0x1 = DEVICE_STATE_ACTIVE
DllCall(NumGet(NumGet(IMMDeviceEnumerator+0)+3*A_PtrSize), "UPtr", IMMDeviceEnumerator, "UInt", 0, "UInt", 0x1, "UPtrP", IMMDeviceCollection, "UInt")
ObjRelease(IMMDeviceEnumerator)
; IMMDeviceCollection::GetCount
DllCall(NumGet(NumGet(IMMDeviceCollection+0)+3*A_PtrSize), "UPtr", IMMDeviceCollection, "UIntP", Count, "UInt")
Loop % (Count)
{
; IMMDeviceCollection::Item
DllCall(NumGet(NumGet(IMMDeviceCollection+0)+4*A_PtrSize), "UPtr", IMMDeviceCollection, "UInt", A_Index-1, "UPtrP", IMMDevice, "UInt")
; IMMDevice::GetId
DllCall(NumGet(NumGet(IMMDevice+0)+5*A_PtrSize), "UPtr", IMMDevice, "UPtrP", pBuffer, "UInt")
DeviceID := StrGet(pBuffer, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "UPtr", pBuffer)
; IMMDevice::OpenPropertyStore
; 0x0 = STGM_READ
DllCall(NumGet(NumGet(IMMDevice+0)+4*A_PtrSize), "UPtr", IMMDevice, "UInt", 0x0, "UPtrP", IPropertyStore, "UInt")
ObjRelease(IMMDevice)
; IPropertyStore::GetValue
VarSetCapacity(PROPVARIANT, A_PtrSize == 4 ? 16 : 24)
VarSetCapacity(PROPERTYKEY, 20)
DllCall("Ole32.dll\CLSIDFromString", "Str", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "UPtr", &PROPERTYKEY)
NumPut(14, &PROPERTYKEY + 16, "UInt")
DllCall(NumGet(NumGet(IPropertyStore+0)+5*A_PtrSize), "UPtr", IPropertyStore, "UPtr", &PROPERTYKEY, "UPtr", &PROPVARIANT, "UInt")
DeviceName := StrGet(NumGet(&PROPVARIANT + 8), "UTF-16") ; LPWSTR PROPVARIANT.pwszVal
DllCall("Ole32.dll\CoTaskMemFree", "UPtr", NumGet(&PROPVARIANT + 8)) ; LPWSTR PROPVARIANT.pwszVal
ObjRelease(IPropertyStore)
ObjRawSet(Devices, DeviceName, DeviceID)
}
ObjRelease(IMMDeviceCollection)
return

How to reset a key sequence with a specific key?

So this sequence resets itself after 1.5 sec (t:=1500) which means if i dont hit the left mouse button for 1.5 sec it always sends A. Otherwise it sends the next letter after each click.
I want to further tweak this code with another function which is to be able to reset the sequence with right mouse button too. So if i hit RButton any time it should reset to A.
Thx.
global s:=0, c:=0, t:=1500
*LButton::
Send % Seqkeys("A","B","C")
KeyWait, LButton
Send, R
return
Seqkeys(params*) {
global s, c, t
max := params.MaxIndex()
(A_TickCount-s<=t && (c+=1)<=max) ? c : c:=1
s := A_TickCount
return params[c]
}
Merely reset the current key index 'c', and the last clicked time 's':
*RButton::
c := 1
s := 0
return
I think your script would benefit with more meaningful variable names:
global lastClickedTime:=0, currentKeyIndex:=0, clickThreshold:=1500
*LButton::
Send % Seqkeys("A","B","C")
KeyWait, LButton
Send, R
return
*RButton::
currentKeyIndex := 1
clickThreshold := 0
return
Seqkeys(params*) {
global lastClickedTime, currentKeyIndex, clickThreshold
max := params.MaxIndex()
currentKeyIndex += 1
if((A_TickCount - lastClickedTime) <= clickThreshold && currentKeyIndex <= max) {
; Do nothing
} else {
currentKeyIndex := 1
}
lastClickedTime := A_TickCount
return params[currentKeyIndex]
}

Autohotkey: How to program to walk in 2D game

I'm writing a macro for a 2D game. The coordinates for the character are given, in (x,y). There are obstacles in the game, so I need to program in a way that the character moves around it.
So I've written a program that reads the memory of x and y coordinates of the game. Then I wrote a function that moves the character to a desired position. The logic is: if x coordinate is less than the desired position, keep moving right, and vice versa. Then, if y coordinate is less than the desired position, keep moving up, and vice versa.
However, because of the obstacles, I have to manually give the coordinates to move to. For example, if I was at (1,1), and told the character to move to (5,5), there might be an obstacle at (3,1) and the character will get stuck. So I'll tell it to move to (1,3) first, then move to (5,3), then (5,5).
I'm just confused on how to SEQUENTIALLY tell the character to move. AFTER I move to (1,3), move to (5,3), then (5,5).
This is what I have:
f1::
WinGetTitle, ai, A
curX := ReadMemory(0x10F6A7D0, ai)
curY := ReadMemory(0x10F6A7D4, ai)
MoveTo(21,13)
if(curX == 21 && curY == 13){
MoveTo(21,28)
}
return
q::
Pause
return
ReadMemory(MADDRESS,PROGRAM)
{
winget, pid, PID, %PROGRAM%
VarSetCapacity(MVALUE,4,0)
ProcessHandle := DllCall("OpenProcess", "Int", 24, "Char", 0, "UInt", pid, "UInt")
DllCall("ReadProcessMemory","UInt",ProcessHandle,"UInt",MADDRESS,"Str",MVALUE,"UInt",4,"UInt *",0)
Loop 4
result += *(&MVALUE + A_Index-1) << 8*(A_Index-1)
return, result
}
MoveTo(targetX,targetY)
{
xadd = 0x10F6A7D0
yadd = 0x10F6A7D4
WinGetTitle, ai, A
Loop{
curX:= ReadMemory(xadd,ai)
curY:= ReadMemory(yadd,ai)
if(curX < targetX){
ControlSend,, {Right}, %ai%
}
else if(curX > targetX){
ControlSend,, {Left}, %ai%
}
else if(curY < targetY){
ControlSend,, {Down}, %ai%
}
else if(curY > targetY){
ControlSend,, {Up}, %ai%
}
Sleep, 30
Okay, so I solved my problem. I just had to make a while-loop in the MoveTo function and terminate it once I reach the position.
MoveTo(targetX,targetY)
{
xadd = 0x10F6A7D0
yadd = 0x10F6A7D4
WinGetTitle, ai, A
isThere := 1
while(isThere = 1){
curX:= ReadMemory(xadd,ai)
curY:= ReadMemory(yadd,ai)
if(curX < targetX){
ControlSend,, {Right}, %ai%
}
else if(curX > targetX){
ControlSend,, {Left}, %ai%
}
else if(curY < targetY){
ControlSend,, {Down}, %ai%
}
else if(curY > targetY){
ControlSend,, {Up}, %ai%
}
if(curX = targetX && curY = targetY){
isThere = 0
}
Sleep, 30