diff --git a/internal/gamepad/api_linux.go b/internal/gamepad/api_linux.go index df6846f06..9ac2f9263 100644 --- a/internal/gamepad/api_linux.go +++ b/internal/gamepad/api_linux.go @@ -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_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 diff --git a/internal/gamepad/gamepad_linux.go b/internal/gamepad/gamepad_linux.go index 25c89167d..50932f7c5 100644 --- a/internal/gamepad/gamepad_linux.go +++ b/internal/gamepad/gamepad_linux.go @@ -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 {