mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +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 (
|
||||
_ABS_X = 0x00
|
||||
_ABS_Y = 0x01
|
||||
_ABS_Z = 0x02
|
||||
_ABS_RX = 0x03
|
||||
_ABS_RY = 0x04
|
||||
_ABS_RZ = 0x05
|
||||
_ABS_HAT0X = 0x10
|
||||
_ABS_HAT0Y = 0x11
|
||||
_ABS_HAT1X = 0x12
|
||||
_ABS_HAT1Y = 0x13
|
||||
_ABS_HAT2X = 0x14
|
||||
_ABS_HAT2Y = 0x15
|
||||
_ABS_HAT3Y = 0x17
|
||||
_ABS_MAX = 0x3f
|
||||
_ABS_CNT = _ABS_MAX + 1
|
||||
|
||||
_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_WRITE = 1
|
||||
|
@ -192,6 +192,12 @@ func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err err
|
||||
var axisCount int
|
||||
var buttonCount 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++ {
|
||||
if !isBitSet(keyBits, code) {
|
||||
continue
|
||||
@ -200,15 +206,16 @@ func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err err
|
||||
buttonCount++
|
||||
}
|
||||
for code := 0; code < _ABS_CNT; code++ {
|
||||
n.absMap[code] = -1
|
||||
if !isBitSet(absBits, code) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
hatCount++
|
||||
// Skip Y.
|
||||
code++
|
||||
continue
|
||||
}
|
||||
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.hatCount_ = hatCount
|
||||
|
||||
n.computeStandardLayout(id.vendor)
|
||||
|
||||
if err := n.pollAbsState(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -295,6 +304,9 @@ type nativeGamepadImpl struct {
|
||||
axisCount_ int
|
||||
buttonCount_ int
|
||||
hatCount_ int
|
||||
|
||||
stdAxisMap map[gamepaddb.StandardAxis]mappingInput
|
||||
stdButtonMap map[gamepaddb.StandardButton]mappingInput
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) close() {
|
||||
@ -355,6 +367,9 @@ func (g *nativeGamepadImpl) update(gamepad *gamepads) error {
|
||||
case unix.EV_KEY:
|
||||
if int(e.code-_BTN_MISC) < len(g.keyMap) {
|
||||
idx := g.keyMap[e.code-_BTN_MISC]
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
g.buttons[idx] = e.value != 0
|
||||
}
|
||||
case unix.EV_ABS:
|
||||
@ -379,6 +394,9 @@ func (g *nativeGamepadImpl) pollAbsState() error {
|
||||
|
||||
func (g *nativeGamepadImpl) handleAbsEvent(code int, value int32) {
|
||||
index := g.absMap[code]
|
||||
if index < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
||||
axis := (code - _ABS_HAT0X) % 2
|
||||
@ -419,16 +437,147 @@ func (g *nativeGamepadImpl) handleAbsEvent(code int, value int32) {
|
||||
g.axes[index] = v
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
func (g *nativeGamepadImpl) computeStandardLayout(vendor uint16) {
|
||||
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 {
|
||||
return nil
|
||||
return g.stdAxisMap[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
return nil
|
||||
return g.stdButtonMap[button]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisCount() int {
|
||||
|
Loading…
Reference in New Issue
Block a user