diff options
author | Garrett Brown <themagnificentmrb@gmail.com> | 2024-06-26 16:18:31 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-26 16:18:31 -0700 |
commit | 85dcca1fd4932c104ebba40f91756ab66895ef55 (patch) | |
tree | 96f318d93a3a9264b24655ce8901cf6f75c37a1f | |
parent | 39369a497c72721b6f31c1ba49af2aa9aaadd0c0 (diff) | |
parent | d23e6184029698e234e464c1553ce1ee214256f6 (diff) |
Merge pull request #25176 from garbear/android-default-map
Android: Fix unmapped joysticks (default joystick buttonmaps)
20 files changed, 603 insertions, 85 deletions
diff --git a/xbmc/games/controllers/CMakeLists.txt b/xbmc/games/controllers/CMakeLists.txt index b00e1caa4a..b54f5c0604 100644 --- a/xbmc/games/controllers/CMakeLists.txt +++ b/xbmc/games/controllers/CMakeLists.txt @@ -1,7 +1,9 @@ set(SOURCES Controller.cpp ControllerLayout.cpp ControllerManager.cpp - ControllerTranslator.cpp) + ControllerTranslator.cpp + DefaultController.cpp +) set(HEADERS Controller.h ControllerDefinitions.h @@ -9,6 +11,8 @@ set(HEADERS Controller.h ControllerLayout.h ControllerManager.h ControllerTranslator.h - ControllerTypes.h) + ControllerTypes.h + DefaultController.h +) core_add_library(games_controller) diff --git a/xbmc/games/controllers/ControllerIDs.h b/xbmc/games/controllers/ControllerIDs.h index a9254e015d..68d100d156 100644 --- a/xbmc/games/controllers/ControllerIDs.h +++ b/xbmc/games/controllers/ControllerIDs.h @@ -13,3 +13,14 @@ #define DEFAULT_KEYBOARD_ID "game.controller.keyboard" #define DEFAULT_MOUSE_ID "game.controller.mouse" #define DEFAULT_REMOTE_ID "game.controller.remote" + +namespace KODI +{ +namespace GAME +{ + +// Used to set the appearance of PlayStation controllers +constexpr const char* CONTROLLER_ID_PLAYSTATION = "game.controller.ps.dualanalog"; + +} // namespace GAME +} // namespace KODI diff --git a/xbmc/games/controllers/DefaultController.cpp b/xbmc/games/controllers/DefaultController.cpp new file mode 100644 index 0000000000..58bb464e3d --- /dev/null +++ b/xbmc/games/controllers/DefaultController.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DefaultController.h" + +using namespace KODI; +using namespace GAME; + +const char* CDefaultController::FEATURE_A = "a"; +const char* CDefaultController::FEATURE_B = "b"; +const char* CDefaultController::FEATURE_X = "x"; +const char* CDefaultController::FEATURE_Y = "y"; +const char* CDefaultController::FEATURE_START = "start"; +const char* CDefaultController::FEATURE_BACK = "back"; +const char* CDefaultController::FEATURE_GUIDE = "guide"; +const char* CDefaultController::FEATURE_UP = "up"; +const char* CDefaultController::FEATURE_RIGHT = "right"; +const char* CDefaultController::FEATURE_DOWN = "down"; +const char* CDefaultController::FEATURE_LEFT = "left"; +const char* CDefaultController::FEATURE_LEFT_THUMB = "leftthumb"; +const char* CDefaultController::FEATURE_RIGHT_THUMB = "rightthumb"; +const char* CDefaultController::FEATURE_LEFT_BUMPER = "leftbumper"; +const char* CDefaultController::FEATURE_RIGHT_BUMPER = "rightbumper"; +const char* CDefaultController::FEATURE_LEFT_TRIGGER = "lefttrigger"; +const char* CDefaultController::FEATURE_RIGHT_TRIGGER = "righttrigger"; +const char* CDefaultController::FEATURE_LEFT_STICK = "leftstick"; +const char* CDefaultController::FEATURE_RIGHT_STICK = "rightstick"; +const char* CDefaultController::FEATURE_LEFT_MOTOR = "leftmotor"; +const char* CDefaultController::FEATURE_RIGHT_MOTOR = "rightmotor"; diff --git a/xbmc/games/controllers/DefaultController.h b/xbmc/games/controllers/DefaultController.h new file mode 100644 index 0000000000..f82ce49748 --- /dev/null +++ b/xbmc/games/controllers/DefaultController.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +namespace KODI +{ +namespace GAME +{ +class CDefaultController +{ +public: + // Face buttons + static const char* FEATURE_A; + static const char* FEATURE_B; + static const char* FEATURE_X; + static const char* FEATURE_Y; + static const char* FEATURE_START; + static const char* FEATURE_BACK; + static const char* FEATURE_GUIDE; + static const char* FEATURE_UP; + static const char* FEATURE_RIGHT; + static const char* FEATURE_DOWN; + static const char* FEATURE_LEFT; + static const char* FEATURE_LEFT_THUMB; + static const char* FEATURE_RIGHT_THUMB; + + // Shoulder buttons + static const char* FEATURE_LEFT_BUMPER; + static const char* FEATURE_RIGHT_BUMPER; + + // Triggers + static const char* FEATURE_LEFT_TRIGGER; + static const char* FEATURE_RIGHT_TRIGGER; + + // Analog sticks + static const char* FEATURE_LEFT_STICK; + static const char* FEATURE_RIGHT_STICK; + + // Haptics + static const char* FEATURE_LEFT_MOTOR; + static const char* FEATURE_RIGHT_MOTOR; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/input/joysticks/JoystickEasterEgg.cpp b/xbmc/input/joysticks/JoystickEasterEgg.cpp index ab02ba0bde..a25c4baf78 100644 --- a/xbmc/input/joysticks/JoystickEasterEgg.cpp +++ b/xbmc/input/joysticks/JoystickEasterEgg.cpp @@ -12,6 +12,7 @@ #include "games/GameServices.h" #include "games/GameSettings.h" #include "games/controllers/ControllerIDs.h" +#include "games/controllers/DefaultController.h" #include "guilib/GUIAudioManager.h" #include "guilib/WindowIDs.h" @@ -22,16 +23,16 @@ const std::map<std::string, std::vector<FeatureName>> CJoystickEasterEgg::m_sequ { DEFAULT_CONTROLLER_ID, { - "up", - "up", - "down", - "down", - "left", - "right", - "left", - "right", - "b", - "a", + GAME::CDefaultController::FEATURE_UP, + GAME::CDefaultController::FEATURE_UP, + GAME::CDefaultController::FEATURE_DOWN, + GAME::CDefaultController::FEATURE_DOWN, + GAME::CDefaultController::FEATURE_LEFT, + GAME::CDefaultController::FEATURE_RIGHT, + GAME::CDefaultController::FEATURE_LEFT, + GAME::CDefaultController::FEATURE_RIGHT, + GAME::CDefaultController::FEATURE_B, + GAME::CDefaultController::FEATURE_A, }, }, { diff --git a/xbmc/peripherals/Peripherals.cpp b/xbmc/peripherals/Peripherals.cpp index e282fbf700..1276cf52d6 100644 --- a/xbmc/peripherals/Peripherals.cpp +++ b/xbmc/peripherals/Peripherals.cpp @@ -917,7 +917,7 @@ void CPeripherals::ResetButtonMaps(const std::string& controllerId) PeripheralAddonPtr addon; if (addonBus->GetAddonWithButtonMap(peripheral.get(), addon)) { - CAddonButtonMap buttonMap(peripheral.get(), addon, controllerId); + CAddonButtonMap buttonMap(peripheral.get(), addon, controllerId, *this); buttonMap.Reset(); } } diff --git a/xbmc/peripherals/addons/AddonButtonMap.cpp b/xbmc/peripherals/addons/AddonButtonMap.cpp index e9764ba81c..0471d293eb 100644 --- a/xbmc/peripherals/addons/AddonButtonMap.cpp +++ b/xbmc/peripherals/addons/AddonButtonMap.cpp @@ -10,6 +10,7 @@ #include "PeripheralAddonTranslator.h" #include "input/joysticks/JoystickUtils.h" +#include "peripherals/Peripherals.h" #include "peripherals/devices/Peripheral.h" #include "utils/log.h" @@ -24,8 +25,9 @@ using namespace PERIPHERALS; CAddonButtonMap::CAddonButtonMap(CPeripheral* device, const std::weak_ptr<CPeripheralAddon>& addon, - const std::string& strControllerId) - : m_device(device), m_addon(addon), m_strControllerId(strControllerId) + const std::string& strControllerId, + CPeripherals& manager) + : m_device(device), m_addon(addon), m_strControllerId(strControllerId), m_manager(manager) { auto peripheralAddon = m_addon.lock(); assert(peripheralAddon != nullptr); @@ -59,6 +61,29 @@ bool CAddonButtonMap::Load(void) bSuccess |= addon->GetIgnoredPrimitives(m_device, ignoredPrimitives); } + if (features.empty()) + { + // Check if we can initialize a buttonmap from the peripheral bus + PeripheralBusPtr peripheralBus = m_manager.GetBusByType(m_device->GetBusType()); + if (peripheralBus) + { + CLog::Log(LOGDEBUG, + "Buttonmap not found for {}, attempting to initialize from peripheral bus", + m_device->Location()); + if (peripheralBus->InitializeButtonMap(*m_device, *this)) + { + bSuccess = true; + + if (auto addon = m_addon.lock()) + { + addon->GetAppearance(m_device, controllerAppearance); + addon->GetFeatures(m_device, m_strControllerId, features); + addon->GetIgnoredPrimitives(m_device, ignoredPrimitives); + } + } + } + } + // GetFeatures() was changed to always return false if no features were // retrieved. Check here, just in case its contract is changed or violated in // the future. diff --git a/xbmc/peripherals/addons/AddonButtonMap.h b/xbmc/peripherals/addons/AddonButtonMap.h index b4b6463fee..7bbc91947f 100644 --- a/xbmc/peripherals/addons/AddonButtonMap.h +++ b/xbmc/peripherals/addons/AddonButtonMap.h @@ -18,6 +18,7 @@ namespace PERIPHERALS { class CPeripheral; +class CPeripherals; /*! * \ingroup peripherals @@ -27,7 +28,8 @@ class CAddonButtonMap : public KODI::JOYSTICK::IButtonMap public: CAddonButtonMap(CPeripheral* device, const std::weak_ptr<CPeripheralAddon>& addon, - const std::string& strControllerId); + const std::string& strControllerId, + CPeripherals& manager); ~CAddonButtonMap(void) override; @@ -133,6 +135,7 @@ private: CPeripheral* const m_device; const std::weak_ptr<CPeripheralAddon> m_addon; const std::string m_strControllerId; + CPeripherals& m_manager; // Button map state std::string m_controllerAppearance; diff --git a/xbmc/peripherals/addons/AddonButtonMapping.cpp b/xbmc/peripherals/addons/AddonButtonMapping.cpp index 3be55df3f6..0da424ab27 100644 --- a/xbmc/peripherals/addons/AddonButtonMapping.cpp +++ b/xbmc/peripherals/addons/AddonButtonMapping.cpp @@ -33,7 +33,7 @@ CAddonButtonMapping::CAddonButtonMapping(CPeripherals& manager, else { const std::string controllerId = mapper->ControllerID(); - m_buttonMap = std::make_unique<CAddonButtonMap>(peripheral, addon, controllerId); + m_buttonMap = std::make_unique<CAddonButtonMap>(peripheral, addon, controllerId, manager); if (m_buttonMap->Load()) { KEYMAP::IKeymap* keymap = peripheral->GetKeymap(controllerId); diff --git a/xbmc/peripherals/addons/AddonInputHandling.cpp b/xbmc/peripherals/addons/AddonInputHandling.cpp index 088d01a2f1..4643e84e3f 100644 --- a/xbmc/peripherals/addons/AddonInputHandling.cpp +++ b/xbmc/peripherals/addons/AddonInputHandling.cpp @@ -24,28 +24,38 @@ using namespace KODI; using namespace JOYSTICK; using namespace PERIPHERALS; -CAddonInputHandling::CAddonInputHandling(CPeripheral* peripheral, +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr<CPeripheralAddon> addon, IInputHandler* handler, IDriverReceiver* receiver) - : m_peripheral(peripheral), + : m_manager(manager), + m_peripheral(peripheral), m_addon(std::move(addon)), m_joystickInputHandler(handler), m_joystickDriverReceiver(receiver) { } -CAddonInputHandling::CAddonInputHandling(CPeripheral* peripheral, +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr<CPeripheralAddon> addon, KEYBOARD::IKeyboardInputHandler* handler) - : m_peripheral(peripheral), m_addon(std::move(addon)), m_keyboardInputHandler(handler) + : m_manager(manager), + m_peripheral(peripheral), + m_addon(std::move(addon)), + m_keyboardInputHandler(handler) { } -CAddonInputHandling::CAddonInputHandling(CPeripheral* peripheral, +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr<CPeripheralAddon> addon, MOUSE::IMouseInputHandler* handler) - : m_peripheral(peripheral), m_addon(std::move(addon)), m_mouseInputHandler(handler) + : m_manager(manager), + m_peripheral(peripheral), + m_addon(std::move(addon)), + m_mouseInputHandler(handler) { } @@ -69,7 +79,7 @@ bool CAddonInputHandling::Load() controllerId = m_mouseInputHandler->ControllerID(); if (!controllerId.empty()) - m_buttonMap = std::make_unique<CAddonButtonMap>(m_peripheral, m_addon, controllerId); + m_buttonMap = std::make_unique<CAddonButtonMap>(m_peripheral, m_addon, controllerId, m_manager); if (m_buttonMap && m_buttonMap->Load()) { diff --git a/xbmc/peripherals/addons/AddonInputHandling.h b/xbmc/peripherals/addons/AddonInputHandling.h index e5c98e3748..19816a6ca5 100644 --- a/xbmc/peripherals/addons/AddonInputHandling.h +++ b/xbmc/peripherals/addons/AddonInputHandling.h @@ -38,6 +38,7 @@ class IMouseInputHandler; namespace PERIPHERALS { class CPeripheral; +class CPeripherals; class CPeripheralAddon; /*! @@ -49,16 +50,19 @@ class CAddonInputHandling : public KODI::JOYSTICK::IDriverHandler, public KODI::MOUSE::IMouseDriverHandler { public: - CAddonInputHandling(CPeripheral* peripheral, + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr<CPeripheralAddon> addon, KODI::JOYSTICK::IInputHandler* handler, KODI::JOYSTICK::IDriverReceiver* receiver); - CAddonInputHandling(CPeripheral* peripheral, + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr<CPeripheralAddon> addon, KODI::KEYBOARD::IKeyboardInputHandler* handler); - CAddonInputHandling(CPeripheral* peripheral, + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr<CPeripheralAddon> addon, KODI::MOUSE::IMouseInputHandler* handler); @@ -89,6 +93,7 @@ public: private: // Construction parameters + CPeripherals& m_manager; CPeripheral* const m_peripheral; const std::shared_ptr<CPeripheralAddon> m_addon; KODI::JOYSTICK::IInputHandler* const m_joystickInputHandler{nullptr}; diff --git a/xbmc/peripherals/bus/PeripheralBus.h b/xbmc/peripherals/bus/PeripheralBus.h index 424d012d9f..33594a0faa 100644 --- a/xbmc/peripherals/bus/PeripheralBus.h +++ b/xbmc/peripherals/bus/PeripheralBus.h @@ -17,6 +17,14 @@ class CFileItemList; +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +} // namespace JOYSTICK +} // namespace KODI + namespace PERIPHERALS { class CPeripheral; @@ -60,6 +68,15 @@ public: virtual bool InitializeProperties(CPeripheral& peripheral); /*! + * \brief Initialize a joystick buttonmap, if possible + */ + virtual bool InitializeButtonMap(const CPeripheral& peripheral, + KODI::JOYSTICK::IButtonMap& buttonMap) const + { + return false; + } + + /*! * @brief Get the instance of the peripheral at the given location. * @param strLocation The location. * @return The peripheral or NULL if it wasn't found. diff --git a/xbmc/peripherals/devices/Peripheral.cpp b/xbmc/peripherals/devices/Peripheral.cpp index ff9910c2ef..bb00d516a3 100644 --- a/xbmc/peripherals/devices/Peripheral.cpp +++ b/xbmc/peripherals/devices/Peripheral.cpp @@ -597,7 +597,7 @@ void CPeripheral::RegisterInputHandler(IInputHandler* handler, bool bPromiscuous if (addon) { std::unique_ptr<CAddonInputHandling> addonInput = std::make_unique<CAddonInputHandling>( - this, std::move(addon), handler, GetDriverReceiver()); + m_manager, this, std::move(addon), handler, GetDriverReceiver()); if (addonInput->Load()) { RegisterJoystickDriverHandler(addonInput.get(), bPromiscuous); @@ -638,7 +638,7 @@ void CPeripheral::RegisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handl if (addon) { std::unique_ptr<CAddonInputHandling> addonInput = - std::make_unique<CAddonInputHandling>(this, std::move(addon), handler); + std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler); if (addonInput->Load()) keyboardDriverHandler = std::move(addonInput); } @@ -689,7 +689,7 @@ void CPeripheral::RegisterMouseHandler(MOUSE::IMouseInputHandler* handler, if (addon) { std::unique_ptr<CAddonInputHandling> addonInput = - std::make_unique<CAddonInputHandling>(this, std::move(addon), handler); + std::make_unique<CAddonInputHandling>(m_manager, this, std::move(addon), handler); if (addonInput->Load()) mouseDriverHandler = std::move(addonInput); } diff --git a/xbmc/peripherals/devices/PeripheralJoystick.cpp b/xbmc/peripherals/devices/PeripheralJoystick.cpp index a6029561d6..c5285c245e 100644 --- a/xbmc/peripherals/devices/PeripheralJoystick.cpp +++ b/xbmc/peripherals/devices/PeripheralJoystick.cpp @@ -92,7 +92,8 @@ bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature) if (bSuccess) { - m_buttonMap = std::make_unique<CAddonButtonMap>(this, addon, DEFAULT_CONTROLLER_ID); + m_buttonMap = + std::make_unique<CAddonButtonMap>(this, addon, DEFAULT_CONTROLLER_ID, m_manager); if (m_buttonMap->Load()) { InitializeDeadzoneFiltering(*m_buttonMap); diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp index 18bbb557e0..c3032b4352 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp @@ -9,6 +9,11 @@ #include "AndroidJoystickState.h" #include "AndroidJoystickTranslator.h" +#include "games/controllers/ControllerIDs.h" +#include "games/controllers/DefaultController.h" +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IButtonMap.h" #include "utils/StringUtils.h" #include "utils/log.h" @@ -19,8 +24,42 @@ #include <android/input.h> #include <androidjni/View.h> +using namespace KODI; using namespace PERIPHERALS; +namespace +{ +// clang-format off +static const std::vector<int> ButtonKeycodes{ + // add the usual suspects + AKEYCODE_BUTTON_A, + AKEYCODE_BUTTON_B, + AKEYCODE_BUTTON_C, + AKEYCODE_BUTTON_X, + AKEYCODE_BUTTON_Y, + AKEYCODE_BUTTON_Z, + AKEYCODE_BACK, + AKEYCODE_MENU, + AKEYCODE_HOME, + AKEYCODE_BUTTON_SELECT, + AKEYCODE_BUTTON_MODE, + AKEYCODE_BUTTON_START, + AKEYCODE_BUTTON_L1, + AKEYCODE_BUTTON_R1, + AKEYCODE_BUTTON_L2, + AKEYCODE_BUTTON_R2, + AKEYCODE_BUTTON_THUMBL, + AKEYCODE_BUTTON_THUMBR, + AKEYCODE_DPAD_UP, + AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_CENTER, + // only add additional buttons at the end of the list +}; +// clang-format on +} // namespace + static std::string PrintAxisIds(const std::vector<int>& axisIds) { if (axisIds.empty()) @@ -54,15 +93,15 @@ static void MapAxisIds(int axisId, if (axisIds.empty()) { - axisIds.push_back(primaryAxisId); - axisIds.push_back(secondaryAxisId); + axisIds.emplace_back(primaryAxisId); + axisIds.emplace_back(secondaryAxisId); } if (axisIds.size() > 1) return; if (axisId == primaryAxisId) - axisIds.push_back(secondaryAxisId); + axisIds.emplace_back(secondaryAxisId); else if (axisId == secondaryAxisId) axisIds.insert(axisIds.begin(), primaryAxisId); } @@ -140,10 +179,10 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) MapAxisIds(axisId, AMOTION_EVENT_AXIS_LTRIGGER, AMOTION_EVENT_AXIS_BRAKE, axis.ids); MapAxisIds(axisId, AMOTION_EVENT_AXIS_RTRIGGER, AMOTION_EVENT_AXIS_GAS, axis.ids); - m_axes.push_back(axis); + m_axes.emplace_back(std::move(axis)); CLog::Log(LOGDEBUG, "CAndroidJoystickState: axis {} on input device \"{}\" with ID {} detected", - PrintAxisIds(axis.ids), deviceName, m_deviceId); + PrintAxisIds(m_axes.back().ids), deviceName, m_deviceId); } else CLog::Log(LOGWARNING, @@ -151,30 +190,9 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) axisId, deviceName, m_deviceId); } - // add the usual suspects - m_buttons.push_back({{AKEYCODE_BUTTON_A}}); - m_buttons.push_back({{AKEYCODE_BUTTON_B}}); - m_buttons.push_back({{AKEYCODE_BUTTON_C}}); - m_buttons.push_back({{AKEYCODE_BUTTON_X}}); - m_buttons.push_back({{AKEYCODE_BUTTON_Y}}); - m_buttons.push_back({{AKEYCODE_BUTTON_Z}}); - m_buttons.push_back({{AKEYCODE_BACK}}); - m_buttons.push_back({{AKEYCODE_MENU}}); - m_buttons.push_back({{AKEYCODE_HOME}}); - m_buttons.push_back({{AKEYCODE_BUTTON_SELECT}}); - m_buttons.push_back({{AKEYCODE_BUTTON_MODE}}); - m_buttons.push_back({{AKEYCODE_BUTTON_START}}); - m_buttons.push_back({{AKEYCODE_BUTTON_L1}}); - m_buttons.push_back({{AKEYCODE_BUTTON_R1}}); - m_buttons.push_back({{AKEYCODE_BUTTON_L2}}); - m_buttons.push_back({{AKEYCODE_BUTTON_R2}}); - m_buttons.push_back({{AKEYCODE_BUTTON_THUMBL}}); - m_buttons.push_back({{AKEYCODE_BUTTON_THUMBR}}); - m_buttons.push_back({{AKEYCODE_DPAD_UP}}); - m_buttons.push_back({{AKEYCODE_DPAD_RIGHT}}); - m_buttons.push_back({{AKEYCODE_DPAD_DOWN}}); - m_buttons.push_back({{AKEYCODE_DPAD_LEFT}}); - m_buttons.push_back({{AKEYCODE_DPAD_CENTER}}); + // map buttons + for (int buttonKeycode : ButtonKeycodes) + m_buttons.emplace_back(JoystickAxis{{buttonKeycode}}); // check if there are no buttons or axes at all if (GetButtonCount() == 0 && GetAxisCount() == 0) @@ -200,6 +218,67 @@ void CAndroidJoystickState::Deinitialize(void) m_digitalEvents.clear(); } +bool CAndroidJoystickState::InitializeButtonMap(JOYSTICK::IButtonMap& buttonMap) const +{ + // We only map the default controller + if (buttonMap.ControllerID() != DEFAULT_CONTROLLER_ID) + return false; + + bool success = false; + + // Map buttons + for (int buttonKeycode : ButtonKeycodes) + success |= MapButton(buttonMap, buttonKeycode); + + // Map D-pad + success |= MapDpad(buttonMap, AMOTION_EVENT_AXIS_HAT_X, AMOTION_EVENT_AXIS_HAT_Y); + + // Map triggers + // Note: This should come after buttons, because the PS4 controller uses + // both a digital button and an analog axis for the triggers, and we want + // the analog axis to override the button for full range of motion. + success |= MapTrigger(buttonMap, AMOTION_EVENT_AXIS_LTRIGGER, + GAME::CDefaultController::FEATURE_LEFT_TRIGGER); + success |= MapTrigger(buttonMap, AMOTION_EVENT_AXIS_RTRIGGER, + GAME::CDefaultController::FEATURE_RIGHT_TRIGGER); + + // Map analog sticks + success |= MapAnalogStick(buttonMap, AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y, + GAME::CDefaultController::FEATURE_LEFT_STICK); + success |= MapAnalogStick(buttonMap, AMOTION_EVENT_AXIS_Z, AMOTION_EVENT_AXIS_RZ, + GAME::CDefaultController::FEATURE_RIGHT_STICK); + + if (success) + { + // If the controller has both L2/R2 buttons and LTRIGGER/RTRIGGER axes, it's + // probably a PS controller + size_t indexL2 = 0; + size_t indexR2 = 0; + size_t indexLTrigger = 0; + size_t indexRTrigger = 0; + if (GetAxesIndex({AKEYCODE_BUTTON_L2}, m_buttons, indexL2) && + GetAxesIndex({AKEYCODE_BUTTON_R2}, m_buttons, indexR2) && + GetAxesIndex({AMOTION_EVENT_AXIS_LTRIGGER}, m_axes, indexLTrigger) && + GetAxesIndex({AMOTION_EVENT_AXIS_RTRIGGER}, m_axes, indexRTrigger)) + { + CLog::Log(LOGDEBUG, "Detected dual-input triggers, ignoring digital buttons"); + std::vector<JOYSTICK::CDriverPrimitive> ignoredPrimitives{ + {JOYSTICK::PRIMITIVE_TYPE::BUTTON, static_cast<unsigned int>(indexL2)}, + {JOYSTICK::PRIMITIVE_TYPE::BUTTON, static_cast<unsigned int>(indexR2)}, + }; + buttonMap.SetIgnoredPrimitives(ignoredPrimitives); + + CLog::Log(LOGDEBUG, "Setting appearance to {}", GAME::CONTROLLER_ID_PLAYSTATION); + buttonMap.SetAppearance(GAME::CONTROLLER_ID_PLAYSTATION); + } + + // Save the buttonmap + buttonMap.SaveButtonMap(); + } + + return success; +} + bool CAndroidJoystickState::ProcessEvent(const AInputEvent* event) { int32_t type = AInputEvent_getType(event); @@ -238,7 +317,7 @@ bool CAndroidJoystickState::ProcessEvent(const AInputEvent* event) std::vector<float> values; values.reserve(axis.ids.size()); for (const auto& axisId : axis.ids) - values.push_back(AMotionEvent_getAxisValue(event, axisId, pointer)); + values.emplace_back(AMotionEvent_getAxisValue(event, axisId, pointer)); // remove all zero values values.erase(std::remove(values.begin(), values.end(), 0.0f), values.end()); @@ -334,6 +413,144 @@ bool CAndroidJoystickState::SetAxisValue(const std::vector<int>& axisIds, return true; } +bool CAndroidJoystickState::MapButton(JOYSTICK::IButtonMap& buttonMap, int buttonKeycode) const +{ + size_t buttonIndex = 0; + std::string featureName; + + if (!GetAxesIndex({buttonKeycode}, m_buttons, buttonIndex)) + return false; + + // Check if button is already mapped + JOYSTICK::CDriverPrimitive buttonPrimitive{JOYSTICK::PRIMITIVE_TYPE::BUTTON, + static_cast<unsigned int>(buttonIndex)}; + if (buttonMap.GetFeature(buttonPrimitive, featureName)) + return false; + + // Translate the button + std::string controllerButton = CAndroidJoystickTranslator::TranslateJoystickButton(buttonKeycode); + if (controllerButton.empty()) + return false; + + // Map the button + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", controllerButton, + buttonPrimitive.ToString()); + buttonMap.AddScalar(controllerButton, buttonPrimitive); + + return true; +} + +bool CAndroidJoystickState::MapTrigger(JOYSTICK::IButtonMap& buttonMap, + int axisId, + const std::string& triggerName) const +{ + size_t axisIndex = 0; + std::string featureName; + + if (!GetAxesIndex({axisId}, m_axes, axisIndex)) + return false; + + const JOYSTICK::CDriverPrimitive semiaxis{static_cast<unsigned int>(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + if (buttonMap.GetFeature(semiaxis, featureName)) + return false; + + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", triggerName, semiaxis.ToString()); + buttonMap.AddScalar(triggerName, semiaxis); + + return true; +} + +bool CAndroidJoystickState::MapDpad(JOYSTICK::IButtonMap& buttonMap, + int horizAxisId, + int vertAxisId) const +{ + bool success = false; + + size_t axisIndex = 0; + std::string featureName; + + // Map horizontal axis + if (GetAxesIndex({horizAxisId}, m_axes, axisIndex)) + { + const JOYSTICK::CDriverPrimitive positiveSemiaxis{static_cast<unsigned int>(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + const JOYSTICK::CDriverPrimitive negativeSemiaxis{static_cast<unsigned int>(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + if (!buttonMap.GetFeature(positiveSemiaxis, featureName) && + !buttonMap.GetFeature(negativeSemiaxis, featureName)) + { + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_LEFT, + negativeSemiaxis.ToString()); + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_RIGHT, + positiveSemiaxis.ToString()); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_LEFT, negativeSemiaxis); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_RIGHT, positiveSemiaxis); + success |= true; + } + } + + // Map vertical axis + if (GetAxesIndex({vertAxisId}, m_axes, axisIndex)) + { + const JOYSTICK::CDriverPrimitive positiveSemiaxis{static_cast<unsigned int>(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + const JOYSTICK::CDriverPrimitive negativeSemiaxis{static_cast<unsigned int>(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + if (!buttonMap.GetFeature(positiveSemiaxis, featureName) && + !buttonMap.GetFeature(negativeSemiaxis, featureName)) + { + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_UP, + negativeSemiaxis.ToString()); + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_DOWN, + positiveSemiaxis.ToString()); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_DOWN, positiveSemiaxis); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_UP, negativeSemiaxis); + success |= true; + } + } + + return success; +} + +bool CAndroidJoystickState::MapAnalogStick(JOYSTICK::IButtonMap& buttonMap, + int horizAxisId, + int vertAxisId, + const std::string& analogStickName) const +{ + size_t axisIndex1 = 0; + size_t axisIndex2 = 0; + std::string featureName; + + if (!GetAxesIndex({horizAxisId}, m_axes, axisIndex1) || + !GetAxesIndex({vertAxisId}, m_axes, axisIndex2)) + return false; + + const JOYSTICK::CDriverPrimitive upSemiaxis{static_cast<unsigned int>(axisIndex2), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + const JOYSTICK::CDriverPrimitive downSemiaxis{static_cast<unsigned int>(axisIndex2), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + const JOYSTICK::CDriverPrimitive leftSemiaxis{static_cast<unsigned int>(axisIndex1), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + const JOYSTICK::CDriverPrimitive rightSemiaxis{static_cast<unsigned int>(axisIndex1), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + if (buttonMap.GetFeature(upSemiaxis, featureName) || + buttonMap.GetFeature(downSemiaxis, featureName) || + buttonMap.GetFeature(leftSemiaxis, featureName) || + buttonMap.GetFeature(rightSemiaxis, featureName)) + return false; + + CLog::Log(LOGDEBUG, "Automatically mapping {} to [{}, {}, {}, {}]", analogStickName, + upSemiaxis.ToString(), downSemiaxis.ToString(), leftSemiaxis.ToString(), + rightSemiaxis.ToString()); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::UP, upSemiaxis); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::DOWN, downSemiaxis); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::LEFT, leftSemiaxis); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::RIGHT, rightSemiaxis); + + return true; +} + float CAndroidJoystickState::Contain(float value, float min, float max) { if (value < min) diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.h b/xbmc/platform/android/peripherals/AndroidJoystickState.h index 33ff953a46..550222d6f7 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.h +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.h @@ -18,6 +18,14 @@ struct AInputEvent; class CJNIViewInputDevice; +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +} // namespace JOYSTICK +} // namespace KODI + namespace PERIPHERALS { class CAndroidJoystickState @@ -33,25 +41,39 @@ public: unsigned int GetAxisCount() const { return static_cast<unsigned int>(m_axes.size()); } /*! - * Initialize the joystick object. Joystick will be initialized before the - * first call to GetEvents(). - */ + * \brief Initialize the joystick object + * + * Joystick will be initialized before the first call to GetEvents(). + */ bool Initialize(const CJNIViewInputDevice& inputDevice); /*! - * Deinitialize the joystick object. GetEvents() will not be called after - * deinitialization. - */ + * \brief Initialize a joystick buttonmap, if possible + * + * Android has a large database of buttonmaps, which it uses to provide + * mapped button keycodes such as AKEYCODE_BUTTON_A. We can take advantage of + * this to initialize a default buttonmap based on these mappings. + * + * If Android can't map the buttons, it will use generic button keycodes such + * as AKEYCODE_BUTTON_1, in which case we can't initialize the buttonmap. + */ + bool InitializeButtonMap(KODI::JOYSTICK::IButtonMap& buttonMap) const; + + /*! + * \brief Deinitialize the joystick object + * + * GetEvents() will not be called after deinitialization. + */ void Deinitialize(); /*! - * Processes the given input event. - */ + * \brief Processes the given input event. + */ bool ProcessEvent(const AInputEvent* event); /*! - * Get events that have occurred since the last call to GetEvents() - */ + * \brief Get events that have occurred since the last call to GetEvents() + */ void GetEvents(std::vector<kodi::addon::PeripheralEvent>& events); private: @@ -61,6 +83,16 @@ private: void GetButtonEvents(std::vector<kodi::addon::PeripheralEvent>& events); void GetAxisEvents(std::vector<kodi::addon::PeripheralEvent>& events) const; + bool MapButton(KODI::JOYSTICK::IButtonMap& buttonMap, int buttonKeycode) const; + bool MapTrigger(KODI::JOYSTICK::IButtonMap& buttonMap, + int axisId, + const std::string& triggerName) const; + bool MapDpad(KODI::JOYSTICK::IButtonMap& buttonMap, int horizAxisId, int vertAxisId) const; + bool MapAnalogStick(KODI::JOYSTICK::IButtonMap& buttonMap, + int horizAxisId, + int vertAxisId, + const std::string& analogStickName) const; + static float Contain(float value, float min, float max); static float Scale(float value, float max, float scaledMax); static float Deadzone(float value, float deadzone); diff --git a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp index 3717616413..f8f638cbde 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp @@ -8,9 +8,12 @@ #include "AndroidJoystickTranslator.h" +#include "games/controllers/DefaultController.h" + #include <android/input.h> #include <android/keycodes.h> +using namespace KODI; using namespace PERIPHERALS; const char* CAndroidJoystickTranslator::TranslateAxis(int axisId) @@ -726,3 +729,52 @@ const char* CAndroidJoystickTranslator::TranslateKeyCode(int keyCode) return "unknown"; } + +const char* CAndroidJoystickTranslator::TranslateJoystickButton(int buttonKeycode) +{ + switch (buttonKeycode) + { + case AKEYCODE_BUTTON_A: + case AKEYCODE_DPAD_CENTER: + return GAME::CDefaultController::FEATURE_A; + case AKEYCODE_BUTTON_B: + return GAME::CDefaultController::FEATURE_B; + case AKEYCODE_BUTTON_X: + return GAME::CDefaultController::FEATURE_X; + case AKEYCODE_BUTTON_Y: + return GAME::CDefaultController::FEATURE_Y; + case AKEYCODE_BUTTON_START: + case AKEYCODE_MENU: + return GAME::CDefaultController::FEATURE_START; + case AKEYCODE_BUTTON_SELECT: + case AKEYCODE_BACK: + return GAME::CDefaultController::FEATURE_BACK; + case AKEYCODE_BUTTON_MODE: + case AKEYCODE_HOME: + return GAME::CDefaultController::FEATURE_GUIDE; + case AKEYCODE_DPAD_UP: + return GAME::CDefaultController::FEATURE_UP; + case AKEYCODE_DPAD_RIGHT: + return GAME::CDefaultController::FEATURE_RIGHT; + case AKEYCODE_DPAD_DOWN: + return GAME::CDefaultController::FEATURE_DOWN; + case AKEYCODE_DPAD_LEFT: + return GAME::CDefaultController::FEATURE_LEFT; + case AKEYCODE_BUTTON_L1: + return GAME::CDefaultController::FEATURE_LEFT_BUMPER; + case AKEYCODE_BUTTON_R1: + return GAME::CDefaultController::FEATURE_RIGHT_BUMPER; + case AKEYCODE_BUTTON_L2: + return GAME::CDefaultController::FEATURE_LEFT_TRIGGER; + case AKEYCODE_BUTTON_R2: + return GAME::CDefaultController::FEATURE_RIGHT_TRIGGER; + case AKEYCODE_BUTTON_THUMBL: + return GAME::CDefaultController::FEATURE_LEFT_THUMB; + case AKEYCODE_BUTTON_THUMBR: + return GAME::CDefaultController::FEATURE_RIGHT_THUMB; + default: + break; + } + + return ""; +} diff --git a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h index 53a953f0ac..8681de0c12 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h +++ b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h @@ -14,21 +14,30 @@ class CAndroidJoystickTranslator { public: /*! - * \brief Translate an axis ID to an Android enum suitable for logging - * - * \param axisId The axis ID given in <android/input.h> - * - * \return The translated enum label, or "unknown" if unknown - */ + * \brief Translate an axis ID to an Android enum suitable for logging + * + * \param axisId The axis ID given in <android/input.h> + * + * \return The translated enum label, or "unknown" if unknown + */ static const char* TranslateAxis(int axisId); /*! - * \brief Translate a key code to an Android enum suitable for logging - * - * \param keyCode The key code given in <android/keycodes.h> - * - * \return The translated enum label, or "unknown" if unknown - */ + * \brief Translate a key code to an Android enum suitable for logging + * + * \param keyCode The key code given in <android/keycodes.h> + * + * \return The translated enum label, or "unknown" if unknown + */ static const char* TranslateKeyCode(int keyCode); + + /*! + * \brief Translate a button key code to a feature on the default controller + * + * \param buttonKeycode The key code given in <android/keycodes.h> + * + * \return The translated feature, or "" if unknown + */ + static const char* TranslateJoystickButton(int buttonKeycode); }; } // namespace PERIPHERALS diff --git a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp index 753dc71099..f8282eeb55 100644 --- a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp +++ b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp @@ -16,6 +16,7 @@ #include "utils/log.h" #include "platform/android/activity/XBMCApp.h" +#include "platform/android/peripherals/AndroidJoystickState.h" #include <algorithm> #include <mutex> @@ -120,6 +121,50 @@ bool CPeripheralBusAndroid::InitializeProperties(CPeripheral& peripheral) return true; } +bool CPeripheralBusAndroid::InitializeButtonMap(const CPeripheral& peripheral, + JOYSTICK::IButtonMap& buttonMap) const +{ + int deviceId; + if (!GetDeviceId(peripheral.Location(), deviceId)) + { + CLog::Log(LOGWARNING, + "CPeripheralBusAndroid: failed to initialize buttonmap due to unknown device ID for " + "peripheral \"{}\"", + peripheral.Location()); + return false; + } + + // get the joystick state + auto it = m_joystickStates.find(deviceId); + if (it == m_joystickStates.end()) + { + CLog::Log(LOGWARNING, + "CPeripheralBusAndroid: joystick with device ID {} not found for peripheral \"{}\"", + deviceId, peripheral.Location()); + return false; + } + + const CAndroidJoystickState& joystick = it->second; + if (joystick.GetButtonCount() == 0 && joystick.GetAxisCount() == 0) + { + CLog::Log(LOGDEBUG, + "CPeripheralBusAndroid: joystick has no buttons or axes for peripheral \"{}\"", + peripheral.Location()); + return false; + } + + if (!joystick.InitializeButtonMap(buttonMap)) + { + CLog::Log( + LOGDEBUG, + "CPeripheralBusAndroid: failed to initialize joystick buttonmap for peripheral \"{}\"", + peripheral.Location()); + return false; + } + + return true; +} + void CPeripheralBusAndroid::Initialise(void) { CPeripheralBus::Initialise(); @@ -208,7 +253,7 @@ void CPeripheralBusAndroid::OnInputDeviceAdded(int deviceId) PeripheralScanResult result; if (!ConvertToPeripheralScanResult(device, result)) return; - m_scanResults.m_results.push_back(result); + m_scanResults.m_results.emplace_back(std::move(result)); } CLog::Log(LOGDEBUG, "CPeripheralBusAndroid: input device with ID {} added", deviceId); @@ -345,7 +390,7 @@ PeripheralScanResults CPeripheralBusAndroid::GetInputDevices() continue; CLog::Log(LOGINFO, "CPeripheralBusAndroid: added input device"); - results.m_results.push_back(result); + results.m_results.emplace_back(std::move(result)); } return results; diff --git a/xbmc/platform/android/peripherals/PeripheralBusAndroid.h b/xbmc/platform/android/peripherals/PeripheralBusAndroid.h index c3c42aa730..5ee9480970 100644 --- a/xbmc/platform/android/peripherals/PeripheralBusAndroid.h +++ b/xbmc/platform/android/peripherals/PeripheralBusAndroid.h @@ -35,6 +35,8 @@ public: // specialisation of CPeripheralBus bool InitializeProperties(CPeripheral& peripheral) override; + bool InitializeButtonMap(const CPeripheral& peripheral, + KODI::JOYSTICK::IButtonMap& buttonMap) const override; void Initialise(void) override; void ProcessEvents() override; |