XInput - Xbox Controller API

Post your working scripts, libraries and tools for AHK v1.1 and older
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

XInput - Xbox Controller API

26 Mar 2017, 06:05

XInput

Originally posted on the old forum.

There is also a version for AutoHotkey v2.

XInput.ahk wraps a few key functions of XInput, a.k.a. the Microsoft Common Controller API. It provides the following capabilities not otherwise accessible to scripts:
  • The official drivers report to Windows one axis for both analog triggers, making it impossible to detect when both triggers are pulled at once. XInput allows one to retrieve the state of each analog trigger individually.
  • Set level of controller vibration - left and right motor speeds.
Other capabilities of the XInput API that this script does not currently support include:
  • Detect input on attached messenger kits (mini keyboards which can be attached to a controller).
  • Retrieve battery levels of wireless controllers.
  • Access the audio input/output of a given controller's headset.
Required: xinput1_*.dll - You probably already have it.

Usage notes are written above each function.

Code: Select all

/*  XInput by Lexikos
 *  Requires AutoHotkey 1.1+.
 */

/*
    Function: XInput_Init
    
    Initializes XInput.ahk with the given XInput DLL.
    
    Parameters:
        dll     -   The path or name of the XInput DLL to load.
*/
XInput_Init(dll:="")
{
    global
    if _XInput_hm
        return
    
    ;======== CONSTANTS DEFINED IN XINPUT.H ========
    
    ; NOTE: These are based on my outdated copy of the DirectX SDK.
    ;       Newer versions of XInput may require additional constants.
    
    ; Device types available in XINPUT_CAPABILITIES
    XINPUT_DEVTYPE_GAMEPAD          := 0x01

    ; Device subtypes available in XINPUT_CAPABILITIES
    XINPUT_DEVSUBTYPE_GAMEPAD       := 0x01

    ; Flags for XINPUT_CAPABILITIES
    XINPUT_CAPS_VOICE_SUPPORTED     := 0x0004

    ; Constants for gamepad buttons
    XINPUT_GAMEPAD_DPAD_UP          := 0x0001
    XINPUT_GAMEPAD_DPAD_DOWN        := 0x0002
    XINPUT_GAMEPAD_DPAD_LEFT        := 0x0004
    XINPUT_GAMEPAD_DPAD_RIGHT       := 0x0008
    XINPUT_GAMEPAD_START            := 0x0010
    XINPUT_GAMEPAD_BACK             := 0x0020
    XINPUT_GAMEPAD_LEFT_THUMB       := 0x0040
    XINPUT_GAMEPAD_RIGHT_THUMB      := 0x0080
    XINPUT_GAMEPAD_LEFT_SHOULDER    := 0x0100
    XINPUT_GAMEPAD_RIGHT_SHOULDER   := 0x0200
    XINPUT_GAMEPAD_GUIDE            := 0x0400
    XINPUT_GAMEPAD_A                := 0x1000
    XINPUT_GAMEPAD_B                := 0x2000
    XINPUT_GAMEPAD_X                := 0x4000
    XINPUT_GAMEPAD_Y                := 0x8000

    ; Gamepad thresholds
    XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE  := 7849
    XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE := 8689
    XINPUT_GAMEPAD_TRIGGER_THRESHOLD    := 30

    ; Flags to pass to XInputGetCapabilities
    XINPUT_FLAG_GAMEPAD             := 0x00000001
    
    ;=============== END CONSTANTS =================
    
    if (dll = "")
        Loop %A_WinDir%\System32\XInput1_*.dll
            dll := A_LoopFileName
    if (dll = "")
        dll := "XInput1_3.dll"
    
    _XInput_hm := DllCall("LoadLibrary" ,"str",dll ,"ptr")
    
    if !_XInput_hm
        throw Exception("Failed to initialize XInput: " dll " not found.")
    
   (_XInput_GetState        := DllCall("GetProcAddress" ,"ptr",_XInput_hm ,"ptr",100 ,"ptr"))
|| (_XInput_GetState        := DllCall("GetProcAddress" ,"ptr",_XInput_hm ,"astr","XInputGetState" ,"ptr"))
    _XInput_SetState        := DllCall("GetProcAddress" ,"ptr",_XInput_hm ,"astr","XInputSetState" ,"ptr")
    _XInput_GetCapabilities := DllCall("GetProcAddress" ,"ptr",_XInput_hm ,"astr","XInputGetCapabilities" ,"ptr")
    
    if !(_XInput_GetState && _XInput_SetState && _XInput_GetCapabilities)
    {
        XInput_Term()
        throw Exception("Failed to initialize XInput: function not found.")
    }
}

/*
    Function: XInput_GetState
    
    Retrieves the current state of the specified controller.

    Parameters:
        UserIndex   -   [in] Index of the user's controller. Can be a value from 0 to 3.
    
    Returns:
        The current state of the controller as an associative array.
    
    ErrorLevel:
        If the function succeeds, ErrorLevel is ERROR_SUCCESS (zero).
        If the controller is not connected, ErrorLevel is ERROR_DEVICE_NOT_CONNECTED (1167).
        If the function fails, ErrorLevel is an error code defined in Winerror.h.
            http://msdn.microsoft.com/en-us/library/ms681381.aspx

    Remarks:
        XInput.dll returns controller state as a binary structure:
            http://msdn.microsoft.com/en-us/library/microsoft.directx_sdk.reference.xinput_state
            http://msdn.microsoft.com/en-us/library/microsoft.directx_sdk.reference.xinput_gamepad
*/
XInput_GetState(UserIndex)
{
    global _XInput_GetState
    
    VarSetCapacity(xiState,16)

    if ErrorLevel := DllCall(_XInput_GetState ,"uint",UserIndex ,"uint",&xiState)
        return 0
    
    return {
    (Join,
        dwPacketNumber: NumGet(xiState,  0, "UInt")
        wButtons:       NumGet(xiState,  4, "UShort")
        bLeftTrigger:   NumGet(xiState,  6, "UChar")
        bRightTrigger:  NumGet(xiState,  7, "UChar")
        sThumbLX:       NumGet(xiState,  8, "Short")
        sThumbLY:       NumGet(xiState, 10, "Short")
        sThumbRX:       NumGet(xiState, 12, "Short")
        sThumbRY:       NumGet(xiState, 14, "Short")
    )}
}

/*
    Function: XInput_SetState
    
    Sends data to a connected controller. This function is used to activate the vibration
    function of a controller.
    
    Parameters:
        UserIndex       -   [in] Index of the user's controller. Can be a value from 0 to 3.
        LeftMotorSpeed  -   [in] Speed of the left motor, between 0 and 65535.
        RightMotorSpeed -   [in] Speed of the right motor, between 0 and 65535.
    
    Returns:
        If the function succeeds, the return value is 0 (ERROR_SUCCESS).
        If the controller is not connected, the return value is 1167 (ERROR_DEVICE_NOT_CONNECTED).
        If the function fails, the return value is an error code defined in Winerror.h.
            http://msdn.microsoft.com/en-us/library/ms681381.aspx
    
    Remarks:
        The left motor is the low-frequency rumble motor. The right motor is the
        high-frequency rumble motor. The two motors are not the same, and they create
        different vibration effects.
*/
XInput_SetState(UserIndex, LeftMotorSpeed, RightMotorSpeed)
{
    global _XInput_SetState
    return DllCall(_XInput_SetState ,"uint",UserIndex ,"uint*",LeftMotorSpeed|RightMotorSpeed<<16)
}

/*
    Function: XInput_GetCapabilities
    
    Retrieves the capabilities and features of a connected controller.
    
    Parameters:
        UserIndex   -   [in] Index of the user's controller. Can be a value in the range 0–3.
        Flags       -   [in] Input flags that identify the controller type.
                                0   - All controllers.
                                1   - XINPUT_FLAG_GAMEPAD: Xbox 360 Controllers only.
    
    Returns:
        The controller capabilities, as an associative array.
    
    ErrorLevel:
        If the function succeeds, ErrorLevel is 0 (ERROR_SUCCESS).
        If the controller is not connected, ErrorLevel is 1167 (ERROR_DEVICE_NOT_CONNECTED).
        If the function fails, ErrorLevel is an error code defined in Winerror.h.
            http://msdn.microsoft.com/en-us/library/ms681381.aspx
    
    Remarks:
        XInput.dll returns capabilities via a binary structure:
            http://msdn.microsoft.com/en-us/library/microsoft.directx_sdk.reference.xinput_capabilities
*/
XInput_GetCapabilities(UserIndex, Flags)
{
    global _XInput_GetCapabilities
    
    VarSetCapacity(xiCaps,20)
    
    if ErrorLevel := DllCall(_XInput_GetCapabilities ,"uint",UserIndex ,"uint",Flags ,"ptr",&xiCaps)
        return 0
    
    return,
    (Join
        {
            Type:                   NumGet(xiCaps,  0, "UChar"),
            SubType:                NumGet(xiCaps,  1, "UChar"),
            Flags:                  NumGet(xiCaps,  2, "UShort"),
            Gamepad:
            {
                wButtons:           NumGet(xiCaps,  4, "UShort"),
                bLeftTrigger:       NumGet(xiCaps,  6, "UChar"),
                bRightTrigger:      NumGet(xiCaps,  7, "UChar"),
                sThumbLX:           NumGet(xiCaps,  8, "Short"),
                sThumbLY:           NumGet(xiCaps, 10, "Short"),
                sThumbRX:           NumGet(xiCaps, 12, "Short"),
                sThumbRY:           NumGet(xiCaps, 14, "Short")
            },
            Vibration:
            {
                wLeftMotorSpeed:    NumGet(xiCaps, 16, "UShort"),
                wRightMotorSpeed:   NumGet(xiCaps, 18, "UShort")
            }
        }
    )
}

/*
    Function: XInput_Term
    Unloads the previously loaded XInput DLL.
*/
XInput_Term() {
    global
    if _XInput_hm
        DllCall("FreeLibrary","uint",_XInput_hm), _XInput_hm :=_XInput_GetState :=_XInput_SetState :=_XInput_GetCapabilities :=0
}

; TODO: XInputEnable, 'GetBatteryInformation and 'GetKeystroke.

Code: Select all

; Example: Control the vibration motors using the analog triggers of each controller.
XInput_Init()
Loop {
    Loop, 4 {
        if State := XInput_GetState(A_Index-1) {
            LT := State.bLeftTrigger
            RT := State.bRightTrigger
            XInput_SetState(A_Index-1, LT*257, RT*257)
        }
    }
    Sleep, 100
}
Last edited by lexikos on 01 Sep 2022, 16:55, edited 1 time in total.
Reason: add v2 link
User avatar
SnowFlake
Posts: 368
Joined: 28 Apr 2015, 05:41
Contact:

Re: XInput - Xbox Controller API

11 Apr 2017, 16:15

isn't this similar to the one evilC made?
:yawn:
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: XInput - Xbox Controller API

11 Apr 2017, 17:03

Clearly not. This is a simple wrapper around the XInput API, with virtually the same usage as the API. The original script apparently predates evilC's library by about 9 years.
alpha0
Posts: 2
Joined: 27 Apr 2017, 23:55

Re: XInput - Xbox Controller API

28 Apr 2017, 02:28

what do these scripts do and how can I use controller key presses in my script
ziggurat
Posts: 1
Joined: 11 Jan 2018, 05:10

Re: XInput - Xbox Controller API

12 Jan 2018, 03:22

Hi I am trying to use This wrapper. I downloaded https://youtu.be/SgFW8WflLng which seams to use the most reason version from the old forum on page 4.
It works great with a Xbox One controller connected via USB or Bluetooth.

The problem I have is when I use Nvidia GameStream. Which is my preferred way to play gamepad games. With Nvidia GameStream it emulates the Xbox controller too, but this wrapper does not detect the controller :( Every other game or program I tried find the Nvidia GameStream Xinput controller. One thing to note, Nvidia does not populate the gamepad list found in control panel. I wonder if this script could be updated for a more universal detection of Xinput controllers?
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: XInput - Xbox Controller API

12 Jan 2018, 05:28

The script uses the only universal method of detection of Xinput controllers - the official API.

It is likely that Nvidia GameStream does not give you a controller at all, but intercepts the Xinput APIs to pretend you have a controller. Your script may be missing some requirement for GameStream to install its hooks; e.g. the use of Direct3D or OpenGL.
fenchai
Posts: 292
Joined: 28 Mar 2016, 07:57

Re: XInput - Xbox Controller API

17 Sep 2020, 12:40

Hi, is it possible to detect the thumbstick axes?
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

23 Nov 2022, 03:35

lexikos wrote:
26 Mar 2017, 06:05
XInput

Code: Select all

; Example: Control the vibration motors using the analog triggers of each controller.
XInput_Init()
Loop {
    Loop, 4 {
        if State := XInput_GetState(A_Index-1) {
            LT := State.bLeftTrigger
            RT := State.bRightTrigger
            XInput_SetState(A_Index-1, LT*257, RT*257)
        }
    }
    Sleep, 100
}
How can I use this in a script without the Loop {} getting in the way of the rest of the script?
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: XInput - Xbox Controller API

23 Nov 2022, 05:46

@chinagreenelvis
Use a timer instead of a loop.
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

23 Nov 2022, 08:47

lexikos wrote:
23 Nov 2022, 05:46
@chinagreenelvis
Use a timer instead of a loop.
Fair enough; how would you recommend formatting it? The "Loop 4" inside the top-level loop is confusing to me since I'm not sure what that's doing exactly or how it would translate to a regular timer. And thanks for the quick response.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: XInput - Xbox Controller API

23 Nov 2022, 09:15

Something like this. The loop, 4 is so that it will apply to up to 4 connected xinput controllers.

Code: Select all

#Persistent

XInput_Init()

SetTimer, update, 100
return

update()
{
    Loop, 4 {
        if State := XInput_GetState(A_Index-1) {
            LT := State.bLeftTrigger
            RT := State.bRightTrigger
            XInput_SetState(A_Index-1, LT*257, RT*257)
        }
    }
}
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

23 Nov 2022, 09:36

geek wrote:
23 Nov 2022, 09:15
Something like this. The loop, 4 is so that it will apply to up to 4 connected xinput controllers.

Code: Select all

#Persistent

XInput_Init()

SetTimer, update, 100
return

update()
{
    Loop, 4 {
        if State := XInput_GetState(A_Index-1) {
            LT := State.bLeftTrigger
            RT := State.bRightTrigger
            XInput_SetState(A_Index-1, LT*257, RT*257)
        }
    }
}
That works great, thanks again!
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

23 Nov 2022, 09:42

Another question - with State.wButtons changing when multiple inputs are depressed, is there any way to isolate the inputs? For example, if I wanted to bind a D-Pad direction to something, I wouldn't be able to use wButtons for it if another input happens to be pressed at the same time, correct? How could I ensure that the D-Pad bind always fires even when other inputs are being used?
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

23 Nov 2022, 12:08

Not sure if it belongs in this thread, but here's a little test script I whipped up to produce results for an XBox One guide button long press and short press. Less than two seconds produces a low beep, and more than two seconds produces a high beep.

I converted the variables into arrays in order to further differentiate between controllers if necessary. If you only wanted the results from the first controller, you could change [CNum] to [0], or [1] for the second, and so on.

Code: Select all

#NoEnv
#SingleInstance force
#Persistent
SetWorkingDir %A_ScriptDir%

#Include xinput.ahk
XInput_Init()

Global ButtonState := {}
Global WasButtonState := {}
Global TimerStarted := {}
Global TimePressed := {}
Global TimerTime := {}

Global BUTTON_HOLD_TIME := 2000

Global XINPUT_GUIDEBUTTON := 1024

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; START

SetTimer, UpdateXInput, 30
Return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; FUNCTIONS

UpdateXInput()
{
	Loop, 4
	{
		State := {}
		CNum := A_Index - 1
		If State[CNum] := XInput_GetState(CNum)
		{
			WasButtonState[CNum] := ButtonState[CNum]
			ButtonState[CNum] := State[CNum].wButtons
			If (!ButtonState[CNum])
			{
				;; Guide Button Short Press
				If (TimerStarted[CNum] && WasButtonState[CNum] = XINPUT_GUIDEBUTTON && TimerTime[CNum] < BUTTON_HOLD_TIME)
				{
					SoundBeep, 500, 100
				}
			}
			Else
			{
				;; Guide Button Pressed
				If (WasButtonState[CNum] != XINPUT_GUIDEBUTTON && ButtonState[CNum] = XINPUT_GUIDEBUTTON)
				{
					TimerStarted[CNum] := 1
					TimePressed[CNum] := A_TickCount
				}
				;; Guide Button Long Press
				If (WasButtonState[CNum] = XINPUT_GUIDEBUTTON && ButtonState[CNum] = XINPUT_GUIDEBUTTON)
				{
					If (TimerStarted[CNum])
					{
						TimerTime[CNum] := (A_TickCount - TimePressed[CNum])
						If (TimerTime[CNum] >= BUTTON_HOLD_TIME)
						{
							TimerStarted[CNum] := 0
							SoundBeep, 1000, 100
						}
					}
				}
				Tooltip, % TimerTime[CNum]
			}
		}
	}
}
Overall, this is starting to feel more elegant than working with the original AHK joycode, but if anyone has any solutions on how to further simplify what I've done, I'm all ears.
User avatar
Scr1pter
Posts: 1271
Joined: 06 Aug 2017, 08:21
Location: Germany

Re: XInput - Xbox Controller API

03 Dec 2022, 11:29

Hi I must ask two questions:

1.
Is this compatible to ANY USB game controllers?
Or must or specifically be an xinput controller?

2.
Does this script support "button up"?
In AHK you can easily set functions to key up::
For Joy buttons this does not work, you have to add getkeystate.

Cheers
Please use [code][/code] when posting code!
Keyboard: Logitech G PRO - Mouse: Logitech G502 LS - OS: Windows 10 Pro 64 Bit - AHK version: 1.1.33.09
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: XInput - Xbox Controller API

04 Dec 2022, 03:15

What is "an xinput controller", if not a controller supported by the XInput API? Of course this only works with XInput controllers.

The script supports both "button down" and "button up" in the same way that GetKeyState supports it - by telling you which state the button is in when you ask. To detect when a button is released or pressed, you must poll the state and detect changes yourself. That is what AutoHotkey does under the hood to implement its joystick button hotkeys. The script is just a basic wrapper around the XInput API, and as such does not even provide separate properties for the buttons (bit math is required to extract the state of each button).
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

06 Jan 2023, 18:30

Scr1pter wrote:
03 Dec 2022, 11:29
Does this script support "button up"?
If you see the example I posted above, I created a WasButtonState variable that sets itself to the previous ButtonState variable on each loop.

You can use something like that to detect the current ButtonState being zero and WasButtonState to the value of whichever button you need to reference.
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

06 Jan 2023, 18:35

lexikos wrote:
26 Mar 2017, 06:05
XInput
Is there a native way to detect if the ButtonState contains a particular value in it? Like how you can use AHK to tell if Window has a particular style with WinGet, even though the returned value is the combination of all the styles a window has? I.E. I'd like to detect whether a particular button is being pressed regardless of any other buttons that might be also pressed.

Mathematically it should be possible, obviously, since each button and button combination produces a unique total number as they all have x2 values (1, 2, 4, 8, 16, 32, and so on), but I figured I'd ask before trying to tackle that kind of calculation.
chinagreenelvis
Posts: 133
Joined: 19 Oct 2015, 16:21

Re: XInput - Xbox Controller API

06 Jan 2023, 18:45

I also notice that there's no value of 2048 (0x0800) in the gamepad button constants. Any idea why?
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: XInput - Xbox Controller API

06 Jan 2023, 18:57

Code: Select all

if (buttonFlags & XINPUT_GAMEPAD_A) ; A was pressed
{
	; do somethig
}
chinagreenelvis wrote:
06 Jan 2023, 18:45
I also notice that there's no value of 2048 (0x0800) in the gamepad button constants. Any idea why?
because its not documented? what button is 0x0800 supposed to map to and how do did u figure that out?

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 105 guests