diff --git a/input.go b/input.go index aec12c8e6..49a6a58f1 100644 --- a/input.go +++ b/input.go @@ -234,6 +234,27 @@ func IsStandardGamepadLayoutAvailable(id GamepadID) bool { return uiDriver().Input().IsStandardGamepadLayoutAvailable(id) } +// UpdateStandardGamepadLayoutMappings updates the gamepad layout definitions using a set of definitions in SDL_GameControllerDB format. +// +// Multiple input definitions can be provided separated by newlines. +// In particular, it is valid to pass an entire gamecontrollerdb.txt file. +// +// Note though that Ebiten already includes its own copy of this file, +// so this call should only be necessary to add mappings for hardware not supported yet; +// ideally games using the StandardGamepad functions should allow the user to provide mappings and then call this function if provided. +// When using this facility to support new hardware, please also send a pull request to https://github.com/gabomdq/SDL_GameControllerDB to make your mapping available to everyone else. +// +// UpdateStandardGamepadLayoutMappings reports whether the mappings were applied, and returns an error in case any occurred while parsing the mappings. +// +// On platforms where gamepad mappings are not managed by Ebiten, this always returns false and nil. +// +// UpdateStandardGamepadLayoutMappings must be called on the main thread before ebiten.Run, and is concurrent-safe after ebiten.Run. +// +// Updated mappings take effect immediately even for already connected gamepads. +func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) { + return uiDriver().Input().UpdateStandardGamepadLayoutMappings(mappings) +} + // TouchID represents a touch's identifier. type TouchID = driver.TouchID diff --git a/internal/driver/input.go b/internal/driver/input.go index 42b1524b3..8b62240dc 100644 --- a/internal/driver/input.go +++ b/internal/driver/input.go @@ -34,6 +34,7 @@ type Input interface { IsStandardGamepadButtonPressed(id GamepadID, button StandardGamepadButton) bool IsStandardGamepadLayoutAvailable(id GamepadID) bool StandardGamepadAxisValue(id GamepadID, button StandardGamepadAxis) float64 + UpdateStandardGamepadLayoutMappings(mapping string) (bool, error) TouchPosition(id TouchID) (x, y int) Wheel() (xoff, yoff float64) } diff --git a/internal/uidriver/glfw/input.go b/internal/uidriver/glfw/input.go index 46eebaa29..ee6c37b18 100644 --- a/internal/uidriver/glfw/input.go +++ b/internal/uidriver/glfw/input.go @@ -452,3 +452,26 @@ func standardButtonToGLFWButton(button driver.StandardGamepadButton) glfw.Gamepa panic(fmt.Sprintf("glfw: invalid or inconvertible StandardGamepadButton: %d", button)) } } + +// UpdateStandardGamepadLayoutMappings can be used to provide new gamepad mappings to Ebiten. +// The string must be in the format of SDL_GameControllerDB. +func (i *Input) UpdateStandardGamepadLayoutMappings(mapping string) (bool, error) { + var err error + if i.ui.isRunning() { + err = i.ui.t.Call(func() error { + return i.updateStandardGamepadLayoutMappings(mapping) + }) + } else { + err = i.updateStandardGamepadLayoutMappings(mapping) + } + return err == nil, err +} + +func (i *Input) updateStandardGamepadLayoutMappings(mapping string) error { + if !glfw.UpdateGamepadMappings(mapping) { + // Would be nice if we could actually get the error string. + // However, go-gl currently does not provide it in any way. + return fmt.Errorf("glfw: could not parse or update gamepad mappings") + } + return nil +} diff --git a/internal/uidriver/js/input_js.go b/internal/uidriver/js/input_js.go index 5ad9a5b7f..3aa68a02a 100644 --- a/internal/uidriver/js/input_js.go +++ b/internal/uidriver/js/input_js.go @@ -496,3 +496,9 @@ func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button drive } return g.standardButtonPressed[button] } + +// UpdateStandardGamepadLayoutMappings is not supported for JS - the browser maintains the mappings. +func (i *Input) UpdateStandardGamepadLayoutMappings(mapping string) (bool, error) { + // All OK - browser owns this though. + return false, nil +} diff --git a/internal/uidriver/mobile/input.go b/internal/uidriver/mobile/input.go index 6f667e2ab..bb7e85745 100644 --- a/internal/uidriver/mobile/input.go +++ b/internal/uidriver/mobile/input.go @@ -147,6 +147,13 @@ func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.Standa return 0 } +// UpdateStandardGamepadLayoutMappings is not supported on mobile - this still needs to be implemented. +func (i *Input) UpdateStandardGamepadLayoutMappings(mapping string) (bool, error) { + // TODO: Implement this (#1557) + // Note: NOT returning an error, as mappings also do not matter right now (all functions above return nothing is pressed anyway). + return false, nil +} + func (i *Input) AppendTouchIDs(touchIDs []driver.TouchID) []driver.TouchID { i.ui.m.RLock() defer i.ui.m.RUnlock()