mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-27 03:02:49 +01:00
internal/gamepad: native standard layout for Linux (#2587)
Implements native standard layout for Linux gamepads by using the kernel-provided button IDs, thereby expanding support to gamepads not listed in gamecontrollerdb.txt. Linux's docs: https://www.kernel.org/doc/Documentation/input/gamepad.txt SDL2's source: https://fossies.org/linux/SDL2/src/joystick/linux/SDL_sysjoystick.c#l_1740 Note that I am NOT 100% convinced about the X/Y swap between Xbox and PlayStation controllers - the Xbox compatible pad I have however does have BTN_NORTH and BTN_WEST swapped (and thus BTN_X and BTN_Y assigned right), which confirms SDL's logic and opposes the kernel docs. Tested with this gamepad: "20d6:2802 BDA Xbox ONE Core controller", label says "PowerA Model 1508491-02" - even after clearing out gamecontrollerdb.txt, examples/gamepad shows a 100% correct mapping. Closes #2052
This commit is contained in:
parent
6ccdc6382c
commit
06c141475c
@ -23,12 +23,43 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
_ABS_X = 0x00
|
||||||
|
_ABS_Y = 0x01
|
||||||
|
_ABS_Z = 0x02
|
||||||
|
_ABS_RX = 0x03
|
||||||
|
_ABS_RY = 0x04
|
||||||
|
_ABS_RZ = 0x05
|
||||||
_ABS_HAT0X = 0x10
|
_ABS_HAT0X = 0x10
|
||||||
|
_ABS_HAT0Y = 0x11
|
||||||
|
_ABS_HAT1X = 0x12
|
||||||
|
_ABS_HAT1Y = 0x13
|
||||||
|
_ABS_HAT2X = 0x14
|
||||||
|
_ABS_HAT2Y = 0x15
|
||||||
_ABS_HAT3Y = 0x17
|
_ABS_HAT3Y = 0x17
|
||||||
_ABS_MAX = 0x3f
|
_ABS_MAX = 0x3f
|
||||||
_ABS_CNT = _ABS_MAX + 1
|
_ABS_CNT = _ABS_MAX + 1
|
||||||
|
|
||||||
_BTN_MISC = 0x100
|
_BTN_MISC = 0x100
|
||||||
|
_BTN_GAMEPAD = 0x130
|
||||||
|
_BTN_A = 0x130
|
||||||
|
_BTN_B = 0x131
|
||||||
|
_BTN_NORTH = 0x133
|
||||||
|
_BTN_X = 0x133
|
||||||
|
_BTN_WEST = 0x134
|
||||||
|
_BTN_Y = 0x134
|
||||||
|
_BTN_TL = 0x136
|
||||||
|
_BTN_TR = 0x137
|
||||||
|
_BTN_TL2 = 0x138
|
||||||
|
_BTN_TR2 = 0x139
|
||||||
|
_BTN_SELECT = 0x13a
|
||||||
|
_BTN_START = 0x13b
|
||||||
|
_BTN_MODE = 0x13c
|
||||||
|
_BTN_THUMBL = 0x13d
|
||||||
|
_BTN_THUMBR = 0x13e
|
||||||
|
_BTN_DPAD_UP = 0x220
|
||||||
|
_BTN_DPAD_DOWN = 0x221
|
||||||
|
_BTN_DPAD_LEFT = 0x222
|
||||||
|
_BTN_DPAD_RIGHT = 0x223
|
||||||
|
|
||||||
_IOC_NONE = 0
|
_IOC_NONE = 0
|
||||||
_IOC_WRITE = 1
|
_IOC_WRITE = 1
|
||||||
|
@ -192,6 +192,12 @@ func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err err
|
|||||||
var axisCount int
|
var axisCount int
|
||||||
var buttonCount int
|
var buttonCount int
|
||||||
var hatCount int
|
var hatCount int
|
||||||
|
for i := range n.keyMap {
|
||||||
|
n.keyMap[i] = -1
|
||||||
|
}
|
||||||
|
for i := range n.absMap {
|
||||||
|
n.absMap[i] = -1
|
||||||
|
}
|
||||||
for code := _BTN_MISC; code < _KEY_CNT; code++ {
|
for code := _BTN_MISC; code < _KEY_CNT; code++ {
|
||||||
if !isBitSet(keyBits, code) {
|
if !isBitSet(keyBits, code) {
|
||||||
continue
|
continue
|
||||||
@ -200,15 +206,16 @@ func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err err
|
|||||||
buttonCount++
|
buttonCount++
|
||||||
}
|
}
|
||||||
for code := 0; code < _ABS_CNT; code++ {
|
for code := 0; code < _ABS_CNT; code++ {
|
||||||
n.absMap[code] = -1
|
|
||||||
if !isBitSet(absBits, code) {
|
if !isBitSet(absBits, code) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
||||||
|
// Write the hat index both for the X and the Y hat axis.
|
||||||
|
// That way, the hat can be referenced using either axis, which is used by the code building hatMappingInput.
|
||||||
|
n.absMap[code] = hatCount
|
||||||
|
code++
|
||||||
n.absMap[code] = hatCount
|
n.absMap[code] = hatCount
|
||||||
hatCount++
|
hatCount++
|
||||||
// Skip Y.
|
|
||||||
code++
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := ioctl(n.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&n.absInfo[code])); err != nil {
|
if err := ioctl(n.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&n.absInfo[code])); err != nil {
|
||||||
@ -222,6 +229,8 @@ func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err err
|
|||||||
n.buttonCount_ = buttonCount
|
n.buttonCount_ = buttonCount
|
||||||
n.hatCount_ = hatCount
|
n.hatCount_ = hatCount
|
||||||
|
|
||||||
|
n.computeStandardLayout(id.vendor)
|
||||||
|
|
||||||
if err := n.pollAbsState(); err != nil {
|
if err := n.pollAbsState(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -295,6 +304,9 @@ type nativeGamepadImpl struct {
|
|||||||
axisCount_ int
|
axisCount_ int
|
||||||
buttonCount_ int
|
buttonCount_ int
|
||||||
hatCount_ int
|
hatCount_ int
|
||||||
|
|
||||||
|
stdAxisMap map[gamepaddb.StandardAxis]mappingInput
|
||||||
|
stdButtonMap map[gamepaddb.StandardButton]mappingInput
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) close() {
|
func (g *nativeGamepadImpl) close() {
|
||||||
@ -355,6 +367,9 @@ func (g *nativeGamepadImpl) update(gamepad *gamepads) error {
|
|||||||
case unix.EV_KEY:
|
case unix.EV_KEY:
|
||||||
if int(e.code-_BTN_MISC) < len(g.keyMap) {
|
if int(e.code-_BTN_MISC) < len(g.keyMap) {
|
||||||
idx := g.keyMap[e.code-_BTN_MISC]
|
idx := g.keyMap[e.code-_BTN_MISC]
|
||||||
|
if idx < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
g.buttons[idx] = e.value != 0
|
g.buttons[idx] = e.value != 0
|
||||||
}
|
}
|
||||||
case unix.EV_ABS:
|
case unix.EV_ABS:
|
||||||
@ -379,6 +394,9 @@ func (g *nativeGamepadImpl) pollAbsState() error {
|
|||||||
|
|
||||||
func (g *nativeGamepadImpl) handleAbsEvent(code int, value int32) {
|
func (g *nativeGamepadImpl) handleAbsEvent(code int, value int32) {
|
||||||
index := g.absMap[code]
|
index := g.absMap[code]
|
||||||
|
if index < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
||||||
axis := (code - _ABS_HAT0X) % 2
|
axis := (code - _ABS_HAT0X) % 2
|
||||||
@ -419,16 +437,147 @@ func (g *nativeGamepadImpl) handleAbsEvent(code int, value int32) {
|
|||||||
g.axes[index] = v
|
g.axes[index] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
func (g *nativeGamepadImpl) computeStandardLayout(vendor uint16) {
|
||||||
return false
|
g.stdAxisMap = map[gamepaddb.StandardAxis]mappingInput{}
|
||||||
|
g.stdButtonMap = map[gamepaddb.StandardButton]mappingInput{}
|
||||||
|
|
||||||
|
// NOTE: assignments to the same value are in exact reverse order as SDL2,
|
||||||
|
// so we can just overwrite rather than checking.
|
||||||
|
|
||||||
|
// BTN_GAMEPAD implies that the kernel module implements standard mapping.
|
||||||
|
if b := g.keyMap[_BTN_GAMEPAD-_BTN_MISC]; b < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A and B buttons go by name.
|
||||||
|
if b := g.keyMap[_BTN_A-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonRightBottom] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_B-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonRightRight] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if vendor == 0x054c /* USB_VENDOR_SONY */ {
|
||||||
|
// Sony uses WEST/NORTH buttons.
|
||||||
|
if b := g.keyMap[_BTN_WEST-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonRightLeft] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_NORTH-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonRightTop] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Xbox uses X/Y buttons.
|
||||||
|
// Note that this is the opposite assignment following the WEST/NORTH mappings,
|
||||||
|
// and contradicts Linux kernel documentation which states
|
||||||
|
// that buttons are always assigned by physical location.
|
||||||
|
// However, it matches actual Xbox gamepads, and SDL2 has the same logic.
|
||||||
|
if b := g.keyMap[_BTN_X-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonRightLeft] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_Y-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonRightTop] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center and thumb buttons.
|
||||||
|
if b := g.keyMap[_BTN_SELECT-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonCenterLeft] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_START-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonCenterRight] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_THUMBL-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftStick] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_THUMBR-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonRightStick] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_MODE-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonCenterCenter] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shoulder buttons can be analog or digital. Prefer digital ones.
|
||||||
|
if h := g.absMap[_ABS_HAT1Y]; h >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontTopLeft] = hatMappingInput{g: g, hat: h, direction: hatDown}
|
||||||
|
}
|
||||||
|
if h := g.absMap[_ABS_HAT1X]; h >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontTopRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_TL-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontTopLeft] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_TR-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontTopRight] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triggers can be analog or digital. Prefer analog ones.
|
||||||
|
if b := g.keyMap[_BTN_TL2-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_TR2-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if a := g.absMap[_ABS_Z]; a >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = axisMappingInput{g: g, axis: a}
|
||||||
|
}
|
||||||
|
if a := g.absMap[_ABS_RZ]; a >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = axisMappingInput{g: g, axis: a}
|
||||||
|
}
|
||||||
|
if h := g.absMap[_ABS_HAT2Y]; h >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = hatMappingInput{g: g, hat: h, direction: hatDown}
|
||||||
|
}
|
||||||
|
if h := g.absMap[_ABS_HAT2X]; h >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
|
||||||
|
}
|
||||||
|
|
||||||
|
// D-pad can be analog or digital. Prefer digital one.
|
||||||
|
if h := g.absMap[_ABS_HAT0X]; h >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftLeft] = hatMappingInput{g: g, hat: h, direction: hatLeft}
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
|
||||||
|
}
|
||||||
|
if h := g.absMap[_ABS_HAT0Y]; h >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftTop] = hatMappingInput{g: g, hat: h, direction: hatUp}
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftBottom] = hatMappingInput{g: g, hat: h, direction: hatDown}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_DPAD_UP-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftTop] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_DPAD_DOWN-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftBottom] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_DPAD_LEFT-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftLeft] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
if b := g.keyMap[_BTN_DPAD_RIGHT-_BTN_MISC]; b >= 0 {
|
||||||
|
g.stdButtonMap[gamepaddb.StandardButtonLeftRight] = buttonMappingInput{g: g, button: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left stick.
|
||||||
|
if a := g.absMap[_ABS_X]; a >= 0 {
|
||||||
|
g.stdAxisMap[gamepaddb.StandardAxisLeftStickHorizontal] = axisMappingInput{g: g, axis: a}
|
||||||
|
}
|
||||||
|
if a := g.absMap[_ABS_Y]; a >= 0 {
|
||||||
|
g.stdAxisMap[gamepaddb.StandardAxisLeftStickVertical] = axisMappingInput{g: g, axis: a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right stick.
|
||||||
|
if a := g.absMap[_ABS_RX]; a >= 0 {
|
||||||
|
g.stdAxisMap[gamepaddb.StandardAxisRightStickHorizontal] = axisMappingInput{g: g, axis: a}
|
||||||
|
}
|
||||||
|
if a := g.absMap[_ABS_RY]; a >= 0 {
|
||||||
|
g.stdAxisMap[gamepaddb.StandardAxisRightStickVertical] = axisMappingInput{g: g, axis: a}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||||
|
return len(g.stdAxisMap) != 0 || len(g.stdButtonMap) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||||
return nil
|
return g.stdAxisMap[axis]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||||
return nil
|
return g.stdButtonMap[button]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) axisCount() int {
|
func (g *nativeGamepadImpl) axisCount() int {
|
||||||
|
Loading…
Reference in New Issue
Block a user