Hi. I'm trying to figure out if there's a way to have a script that only triggers off gamepad input from a specific device.
Specifically, this is for a Lenovo Legion Go, connected to an 8bitdo arcade stick, which can toggle between presenting itself to the system as an XBox 360 controller or a Switch Pro Controller.
I'd like to leave the Xbox mode alone to use in games, but map the buttons in Switch mode to keyboard commands for opening specific programs, volume control, opening the settings menu, etc.
I'd like to have it trigger off some consistent aspect of the Switch Pro Controller mode, so that it doesn't matter how many other game controllers are connected, or what order they were connected in -- Switch input gets translated to keyboard commands and other input passes through without being altered.
Is this possible?
Thanks
Per-device joystick mapping
- Noitalommi_2
- Posts: 286
- Joined: 16 Aug 2023, 10:58
Re: Per-device joystick mapping
Hi @Thraeg
The problem will probably be that Autohotkey or Windows does not natively recognize input from the 8bitdo Arcade Stick in Switch mode
Because otherwise you wouldn't need a selector to switch between PC (Windows) and Switch (Console).
But alternatively you could do it so that you use the second selector (LS,DP,RS) to change the button layout because LS and D-pad are used in most games for directional input and RS is only used for the camera.
In short:
Selector is set to RS
move the Stick -> the Script detects input from RS -> activates the script
Selector is set to LS
move the Stick -> the Script detects input from LS -> deactivates the script
Here is an example which uses the XInput function to map a sound beep to the a-Button if RS is selected:
or you could use a button combination like holding start & select for 2s to start / stop the script.
The problem will probably be that Autohotkey or Windows does not natively recognize input from the 8bitdo Arcade Stick in Switch mode
Because otherwise you wouldn't need a selector to switch between PC (Windows) and Switch (Console).
But alternatively you could do it so that you use the second selector (LS,DP,RS) to change the button layout because LS and D-pad are used in most games for directional input and RS is only used for the camera.
In short:
Selector is set to RS
move the Stick -> the Script detects input from RS -> activates the script
Selector is set to LS
move the Stick -> the Script detects input from LS -> deactivates the script
Here is an example which uses the XInput function to map a sound beep to the a-Button if RS is selected:
Code: Select all
#Requires AutoHotkey >=2.0
#SingleInstance
XInput_Init()
SetTimer Button, 100
Button() {
Loop 4 {
if State := XInput_GetState(A_Index-1)
if State.wButtons & 0x1000 && on = true ; A-Button
SoundBeep
}
}
ToolTip "move the stick"
Loop {
Loop 4 {
if State := XInput_GetState(A_Index-1) {
if State.sThumbLX || State.sThumbLY
ToolTip("LS, A-Button Soundbeep disabled"), on := false
else if State.sThumbRX || State.sThumbRY
ToolTip("RS, A-Button Soundbeep enabled"), on := true
}
}
Sleep 100
}
/* XInput by Lexikos
*/
/*
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 ?? 0
return
;======== CONSTANTS DEFINED IN XINPUT.H ========
; 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 Files 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 Error("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 Error("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:
An object with with properties equivalent to merging XINPUT_STATE and XINPUT_GAMEPAD.
https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_state
Error handling:
Returns false if the controller is disconnected.
Throws an OSError for other errors.
*/
XInput_GetState(UserIndex)
{
global _XInput_GetState
xiState := Buffer(16)
if err := DllCall(_XInput_GetState ,"uint",UserIndex ,"ptr",xiState) {
if err = 1167 ; ERROR_DEVICE_NOT_CONNECTED
return 0
throw OSError(err, -1)
}
return {
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.
Error handling:
Throws an OSError on failure, such as if the controller is not connected.
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
if err := DllCall(_XInput_SetState ,"uint",UserIndex ,"uint*",LeftMotorSpeed|RightMotorSpeed<<16)
throw OSError(err, -1)
}
/*
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 03.
Flags - [in] Input flags that identify the controller type.
0 - All controllers.
1 - XINPUT_FLAG_GAMEPAD: Xbox 360 Controllers only.
Returns:
An object with properties equivalent to XINPUT_CAPABILITIES.
https://docs.microsoft.com/en-au/windows/win32/api/xinput/ns-xinput-xinput_capabilities
Error handling:
Returns false if the controller is disconnected.
Throws an OSError for other errors.
*/
XInput_GetCapabilities(UserIndex, Flags)
{
global _XInput_GetCapabilities
xiCaps := Buffer(20)
if err := DllCall(_XInput_GetCapabilities ,"uint",UserIndex ,"uint",Flags ,"ptr",xiCaps) {
if err = 1167 ; ERROR_DEVICE_NOT_CONNECTED
return 0
throw OSError(err, -1)
}
return {
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
#Requires AutoHotkey >=2.0
#SingleInstance
XInput_Init()
SetTimer Button, 100
Button() {
Loop 4 {
if State := XInput_GetState(A_Index-1)
if State.wButtons & 0x1000 && on = true ; A-Button
SoundBeep
}
}
ToolTip "press and hold Start & Select for 2s"
on := false
Loop {
Loop 4 {
if State := XInput_GetState(Index := A_Index-1) {
if State.wButtons = 0x0030 { ; Start & Select
TickCount := A_TickCount
while State.wButtons = 0x0030 {
State := XInput_GetState(Index)
Sleep 100
if A_TickCount-TickCount>=2000 {
on := !on
SoundBeep 700
ToolTip "A-Button sound beep " (on ? "on" : "off")
break
}
}
}
}
}
Sleep 100
}
/* XInput by Lexikos
*/
/*
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 ?? 0
return
;======== CONSTANTS DEFINED IN XINPUT.H ========
; 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 Files 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 Error("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 Error("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:
An object with with properties equivalent to merging XINPUT_STATE and XINPUT_GAMEPAD.
https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_state
Error handling:
Returns false if the controller is disconnected.
Throws an OSError for other errors.
*/
XInput_GetState(UserIndex)
{
global _XInput_GetState
xiState := Buffer(16)
if err := DllCall(_XInput_GetState ,"uint",UserIndex ,"ptr",xiState) {
if err = 1167 ; ERROR_DEVICE_NOT_CONNECTED
return 0
throw OSError(err, -1)
}
return {
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.
Error handling:
Throws an OSError on failure, such as if the controller is not connected.
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
if err := DllCall(_XInput_SetState ,"uint",UserIndex ,"uint*",LeftMotorSpeed|RightMotorSpeed<<16)
throw OSError(err, -1)
}
/*
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 03.
Flags - [in] Input flags that identify the controller type.
0 - All controllers.
1 - XINPUT_FLAG_GAMEPAD: Xbox 360 Controllers only.
Returns:
An object with properties equivalent to XINPUT_CAPABILITIES.
https://docs.microsoft.com/en-au/windows/win32/api/xinput/ns-xinput-xinput_capabilities
Error handling:
Returns false if the controller is disconnected.
Throws an OSError for other errors.
*/
XInput_GetCapabilities(UserIndex, Flags)
{
global _XInput_GetCapabilities
xiCaps := Buffer(20)
if err := DllCall(_XInput_GetCapabilities ,"uint",UserIndex ,"uint",Flags ,"ptr",xiCaps) {
if err = 1167 ; ERROR_DEVICE_NOT_CONNECTED
return 0
throw OSError(err, -1)
}
return {
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.
Re: Per-device joystick mapping
Thanks! I'll try that out.
Who is online
Users browsing this forum: No registered users and 10 guests