summaryrefslogtreecommitdiffstats
path: root/xbmc/input/joysticks
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/input/joysticks')
-rw-r--r--xbmc/input/joysticks/CMakeLists.txt30
-rw-r--r--xbmc/input/joysticks/DeadzoneFilter.cpp96
-rw-r--r--xbmc/input/joysticks/DeadzoneFilter.h82
-rw-r--r--xbmc/input/joysticks/DriverPrimitive.cpp271
-rw-r--r--xbmc/input/joysticks/DriverPrimitive.h200
-rw-r--r--xbmc/input/joysticks/JoystickEasterEgg.cpp107
-rw-r--r--xbmc/input/joysticks/JoystickEasterEgg.h45
-rw-r--r--xbmc/input/joysticks/JoystickIDs.h13
-rw-r--r--xbmc/input/joysticks/JoystickMonitor.cpp111
-rw-r--r--xbmc/input/joysticks/JoystickMonitor.h58
-rw-r--r--xbmc/input/joysticks/JoystickTranslator.cpp175
-rw-r--r--xbmc/input/joysticks/JoystickTranslator.h125
-rw-r--r--xbmc/input/joysticks/JoystickTypes.h186
-rw-r--r--xbmc/input/joysticks/JoystickUtils.cpp103
-rw-r--r--xbmc/input/joysticks/JoystickUtils.h110
-rw-r--r--xbmc/input/joysticks/RumbleGenerator.cpp130
-rw-r--r--xbmc/input/joysticks/RumbleGenerator.h58
-rw-r--r--xbmc/input/joysticks/dialogs/CMakeLists.txt5
-rw-r--r--xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.cpp59
-rw-r--r--xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.h30
-rw-r--r--xbmc/input/joysticks/generic/ButtonMapping.cpp592
-rw-r--r--xbmc/input/joysticks/generic/ButtonMapping.h393
-rw-r--r--xbmc/input/joysticks/generic/CMakeLists.txt11
-rw-r--r--xbmc/input/joysticks/generic/DriverReceiving.cpp38
-rw-r--r--xbmc/input/joysticks/generic/DriverReceiving.h46
-rw-r--r--xbmc/input/joysticks/generic/FeatureHandling.cpp551
-rw-r--r--xbmc/input/joysticks/generic/FeatureHandling.h282
-rw-r--r--xbmc/input/joysticks/generic/InputHandling.cpp179
-rw-r--r--xbmc/input/joysticks/generic/InputHandling.h69
-rw-r--r--xbmc/input/joysticks/interfaces/IButtonMap.h343
-rw-r--r--xbmc/input/joysticks/interfaces/IButtonMapCallback.h47
-rw-r--r--xbmc/input/joysticks/interfaces/IButtonMapper.h124
-rw-r--r--xbmc/input/joysticks/interfaces/IButtonSequence.h31
-rw-r--r--xbmc/input/joysticks/interfaces/IDriverHandler.h77
-rw-r--r--xbmc/input/joysticks/interfaces/IDriverReceiver.h35
-rw-r--r--xbmc/input/joysticks/interfaces/IInputHandler.h169
-rw-r--r--xbmc/input/joysticks/interfaces/IInputProvider.h43
-rw-r--r--xbmc/input/joysticks/interfaces/IInputReceiver.h37
-rw-r--r--xbmc/input/joysticks/interfaces/IKeyHandler.h56
-rw-r--r--xbmc/input/joysticks/interfaces/IKeymapHandler.h52
-rw-r--r--xbmc/input/joysticks/keymaps/CMakeLists.txt11
-rw-r--r--xbmc/input/joysticks/keymaps/KeyHandler.cpp290
-rw-r--r--xbmc/input/joysticks/keymaps/KeyHandler.h114
-rw-r--r--xbmc/input/joysticks/keymaps/KeymapHandler.cpp278
-rw-r--r--xbmc/input/joysticks/keymaps/KeymapHandler.h108
-rw-r--r--xbmc/input/joysticks/keymaps/KeymapHandling.cpp104
-rw-r--r--xbmc/input/joysticks/keymaps/KeymapHandling.h76
47 files changed, 6150 insertions, 0 deletions
diff --git a/xbmc/input/joysticks/CMakeLists.txt b/xbmc/input/joysticks/CMakeLists.txt
new file mode 100644
index 0000000..0155e5e
--- /dev/null
+++ b/xbmc/input/joysticks/CMakeLists.txt
@@ -0,0 +1,30 @@
+set(SOURCES DeadzoneFilter.cpp
+ DriverPrimitive.cpp
+ JoystickEasterEgg.cpp
+ JoystickMonitor.cpp
+ JoystickTranslator.cpp
+ JoystickUtils.cpp
+ RumbleGenerator.cpp)
+
+set(HEADERS interfaces/IButtonMap.h
+ interfaces/IButtonMapCallback.h
+ interfaces/IButtonMapper.h
+ interfaces/IButtonSequence.h
+ interfaces/IDriverHandler.h
+ interfaces/IDriverReceiver.h
+ interfaces/IInputHandler.h
+ interfaces/IInputProvider.h
+ interfaces/IInputReceiver.h
+ interfaces/IKeyHandler.h
+ interfaces/IKeymapHandler.h
+ DeadzoneFilter.h
+ DriverPrimitive.h
+ JoystickEasterEgg.h
+ JoystickIDs.h
+ JoystickMonitor.h
+ JoystickTranslator.h
+ JoystickTypes.h
+ JoystickUtils.h
+ RumbleGenerator.h)
+
+core_add_library(input_joystick)
diff --git a/xbmc/input/joysticks/DeadzoneFilter.cpp b/xbmc/input/joysticks/DeadzoneFilter.cpp
new file mode 100644
index 0000000..c437f30
--- /dev/null
+++ b/xbmc/input/joysticks/DeadzoneFilter.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016-2018 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 "DeadzoneFilter.h"
+
+#include "JoystickIDs.h"
+#include "games/controllers/ControllerIDs.h"
+#include "input/joysticks/interfaces/IButtonMap.h"
+#include "peripherals/devices/Peripheral.h"
+#include "utils/log.h"
+
+#include <cmath>
+#include <vector>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+//! Allowed noise for detecting discrete D-pads (value of 0.007 when centered
+//! has been observed)
+#define AXIS_EPSILON 0.01f
+
+// Settings for analog sticks
+#define SETTING_LEFT_STICK_DEADZONE "left_stick_deadzone"
+#define SETTING_RIGHT_STICK_DEADZONE "right_stick_deadzone"
+
+CDeadzoneFilter::CDeadzoneFilter(IButtonMap* buttonMap, PERIPHERALS::CPeripheral* peripheral)
+ : m_buttonMap(buttonMap), m_peripheral(peripheral)
+{
+ if (m_buttonMap->ControllerID() != DEFAULT_CONTROLLER_ID)
+ CLog::Log(LOGERROR, "ERROR: Must use default controller profile instead of {}",
+ m_buttonMap->ControllerID());
+}
+
+float CDeadzoneFilter::FilterAxis(unsigned int axisIndex, float axisValue)
+{
+ float deadzone = 0.0f;
+
+ bool bSuccess =
+ GetDeadzone(axisIndex, deadzone, DEFAULT_LEFT_STICK_NAME, SETTING_LEFT_STICK_DEADZONE) ||
+ GetDeadzone(axisIndex, deadzone, DEFAULT_RIGHT_STICK_NAME, SETTING_RIGHT_STICK_DEADZONE);
+
+ if (bSuccess)
+ return ApplyDeadzone(axisValue, deadzone);
+
+ // Always filter noise about the center
+ if (std::abs(axisValue) < AXIS_EPSILON)
+ axisValue = 0.0f;
+
+ return axisValue;
+}
+
+bool CDeadzoneFilter::GetDeadzone(unsigned int axisIndex,
+ float& deadzone,
+ const char* featureName,
+ const char* settingName)
+{
+ std::vector<ANALOG_STICK_DIRECTION> dirs = {
+ ANALOG_STICK_DIRECTION::UP,
+ ANALOG_STICK_DIRECTION::RIGHT,
+ ANALOG_STICK_DIRECTION::DOWN,
+ ANALOG_STICK_DIRECTION::LEFT,
+ };
+
+ CDriverPrimitive primitive;
+ for (auto dir : dirs)
+ {
+ if (m_buttonMap->GetAnalogStick(featureName, dir, primitive))
+ {
+ if (primitive.Type() == PRIMITIVE_TYPE::SEMIAXIS && primitive.Index() == axisIndex)
+ {
+ deadzone = m_peripheral->GetSettingFloat(settingName);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+float CDeadzoneFilter::ApplyDeadzone(float value, float deadzone)
+{
+ if (deadzone < 0.0f || deadzone >= 1.0f)
+ return 0.0f;
+
+ if (value > deadzone)
+ return (value - deadzone) / (1.0f - deadzone);
+ else if (value < -deadzone)
+ return (value + deadzone) / (1.0f - deadzone);
+
+ return 0.0f;
+}
diff --git a/xbmc/input/joysticks/DeadzoneFilter.h b/xbmc/input/joysticks/DeadzoneFilter.h
new file mode 100644
index 0000000..4547050
--- /dev/null
+++ b/xbmc/input/joysticks/DeadzoneFilter.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016-2018 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 PERIPHERALS
+{
+class CPeripheral;
+}
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IButtonMap;
+
+/*!
+ * \ingroup joystick
+ * \brief Analog axis deadzone filtering
+ *
+ * Axis is scaled appropriately, so position is continuous
+ * from -1.0 to 1.0:
+ *
+ * | / 1.0
+ * | /
+ * __|__/
+ * / |
+ * / |--| Deadzone
+ * -1.0 / |
+ *
+ * After deadzone filtering, the value will be:
+ *
+ * - Negative in the interval [-1.0, -deadzone)
+ * - Zero in the interval [-deadzone, deadzone]
+ * - Positive in the interval (deadzone, 1.0]
+ */
+class CDeadzoneFilter
+{
+public:
+ CDeadzoneFilter(IButtonMap* buttonMap, PERIPHERALS::CPeripheral* peripheral);
+
+ /*!
+ * \brief Apply deadzone filtering to an axis
+ * \param axisIndex The axis index
+ * \param axisValue The axis value
+ * \return The value after applying deadzone filtering
+ */
+ float FilterAxis(unsigned int axisIndex, float axisValue);
+
+private:
+ /*!
+ * \brief Get the deadzone value from the peripheral's settings
+ * \param axisIndex The axis index
+ * \param[out] result The deadzone value
+ * \param featureName The feature that axisIndex is mapped to
+ * \param settingName The setting corresponding to the given feature
+ * \return True if the feature is an analog stick and the peripheral has the setting
+ */
+ bool GetDeadzone(unsigned int axisIndex,
+ float& result,
+ const char* featureName,
+ const char* settingName);
+
+ /*!
+ * \brief Utility function to calculate the deadzone
+ * \param value The value
+ * \param deadzone The deadzone
+ * \return The scaled deadzone
+ */
+ static float ApplyDeadzone(float value, float deadzone);
+
+ // Construction parameters
+ IButtonMap* const m_buttonMap;
+ PERIPHERALS::CPeripheral* const m_peripheral;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/DriverPrimitive.cpp b/xbmc/input/joysticks/DriverPrimitive.cpp
new file mode 100644
index 0000000..166e926
--- /dev/null
+++ b/xbmc/input/joysticks/DriverPrimitive.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2014-2018 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 "DriverPrimitive.h"
+
+#include "games/controllers/ControllerTranslator.h"
+#include "utils/StringUtils.h"
+
+#include <utility>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+CDriverPrimitive::CDriverPrimitive(void) = default;
+
+CDriverPrimitive::CDriverPrimitive(PRIMITIVE_TYPE type, unsigned int index)
+ : m_type(type), m_driverIndex(index)
+{
+}
+
+CDriverPrimitive::CDriverPrimitive(unsigned int hatIndex, HAT_DIRECTION direction)
+ : m_type(PRIMITIVE_TYPE::HAT), m_driverIndex(hatIndex), m_hatDirection(direction)
+{
+}
+
+CDriverPrimitive::CDriverPrimitive(unsigned int axisIndex,
+ int center,
+ SEMIAXIS_DIRECTION direction,
+ unsigned int range)
+ : m_type(PRIMITIVE_TYPE::SEMIAXIS),
+ m_driverIndex(axisIndex),
+ m_center(center),
+ m_semiAxisDirection(direction),
+ m_range(range)
+{
+}
+
+CDriverPrimitive::CDriverPrimitive(XBMCKey keycode)
+ : m_type(PRIMITIVE_TYPE::KEY), m_keycode(keycode)
+{
+}
+
+CDriverPrimitive::CDriverPrimitive(MOUSE::BUTTON_ID index)
+ : m_type(PRIMITIVE_TYPE::MOUSE_BUTTON), m_driverIndex(static_cast<unsigned int>(index))
+{
+}
+
+CDriverPrimitive::CDriverPrimitive(RELATIVE_POINTER_DIRECTION direction)
+ : m_type(PRIMITIVE_TYPE::RELATIVE_POINTER), m_pointerDirection(direction)
+{
+}
+
+bool CDriverPrimitive::operator==(const CDriverPrimitive& rhs) const
+{
+ if (m_type == rhs.m_type)
+ {
+ switch (m_type)
+ {
+ case PRIMITIVE_TYPE::BUTTON:
+ case PRIMITIVE_TYPE::MOTOR:
+ case PRIMITIVE_TYPE::MOUSE_BUTTON:
+ return m_driverIndex == rhs.m_driverIndex;
+ case PRIMITIVE_TYPE::HAT:
+ return m_driverIndex == rhs.m_driverIndex && m_hatDirection == rhs.m_hatDirection;
+ case PRIMITIVE_TYPE::SEMIAXIS:
+ return m_driverIndex == rhs.m_driverIndex && m_center == rhs.m_center &&
+ m_semiAxisDirection == rhs.m_semiAxisDirection && m_range == rhs.m_range;
+ case PRIMITIVE_TYPE::KEY:
+ return m_keycode == rhs.m_keycode;
+ case PRIMITIVE_TYPE::RELATIVE_POINTER:
+ return m_pointerDirection == rhs.m_pointerDirection;
+ default:
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CDriverPrimitive::operator<(const CDriverPrimitive& rhs) const
+{
+ if (m_type < rhs.m_type)
+ return true;
+ if (m_type > rhs.m_type)
+ return false;
+
+ if (m_type == PRIMITIVE_TYPE::BUTTON || m_type == PRIMITIVE_TYPE::HAT ||
+ m_type == PRIMITIVE_TYPE::SEMIAXIS || m_type == PRIMITIVE_TYPE::MOTOR ||
+ m_type == PRIMITIVE_TYPE::MOUSE_BUTTON)
+ {
+ if (m_driverIndex < rhs.m_driverIndex)
+ return true;
+ if (m_driverIndex > rhs.m_driverIndex)
+ return false;
+ }
+
+ if (m_type == PRIMITIVE_TYPE::HAT)
+ {
+ if (m_hatDirection < rhs.m_hatDirection)
+ return true;
+ if (m_hatDirection > rhs.m_hatDirection)
+ return false;
+ }
+
+ if (m_type == PRIMITIVE_TYPE::SEMIAXIS)
+ {
+ if (m_center < rhs.m_center)
+ return true;
+ if (m_center > rhs.m_center)
+ return false;
+
+ if (m_semiAxisDirection < rhs.m_semiAxisDirection)
+ return true;
+ if (m_semiAxisDirection > rhs.m_semiAxisDirection)
+ return false;
+
+ if (m_range < rhs.m_range)
+ return true;
+ if (m_range > rhs.m_range)
+ return false;
+ }
+
+ if (m_type == PRIMITIVE_TYPE::KEY)
+ {
+ if (m_keycode < rhs.m_keycode)
+ return true;
+ if (m_keycode > rhs.m_keycode)
+ return false;
+ }
+
+ if (m_type == PRIMITIVE_TYPE::RELATIVE_POINTER)
+ {
+ if (m_pointerDirection < rhs.m_pointerDirection)
+ return true;
+ if (m_pointerDirection > rhs.m_pointerDirection)
+ return false;
+ }
+
+ return false;
+}
+
+bool CDriverPrimitive::IsValid(void) const
+{
+ if (m_type == PRIMITIVE_TYPE::BUTTON || m_type == PRIMITIVE_TYPE::MOTOR ||
+ m_type == PRIMITIVE_TYPE::MOUSE_BUTTON)
+ return true;
+
+ if (m_type == PRIMITIVE_TYPE::HAT)
+ {
+ return m_hatDirection == HAT_DIRECTION::UP || m_hatDirection == HAT_DIRECTION::DOWN ||
+ m_hatDirection == HAT_DIRECTION::RIGHT || m_hatDirection == HAT_DIRECTION::LEFT;
+ }
+
+ if (m_type == PRIMITIVE_TYPE::SEMIAXIS)
+ {
+ unsigned int maxRange = 1;
+
+ switch (m_center)
+ {
+ case -1:
+ {
+ if (m_semiAxisDirection != SEMIAXIS_DIRECTION::POSITIVE)
+ return false;
+ maxRange = 2;
+ break;
+ }
+ case 0:
+ {
+ if (m_semiAxisDirection != SEMIAXIS_DIRECTION::POSITIVE &&
+ m_semiAxisDirection != SEMIAXIS_DIRECTION::NEGATIVE)
+ return false;
+ break;
+ }
+ case 1:
+ {
+ if (m_semiAxisDirection != SEMIAXIS_DIRECTION::POSITIVE)
+ return false;
+ maxRange = 2;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return 1 <= m_range && m_range <= maxRange;
+ }
+
+ if (m_type == PRIMITIVE_TYPE::KEY)
+ return m_keycode != XBMCK_UNKNOWN;
+
+ if (m_type == PRIMITIVE_TYPE::RELATIVE_POINTER)
+ {
+ return m_pointerDirection == RELATIVE_POINTER_DIRECTION::UP ||
+ m_pointerDirection == RELATIVE_POINTER_DIRECTION::DOWN ||
+ m_pointerDirection == RELATIVE_POINTER_DIRECTION::RIGHT ||
+ m_pointerDirection == RELATIVE_POINTER_DIRECTION::LEFT;
+ }
+
+ return false;
+}
+
+std::string CDriverPrimitive::ToString() const
+{
+ switch (m_type)
+ {
+ case PRIMITIVE_TYPE::BUTTON:
+ return StringUtils::Format("button {}", m_driverIndex);
+ case PRIMITIVE_TYPE::MOTOR:
+ return StringUtils::Format("motor {}", m_driverIndex);
+ case PRIMITIVE_TYPE::MOUSE_BUTTON:
+ return StringUtils::Format("mouse button {}", m_driverIndex);
+ case PRIMITIVE_TYPE::HAT:
+ {
+ switch (m_hatDirection)
+ {
+ case HAT_DIRECTION::UP:
+ return StringUtils::Format("hat {} up", m_driverIndex);
+ case HAT_DIRECTION::DOWN:
+ return StringUtils::Format("hat {} down", m_driverIndex);
+ case HAT_DIRECTION::RIGHT:
+ return StringUtils::Format("hat {} right", m_driverIndex);
+ case HAT_DIRECTION::LEFT:
+ return StringUtils::Format("hat {} left", m_driverIndex);
+ default:
+ break;
+ }
+ break;
+ }
+ case PRIMITIVE_TYPE::SEMIAXIS:
+ {
+ switch (m_semiAxisDirection)
+ {
+ case SEMIAXIS_DIRECTION::POSITIVE:
+ return StringUtils::Format("semiaxis +{}", m_driverIndex);
+ case SEMIAXIS_DIRECTION::NEGATIVE:
+ return StringUtils::Format("semiaxis -{}", m_driverIndex);
+ default:
+ break;
+ }
+ break;
+ }
+ case PRIMITIVE_TYPE::KEY:
+ return StringUtils::Format("key {}",
+ GAME::CControllerTranslator::TranslateKeycode(m_keycode));
+ case PRIMITIVE_TYPE::RELATIVE_POINTER:
+ {
+ switch (m_pointerDirection)
+ {
+ case RELATIVE_POINTER_DIRECTION::UP:
+ return StringUtils::Format("pointer {} up", m_driverIndex);
+ case RELATIVE_POINTER_DIRECTION::DOWN:
+ return StringUtils::Format("pointer {} down", m_driverIndex);
+ case RELATIVE_POINTER_DIRECTION::RIGHT:
+ return StringUtils::Format("pointer {} right", m_driverIndex);
+ case RELATIVE_POINTER_DIRECTION::LEFT:
+ return StringUtils::Format("pointer {} left", m_driverIndex);
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return "";
+}
diff --git a/xbmc/input/joysticks/DriverPrimitive.h b/xbmc/input/joysticks/DriverPrimitive.h
new file mode 100644
index 0000000..11d391b
--- /dev/null
+++ b/xbmc/input/joysticks/DriverPrimitive.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "JoystickTypes.h"
+#include "input/keyboard/KeyboardTypes.h"
+#include "input/mouse/MouseTypes.h"
+
+#include <stdint.h>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \ingroup joystick
+ * \brief Basic driver element associated with input events
+ *
+ * Driver input (bools, floats and enums) is split into primitives that better
+ * map to the physical features on a joystick.
+ *
+ * A bool obviously only maps to a single feature, so it is a driver
+ * primitive. Here, these are called "buttons".
+ *
+ * A hat enum encodes the state of the four hat directions. Each direction
+ * can map to a different feature, so a hat enum consists of four driver
+ * primitives called "hat directions".
+ *
+ * A float is a little trickier. Trivially, it can map to an analog stick or
+ * trigger. However, DirectInput combines two triggers onto a single axis.
+ * Therefore, the axis is split into two primitives called "semiaxes".
+ *
+ * The type determines the fields in use:
+ *
+ * Button:
+ * - driver index
+ *
+ * Hat direction:
+ * - driver index
+ * - hat direction (up/right/down/left)
+ *
+ * Semiaxis:
+ * - driver index
+ * - center (-1, 0 or 1)
+ * - semiaxis direction (positive/negative)
+ * - range (1 or 2)
+ *
+ * Motor:
+ * - driver index
+ *
+ * Key:
+ * - keycode
+ *
+ * Mouse button:
+ * - driver index
+ *
+ * Relative pointer:
+ * - pointer direction
+ *
+ * For more info, see "Chapter 2. Joystick drivers" in the documentation
+ * thread: http://forum.kodi.tv/showthread.php?tid=257764
+ */
+class CDriverPrimitive
+{
+public:
+ /*!
+ * \brief Construct an invalid driver primitive
+ */
+ CDriverPrimitive(void);
+
+ /*!
+ * \brief Construct a driver primitive representing a button or motor
+ */
+ CDriverPrimitive(PRIMITIVE_TYPE type, unsigned int index);
+
+ /*!
+ * \brief Construct a driver primitive representing one of the four
+ * direction arrows on a dpad
+ */
+ CDriverPrimitive(unsigned int hatIndex, HAT_DIRECTION direction);
+
+ /*!
+ * \brief Construct a driver primitive representing the positive or negative
+ * half of an axis
+ */
+ CDriverPrimitive(unsigned int axisIndex,
+ int center,
+ SEMIAXIS_DIRECTION direction,
+ unsigned int range);
+
+ /*!
+ * \brief Construct a driver primitive representing a key on a keyboard
+ */
+ CDriverPrimitive(KEYBOARD::KeySymbol keycode);
+
+ /*!
+ * \brief Construct a driver primitive representing a mouse button
+ */
+ CDriverPrimitive(MOUSE::BUTTON_ID index);
+
+ /*!
+ * \brief Construct a driver primitive representing a relative pointer
+ */
+ CDriverPrimitive(RELATIVE_POINTER_DIRECTION direction);
+
+ bool operator==(const CDriverPrimitive& rhs) const;
+ bool operator<(const CDriverPrimitive& rhs) const;
+
+ bool operator!=(const CDriverPrimitive& rhs) const { return !operator==(rhs); }
+ bool operator>(const CDriverPrimitive& rhs) const { return !(operator<(rhs) || operator==(rhs)); }
+ bool operator<=(const CDriverPrimitive& rhs) const { return operator<(rhs) || operator==(rhs); }
+ bool operator>=(const CDriverPrimitive& rhs) const { return !operator<(rhs); }
+
+ /*!
+ * \brief The type of driver primitive
+ */
+ PRIMITIVE_TYPE Type(void) const { return m_type; }
+
+ /*!
+ * \brief The index used by the joystick driver
+ *
+ * Valid for:
+ * - buttons
+ * - hats
+ * - semiaxes
+ * - motors
+ */
+ unsigned int Index(void) const { return m_driverIndex; }
+
+ /*!
+ * \brief The direction arrow (valid for hat directions)
+ */
+ HAT_DIRECTION HatDirection(void) const { return m_hatDirection; }
+
+ /*!
+ * \brief The location of the zero point of the semiaxis
+ */
+ int Center() const { return m_center; }
+
+ /*!
+ * \brief The semiaxis direction (valid for semiaxes)
+ */
+ SEMIAXIS_DIRECTION SemiAxisDirection(void) const { return m_semiAxisDirection; }
+
+ /*!
+ * \brief The distance between the center and the farthest valid value (valid for semiaxes)
+ */
+ unsigned int Range() const { return m_range; }
+
+ /*!
+ * \brief The keyboard symbol (valid for keys)
+ */
+ KEYBOARD::KeySymbol Keycode() const { return m_keycode; }
+
+ /*!
+ * \brief The mouse button ID (valid for mouse buttons)
+ */
+ MOUSE::BUTTON_ID MouseButton() const { return static_cast<MOUSE::BUTTON_ID>(m_driverIndex); }
+
+ /*!
+ * \brief The relative pointer direction (valid for relative pointers)
+ */
+ RELATIVE_POINTER_DIRECTION PointerDirection() const { return m_pointerDirection; }
+
+ /*!
+ * \brief Test if an driver primitive is valid
+ *
+ * A driver primitive is valid if it has a known type and:
+ *
+ * 1) for hats, it is a cardinal direction
+ * 2) for semi-axes, it is a positive or negative direction
+ * 3) for keys, the keycode is non-empty
+ */
+ bool IsValid(void) const;
+
+ /*!
+ * \brief Convert primitive to a string suitable for logging
+ *
+ * \return The primitive as described by a short string, or empty if invalid
+ */
+ std::string ToString() const;
+
+private:
+ PRIMITIVE_TYPE m_type = PRIMITIVE_TYPE::UNKNOWN;
+ unsigned int m_driverIndex = 0;
+ HAT_DIRECTION m_hatDirection = HAT_DIRECTION::NONE;
+ int m_center = 0;
+ SEMIAXIS_DIRECTION m_semiAxisDirection = SEMIAXIS_DIRECTION::ZERO;
+ unsigned int m_range = 1;
+ KEYBOARD::KeySymbol m_keycode = XBMCK_UNKNOWN;
+ RELATIVE_POINTER_DIRECTION m_pointerDirection = RELATIVE_POINTER_DIRECTION::NONE;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/JoystickEasterEgg.cpp b/xbmc/input/joysticks/JoystickEasterEgg.cpp
new file mode 100644
index 0000000..b0259a6
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickEasterEgg.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016-2018 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 "JoystickEasterEgg.h"
+
+#include "ServiceBroker.h"
+#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"
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+const std::map<std::string, std::vector<FeatureName>> CJoystickEasterEgg::m_sequence = {
+ {
+ DEFAULT_CONTROLLER_ID,
+ {
+ 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,
+ },
+ },
+ {
+ DEFAULT_REMOTE_ID,
+ {
+ "up",
+ "up",
+ "down",
+ "down",
+ "left",
+ "right",
+ "left",
+ "right",
+ "back",
+ "ok",
+ },
+ },
+};
+
+CJoystickEasterEgg::CJoystickEasterEgg(const std::string& controllerId)
+ : m_controllerId(controllerId), m_state(0)
+{
+}
+
+bool CJoystickEasterEgg::OnButtonPress(const FeatureName& feature)
+{
+ bool bHandled = false;
+
+ auto it = m_sequence.find(m_controllerId);
+ if (it != m_sequence.end())
+ {
+ const auto& sequence = it->second;
+
+ // Reset state if it previously finished
+ if (m_state >= sequence.size())
+ m_state = 0;
+
+ if (feature == sequence[m_state])
+ m_state++;
+ else
+ m_state = 0;
+
+ if (IsCapturing())
+ {
+ bHandled = true;
+
+ if (m_state >= sequence.size())
+ OnFinish();
+ }
+ }
+
+ return bHandled;
+}
+
+bool CJoystickEasterEgg::IsCapturing()
+{
+ // Capture input when finished with arrows (2 x up/down/left/right)
+ return m_state > 8;
+}
+
+void CJoystickEasterEgg::OnFinish(void)
+{
+ GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings();
+ gameSettings.ToggleGames();
+
+ WINDOW_SOUND sound = gameSettings.GamesEnabled() ? SOUND_INIT : SOUND_DEINIT;
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetAudioManager().PlayWindowSound(WINDOW_DIALOG_KAI_TOAST, sound);
+
+ //! @todo Shake screen
+}
diff --git a/xbmc/input/joysticks/JoystickEasterEgg.h b/xbmc/input/joysticks/JoystickEasterEgg.h
new file mode 100644
index 0000000..7059d13
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickEasterEgg.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016-2018 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
+
+#include "input/joysticks/interfaces/IButtonSequence.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \brief Hush!!!
+ */
+class CJoystickEasterEgg : public IButtonSequence
+{
+public:
+ explicit CJoystickEasterEgg(const std::string& controllerId);
+ ~CJoystickEasterEgg() override = default;
+
+ // implementation of IButtonSequence
+ bool OnButtonPress(const FeatureName& feature) override;
+ bool IsCapturing() override;
+
+ static void OnFinish(void);
+
+private:
+ // Construction parameters
+ const std::string m_controllerId;
+
+ static const std::map<std::string, std::vector<FeatureName>> m_sequence;
+
+ unsigned int m_state;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/JoystickIDs.h b/xbmc/input/joysticks/JoystickIDs.h
new file mode 100644
index 0000000..52ffcf4
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickIDs.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) 2017-2018 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
+
+// Analog sticks on the default controller
+#define DEFAULT_LEFT_STICK_NAME "leftstick"
+#define DEFAULT_RIGHT_STICK_NAME "rightstick"
diff --git a/xbmc/input/joysticks/JoystickMonitor.cpp b/xbmc/input/joysticks/JoystickMonitor.cpp
new file mode 100644
index 0000000..6375a41
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickMonitor.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015-2018 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 "JoystickMonitor.h"
+
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPowerHandling.h"
+#include "games/controllers/ControllerIDs.h"
+#include "input/InputManager.h"
+
+#include <cmath>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+#define AXIS_DEADZONE 0.05f
+
+std::string CJoystickMonitor::ControllerID() const
+{
+ return DEFAULT_CONTROLLER_ID;
+}
+
+bool CJoystickMonitor::AcceptsInput(const FeatureName& feature) const
+{
+ // Only accept input when screen saver is active
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ return appPower->IsInScreenSaver();
+}
+
+bool CJoystickMonitor::OnButtonPress(const FeatureName& feature, bool bPressed)
+{
+ if (bPressed)
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ return ResetTimers();
+ }
+
+ return false;
+}
+
+bool CJoystickMonitor::OnButtonMotion(const FeatureName& feature,
+ float magnitude,
+ unsigned int motionTimeMs)
+{
+ if (std::fabs(magnitude) > AXIS_DEADZONE)
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ return ResetTimers();
+ }
+
+ return false;
+}
+
+bool CJoystickMonitor::OnAnalogStickMotion(const FeatureName& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs)
+{
+ // Analog stick deadzone already processed
+ if (x != 0.0f || y != 0.0f)
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ return ResetTimers();
+ }
+
+ return false;
+}
+
+bool CJoystickMonitor::OnWheelMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ if (std::fabs(position) > AXIS_DEADZONE)
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ return ResetTimers();
+ }
+
+ return false;
+}
+
+bool CJoystickMonitor::OnThrottleMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ if (std::fabs(position) > AXIS_DEADZONE)
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ return ResetTimers();
+ }
+
+ return false;
+}
+
+bool CJoystickMonitor::ResetTimers(void)
+{
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ appPower->ResetSystemIdleTimer();
+ appPower->ResetScreenSaver();
+ return appPower->WakeUpScreenSaverAndDPMS();
+
+ return true;
+}
diff --git a/xbmc/input/joysticks/JoystickMonitor.h b/xbmc/input/joysticks/JoystickMonitor.h
new file mode 100644
index 0000000..783fff1
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickMonitor.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015-2018 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
+
+#include "input/joysticks/interfaces/IInputHandler.h"
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \ingroup joystick
+ * \brief Monitors joystick input and resets screensaver/shutdown timers
+ * whenever motion occurs.
+ */
+class CJoystickMonitor : public IInputHandler
+{
+public:
+ // implementation of IInputHandler
+ std::string ControllerID() const override;
+ bool HasFeature(const FeatureName& feature) const override { return true; }
+ bool AcceptsInput(const FeatureName& feature) const override;
+ bool OnButtonPress(const FeatureName& feature, bool bPressed) override;
+ void OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs) override {}
+ bool OnButtonMotion(const FeatureName& feature,
+ float magnitude,
+ unsigned int motionTimeMs) override;
+ bool OnAnalogStickMotion(const FeatureName& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs) override;
+ bool OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z) override
+ {
+ return false;
+ }
+ bool OnWheelMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ bool OnThrottleMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ void OnInputFrame() override {}
+
+private:
+ /*!
+ * \brief Reset screensaver and shutdown timers
+ * \return True if the application was woken from screensaver
+ */
+ bool ResetTimers(void);
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/JoystickTranslator.cpp b/xbmc/input/joysticks/JoystickTranslator.cpp
new file mode 100644
index 0000000..7edfeaf
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickTranslator.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015-2018 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 "JoystickTranslator.h"
+
+#include "guilib/LocalizeStrings.h"
+#include "input/joysticks/DriverPrimitive.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+const char* CJoystickTranslator::HatStateToString(HAT_STATE state)
+{
+ switch (state)
+ {
+ case HAT_STATE::UP:
+ return "UP";
+ case HAT_STATE::DOWN:
+ return "DOWN";
+ case HAT_STATE::RIGHT:
+ return "RIGHT";
+ case HAT_STATE::LEFT:
+ return "LEFT";
+ case HAT_STATE::RIGHTUP:
+ return "UP RIGHT";
+ case HAT_STATE::RIGHTDOWN:
+ return "DOWN RIGHT";
+ case HAT_STATE::LEFTUP:
+ return "UP LEFT";
+ case HAT_STATE::LEFTDOWN:
+ return "DOWN LEFT";
+ default:
+ break;
+ }
+
+ return "RELEASED";
+}
+
+const char* CJoystickTranslator::TranslateAnalogStickDirection(ANALOG_STICK_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case ANALOG_STICK_DIRECTION::UP:
+ return "up";
+ case ANALOG_STICK_DIRECTION::DOWN:
+ return "down";
+ case ANALOG_STICK_DIRECTION::RIGHT:
+ return "right";
+ case ANALOG_STICK_DIRECTION::LEFT:
+ return "left";
+ default:
+ break;
+ }
+
+ return "";
+}
+
+ANALOG_STICK_DIRECTION CJoystickTranslator::TranslateAnalogStickDirection(const std::string& dir)
+{
+ if (dir == "up")
+ return ANALOG_STICK_DIRECTION::UP;
+ if (dir == "down")
+ return ANALOG_STICK_DIRECTION::DOWN;
+ if (dir == "right")
+ return ANALOG_STICK_DIRECTION::RIGHT;
+ if (dir == "left")
+ return ANALOG_STICK_DIRECTION::LEFT;
+
+ return ANALOG_STICK_DIRECTION::NONE;
+}
+
+const char* CJoystickTranslator::TranslateWheelDirection(WHEEL_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case WHEEL_DIRECTION::RIGHT:
+ return "right";
+ case WHEEL_DIRECTION::LEFT:
+ return "left";
+ default:
+ break;
+ }
+
+ return "";
+}
+
+WHEEL_DIRECTION CJoystickTranslator::TranslateWheelDirection(const std::string& dir)
+{
+ if (dir == "right")
+ return WHEEL_DIRECTION::RIGHT;
+ if (dir == "left")
+ return WHEEL_DIRECTION::LEFT;
+
+ return WHEEL_DIRECTION::NONE;
+}
+
+const char* CJoystickTranslator::TranslateThrottleDirection(THROTTLE_DIRECTION dir)
+{
+ switch (dir)
+ {
+ case THROTTLE_DIRECTION::UP:
+ return "up";
+ case THROTTLE_DIRECTION::DOWN:
+ return "down";
+ default:
+ break;
+ }
+
+ return "";
+}
+
+THROTTLE_DIRECTION CJoystickTranslator::TranslateThrottleDirection(const std::string& dir)
+{
+ if (dir == "up")
+ return THROTTLE_DIRECTION::UP;
+ if (dir == "down")
+ return THROTTLE_DIRECTION::DOWN;
+
+ return THROTTLE_DIRECTION::NONE;
+}
+
+SEMIAXIS_DIRECTION CJoystickTranslator::PositionToSemiAxisDirection(float position)
+{
+ if (position > 0)
+ return SEMIAXIS_DIRECTION::POSITIVE;
+ else if (position < 0)
+ return SEMIAXIS_DIRECTION::NEGATIVE;
+
+ return SEMIAXIS_DIRECTION::ZERO;
+}
+
+WHEEL_DIRECTION CJoystickTranslator::PositionToWheelDirection(float position)
+{
+ if (position > 0.0f)
+ return WHEEL_DIRECTION::RIGHT;
+ else if (position < 0.0f)
+ return WHEEL_DIRECTION::LEFT;
+
+ return WHEEL_DIRECTION::NONE;
+}
+
+THROTTLE_DIRECTION CJoystickTranslator::PositionToThrottleDirection(float position)
+{
+ if (position > 0.0f)
+ return THROTTLE_DIRECTION::UP;
+ else if (position < 0.0f)
+ return THROTTLE_DIRECTION::DOWN;
+
+ return THROTTLE_DIRECTION::NONE;
+}
+
+std::string CJoystickTranslator::GetPrimitiveName(const CDriverPrimitive& primitive)
+{
+ std::string primitiveTemplate;
+
+ switch (primitive.Type())
+ {
+ case PRIMITIVE_TYPE::BUTTON:
+ primitiveTemplate = g_localizeStrings.Get(35015); // "Button %d"
+ break;
+ case PRIMITIVE_TYPE::SEMIAXIS:
+ primitiveTemplate = g_localizeStrings.Get(35016); // "Axis %d"
+ break;
+ default:
+ break;
+ }
+
+ return StringUtils::Format(primitiveTemplate, primitive.Index());
+}
diff --git a/xbmc/input/joysticks/JoystickTranslator.h b/xbmc/input/joysticks/JoystickTranslator.h
new file mode 100644
index 0000000..2efeacc
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickTranslator.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015-2018 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
+
+#include "JoystickTypes.h"
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CDriverPrimitive;
+
+/*!
+ * \brief Joystick translation utilities
+ */
+class CJoystickTranslator
+{
+public:
+ /*!
+ * \brief Translate a hat state to a string representation
+ *
+ * \param state The hat state
+ *
+ * \return A capitalized string representation, or "RELEASED" if the hat is centered.
+ */
+ static const char* HatStateToString(HAT_STATE state);
+
+ /*!
+ * \brief Translate an analog stick direction to a lower-case string
+ *
+ * \param dir The analog stick direction
+ *
+ * \return A lower-case string representation, or "" if the direction is invalid
+ */
+ static const char* TranslateAnalogStickDirection(ANALOG_STICK_DIRECTION dir);
+
+ /*!
+ * \brief Translate an analog stick direction string to an enum value
+ *
+ * \param dir The analog stick direction
+ *
+ * \return The translated direction, or ANALOG_STICK_DIRECTION::UNKNOWN if unknown
+ */
+ static ANALOG_STICK_DIRECTION TranslateAnalogStickDirection(const std::string& dir);
+
+ /*!
+ * \brief Translate a wheel direction to a lower-case string
+ *
+ * \param dir The wheel direction
+ *
+ * \return A lower-case string representation, or "" if the direction is invalid
+ */
+ static const char* TranslateWheelDirection(WHEEL_DIRECTION dir);
+
+ /*!
+ * \brief Translate a wheel direction string to an enum value
+ *
+ * \param dir The wheel direction
+ *
+ * \return The translated direction, or WHEEL_DIRECTION::UNKNOWN if unknown
+ */
+ static WHEEL_DIRECTION TranslateWheelDirection(const std::string& dir);
+
+ /*!
+ * \brief Translate a throttle direction to a lower-case string
+ *
+ * \param dir The analog stick direction
+ *
+ * \return A lower-case string representation, or "" if the direction is invalid
+ */
+ static const char* TranslateThrottleDirection(THROTTLE_DIRECTION dir);
+
+ /*!
+ * \brief Translate a throttle direction string to an enum value
+ *
+ * \param dir The throttle direction
+ *
+ * \return The translated direction, or THROTTLE_DIRECTION::UNKNOWN if unknown
+ */
+ static THROTTLE_DIRECTION TranslateThrottleDirection(const std::string& dir);
+
+ /*!
+ * \brief Get the semi-axis direction containing the specified position
+ *
+ * \param position The position of the axis
+ *
+ * \return POSITIVE, NEGATIVE, or UNKNOWN if position is 0
+ */
+ static SEMIAXIS_DIRECTION PositionToSemiAxisDirection(float position);
+
+ /*!
+ * \brief Get the wheel direction containing the specified position
+ *
+ * \param position The position of the axis
+ *
+ * \return LEFT, RIGHT, or UNKNOWN if position is 0
+ */
+ static WHEEL_DIRECTION PositionToWheelDirection(float position);
+
+ /*!
+ * \brief Get the throttle direction containing the specified position
+ *
+ * \param position The position of the axis
+ *
+ * \return UP, DOWN, or UNKNOWN if position is 0
+ */
+ static THROTTLE_DIRECTION PositionToThrottleDirection(float position);
+
+ /*!
+ * \brief Get the localized name of the primitive
+ *
+ * \param primitive The primitive, currently only buttons and axes are supported
+ *
+ * \return A title for the primitive, e.g. "Button 0" or "Axis 1"
+ */
+ static std::string GetPrimitiveName(const CDriverPrimitive& primitive);
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/JoystickTypes.h b/xbmc/input/joysticks/JoystickTypes.h
new file mode 100644
index 0000000..5925793
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickTypes.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+/*!
+ \file
+ \ingroup joystick
+ */
+
+#include "input/InputTypes.h"
+
+#include <set>
+#include <string>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \brief Name of a physical feature belonging to the joystick
+ */
+using FeatureName = std::string;
+
+/*!
+ * \brief Types of features used in the joystick library
+ *
+ * Available types:
+ *
+ * 1) scalar[*]
+ * 2) analog stick
+ * 3) accelerometer
+ * 4) rumble motor
+ * 5) relative pointer
+ * 6) absolute pointer
+ * 7) wheel
+ * 8) throttle
+ * 9) keyboard key
+ *
+ * [*] All three driver primitives (buttons, hats and axes) have a state that
+ * can be represented using a single scalar value. For this reason,
+ * features that map to a single primitive are called "scalar features".
+ */
+enum class FEATURE_TYPE
+{
+ UNKNOWN,
+ SCALAR,
+ ANALOG_STICK,
+ ACCELEROMETER,
+ MOTOR,
+ RELPOINTER,
+ ABSPOINTER,
+ WHEEL,
+ THROTTLE,
+ KEY,
+};
+
+/*!
+ * \brief Categories of features used in the joystick library
+ */
+enum class FEATURE_CATEGORY
+{
+ UNKNOWN,
+ FACE,
+ SHOULDER,
+ TRIGGER,
+ ANALOG_STICK,
+ ACCELEROMETER,
+ HAPTICS,
+ MOUSE_BUTTON,
+ POINTER,
+ LIGHTGUN,
+ OFFSCREEN, // Virtual button to shoot light gun offscreen
+ KEY, // A keyboard key
+ KEYPAD, // A key on a numeric keymap, including star and pound
+ HARDWARE, // A button or functionality on the console
+ WHEEL,
+ JOYSTICK,
+ PADDLE,
+};
+
+/*!
+ * \brief Direction arrows on the hat (directional pad)
+ */
+using HAT_DIRECTION = INPUT::CARDINAL_DIRECTION;
+
+/*!
+ * \brief States in which a hat can be
+ */
+using HAT_STATE = INPUT::INTERCARDINAL_DIRECTION;
+
+/*!
+ * \brief Typedef for analog stick directions
+ */
+using ANALOG_STICK_DIRECTION = INPUT::CARDINAL_DIRECTION;
+
+/*!
+ * \brief Directions of motion for a relative pointer
+ */
+using RELATIVE_POINTER_DIRECTION = INPUT::CARDINAL_DIRECTION;
+
+/*!
+ * \brief Directions in which a semiaxis can point
+ */
+enum class SEMIAXIS_DIRECTION
+{
+ NEGATIVE = -1, // semiaxis lies in the interval [-1.0, 0.0]
+ ZERO = 0, // semiaxis is unknown or invalid
+ POSITIVE = 1, // semiaxis lies in the interval [0.0, 1.0]
+};
+
+/*!
+ * \brief Directions on a wheel
+ */
+enum class WHEEL_DIRECTION
+{
+ NONE,
+ RIGHT,
+ LEFT,
+};
+
+/*!
+ * \brief Directions on a throttle
+ */
+enum class THROTTLE_DIRECTION
+{
+ NONE,
+ UP,
+ DOWN,
+};
+
+/*!
+ * \brief Types of input available for scalar features
+ */
+enum class INPUT_TYPE
+{
+ UNKNOWN,
+ DIGITAL,
+ ANALOG,
+};
+
+/*!
+ * \brief Type of driver primitive
+ */
+enum class PRIMITIVE_TYPE
+{
+ UNKNOWN = 0, // primitive has no type (invalid)
+ BUTTON, // a digital button
+ HAT, // one of the four direction arrows on a D-pad
+ SEMIAXIS, // the positive or negative half of an axis
+ MOTOR, // a rumble motor
+ KEY, // a keyboard key
+ MOUSE_BUTTON, // a mouse button
+ RELATIVE_POINTER, // a relative pointer, such as on a mouse
+};
+
+/*!
+ * \ingroup joystick
+ * \brief Action entry in joystick.xml
+ */
+struct KeymapAction
+{
+ unsigned int actionId;
+ std::string actionString;
+ unsigned int holdTimeMs;
+ std::set<std::string> hotkeys;
+
+ bool operator<(const KeymapAction& rhs) const { return holdTimeMs < rhs.holdTimeMs; }
+};
+
+/*!
+ * \ingroup joystick
+ * \brief Container that sorts action entries by their holdtime
+ */
+struct KeymapActionGroup
+{
+ int windowId = -1;
+ std::set<KeymapAction> actions;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/JoystickUtils.cpp b/xbmc/input/joysticks/JoystickUtils.cpp
new file mode 100644
index 0000000..3c25121
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickUtils.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017-2018 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 "JoystickUtils.h"
+
+#include "JoystickTranslator.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+std::string CJoystickUtils::MakeKeyName(const FeatureName& feature)
+{
+ return feature;
+}
+
+std::string CJoystickUtils::MakeKeyName(const FeatureName& feature, ANALOG_STICK_DIRECTION dir)
+{
+ std::string keyName = feature;
+
+ if (dir != ANALOG_STICK_DIRECTION::NONE)
+ keyName += CJoystickTranslator::TranslateAnalogStickDirection(dir);
+
+ return keyName;
+}
+
+std::string CJoystickUtils::MakeKeyName(const FeatureName& feature, WHEEL_DIRECTION dir)
+{
+ ANALOG_STICK_DIRECTION stickDir = ANALOG_STICK_DIRECTION::NONE;
+
+ switch (dir)
+ {
+ case WHEEL_DIRECTION::LEFT:
+ stickDir = ANALOG_STICK_DIRECTION::LEFT;
+ break;
+ case WHEEL_DIRECTION::RIGHT:
+ stickDir = ANALOG_STICK_DIRECTION::RIGHT;
+ break;
+ default:
+ break;
+ }
+
+ return MakeKeyName(feature, stickDir);
+}
+
+std::string CJoystickUtils::MakeKeyName(const FeatureName& feature, THROTTLE_DIRECTION dir)
+{
+ ANALOG_STICK_DIRECTION stickDir = ANALOG_STICK_DIRECTION::NONE;
+
+ switch (dir)
+ {
+ case THROTTLE_DIRECTION::UP:
+ stickDir = ANALOG_STICK_DIRECTION::UP;
+ break;
+ case THROTTLE_DIRECTION::DOWN:
+ stickDir = ANALOG_STICK_DIRECTION::DOWN;
+ break;
+ default:
+ break;
+ }
+
+ return MakeKeyName(feature, stickDir);
+}
+
+const std::vector<ANALOG_STICK_DIRECTION>& CJoystickUtils::GetAnalogStickDirections()
+{
+ static std::vector<ANALOG_STICK_DIRECTION> directions;
+ if (directions.empty())
+ {
+ directions.push_back(ANALOG_STICK_DIRECTION::UP);
+ directions.push_back(ANALOG_STICK_DIRECTION::DOWN);
+ directions.push_back(ANALOG_STICK_DIRECTION::RIGHT);
+ directions.push_back(ANALOG_STICK_DIRECTION::LEFT);
+ }
+ return directions;
+}
+
+const std::vector<WHEEL_DIRECTION>& CJoystickUtils::GetWheelDirections()
+{
+ static std::vector<WHEEL_DIRECTION> directions;
+ if (directions.empty())
+ {
+ directions.push_back(WHEEL_DIRECTION::RIGHT);
+ directions.push_back(WHEEL_DIRECTION::LEFT);
+ }
+ return directions;
+}
+
+const std::vector<THROTTLE_DIRECTION>& CJoystickUtils::GetThrottleDirections()
+{
+ static std::vector<THROTTLE_DIRECTION> directions;
+ if (directions.empty())
+ {
+ directions.push_back(THROTTLE_DIRECTION::UP);
+ directions.push_back(THROTTLE_DIRECTION::DOWN);
+ }
+ return directions;
+}
diff --git a/xbmc/input/joysticks/JoystickUtils.h b/xbmc/input/joysticks/JoystickUtils.h
new file mode 100644
index 0000000..49095d2
--- /dev/null
+++ b/xbmc/input/joysticks/JoystickUtils.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "JoystickTypes.h"
+
+#include <string>
+#include <vector>
+
+/// \ingroup joystick
+/// \{
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+
+inline HAT_DIRECTION& operator|=(HAT_DIRECTION& lhs, HAT_DIRECTION rhs)
+{
+ return lhs = static_cast<HAT_DIRECTION>(static_cast<int>(lhs) | static_cast<int>(rhs));
+}
+
+inline HAT_STATE& operator|=(HAT_STATE& lhs, HAT_STATE rhs)
+{
+ return lhs = static_cast<HAT_STATE>(static_cast<int>(lhs) | static_cast<int>(rhs));
+}
+
+inline bool operator&(HAT_STATE lhs, HAT_DIRECTION rhs)
+{
+ return (static_cast<int>(lhs) & static_cast<int>(rhs)) ? true : false;
+}
+
+inline SEMIAXIS_DIRECTION operator*(SEMIAXIS_DIRECTION lhs, int rhs)
+{
+ return static_cast<SEMIAXIS_DIRECTION>(static_cast<int>(lhs) * rhs);
+}
+
+inline float operator*(float lhs, SEMIAXIS_DIRECTION rhs)
+{
+ return lhs * static_cast<int>(rhs);
+}
+
+class CJoystickUtils
+{
+public:
+ /*!
+ * \brief Create a key name used to index an action in the keymap
+ *
+ * \param feature The feature name
+ *
+ * \return A valid name for a key in the joystick keymap
+ */
+ static std::string MakeKeyName(const FeatureName& feature);
+
+ /*!
+ * \brief Create a key name used to index an action in the keymap
+ *
+ * \param feature The feature name
+ * \param dir The direction for analog sticks
+ *
+ * \return A valid name for a key in the joystick keymap
+ */
+ static std::string MakeKeyName(const FeatureName& feature, ANALOG_STICK_DIRECTION dir);
+
+ /*!
+ * \brief Create a key name used to index an action in the keymap
+ *
+ * \param feature The feature name
+ * \param dir The direction for a wheel to turn
+ *
+ * \return A valid name for a key in the joystick keymap
+ */
+ static std::string MakeKeyName(const FeatureName& feature, WHEEL_DIRECTION dir);
+
+ /*!
+ * \brief Create a key name used to index an action in the keymap
+ *
+ * \param feature The feature name
+ * \param dir The direction for a throttle to move
+ *
+ * \return A valid name for a key in the joystick keymap
+ */
+ static std::string MakeKeyName(const FeatureName& feature, THROTTLE_DIRECTION dir);
+
+ /*!
+ * \brief Return a vector of the four cardinal directions
+ */
+ static const std::vector<ANALOG_STICK_DIRECTION>& GetAnalogStickDirections();
+
+ /*!
+ * \brief Return a vector of the two wheel directions
+ */
+ static const std::vector<WHEEL_DIRECTION>& GetWheelDirections();
+
+ /*!
+ * \brief Return a vector of the two throttle directions
+ */
+ static const std::vector<THROTTLE_DIRECTION>& GetThrottleDirections();
+};
+
+} // namespace JOYSTICK
+} // namespace KODI
+
+/// \}
diff --git a/xbmc/input/joysticks/RumbleGenerator.cpp b/xbmc/input/joysticks/RumbleGenerator.cpp
new file mode 100644
index 0000000..db60cfa
--- /dev/null
+++ b/xbmc/input/joysticks/RumbleGenerator.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016-2018 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 "RumbleGenerator.h"
+
+#include "ServiceBroker.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerIDs.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/joysticks/interfaces/IInputReceiver.h"
+
+#include <algorithm>
+
+using namespace std::chrono_literals;
+
+namespace
+{
+constexpr auto RUMBLE_TEST_DURATION_MS = 1000ms; // Per motor
+constexpr auto RUMBLE_NOTIFICATION_DURATION_MS = 300ms;
+
+// From game.controller.default profile
+#define WEAK_MOTOR_NAME "rightmotor"
+} // namespace
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+CRumbleGenerator::CRumbleGenerator()
+ : CThread("RumbleGenerator"), m_motors(GetMotors(ControllerID()))
+{
+}
+
+std::string CRumbleGenerator::ControllerID() const
+{
+ return DEFAULT_CONTROLLER_ID;
+}
+
+void CRumbleGenerator::NotifyUser(IInputReceiver* receiver)
+{
+ if (receiver && !m_motors.empty())
+ {
+ if (IsRunning())
+ StopThread(true);
+
+ m_receiver = receiver;
+ m_type = RUMBLE_NOTIFICATION;
+ Create();
+ }
+}
+
+bool CRumbleGenerator::DoTest(IInputReceiver* receiver)
+{
+ if (receiver && !m_motors.empty())
+ {
+ if (IsRunning())
+ StopThread(true);
+
+ m_receiver = receiver;
+ m_type = RUMBLE_TEST;
+ Create();
+
+ return true;
+ }
+ return false;
+}
+
+void CRumbleGenerator::Process(void)
+{
+ switch (m_type)
+ {
+ case RUMBLE_NOTIFICATION:
+ {
+ std::vector<std::string> motors;
+
+ if (std::find(m_motors.begin(), m_motors.end(), WEAK_MOTOR_NAME) != m_motors.end())
+ motors.emplace_back(WEAK_MOTOR_NAME);
+ else
+ motors = m_motors; // Not using default profile? Just rumble all motors
+
+ for (const std::string& motor : motors)
+ m_receiver->SetRumbleState(motor, 1.0f);
+
+ CThread::Sleep(RUMBLE_NOTIFICATION_DURATION_MS);
+
+ if (m_bStop)
+ break;
+
+ for (const std::string& motor : motors)
+ m_receiver->SetRumbleState(motor, 0.0f);
+
+ break;
+ }
+ case RUMBLE_TEST:
+ {
+ for (const std::string& motor : m_motors)
+ {
+ m_receiver->SetRumbleState(motor, 1.0f);
+
+ CThread::Sleep(RUMBLE_TEST_DURATION_MS);
+
+ if (m_bStop)
+ break;
+
+ m_receiver->SetRumbleState(motor, 0.0f);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+std::vector<std::string> CRumbleGenerator::GetMotors(const std::string& controllerId)
+{
+ using namespace GAME;
+
+ std::vector<std::string> motors;
+
+ CControllerManager& controllerManager = CServiceBroker::GetGameControllerManager();
+ ControllerPtr controller = controllerManager.GetController(controllerId);
+ if (controller)
+ controller->GetFeatures(motors, FEATURE_TYPE::MOTOR);
+
+ return motors;
+}
diff --git a/xbmc/input/joysticks/RumbleGenerator.h b/xbmc/input/joysticks/RumbleGenerator.h
new file mode 100644
index 0000000..a6e9853
--- /dev/null
+++ b/xbmc/input/joysticks/RumbleGenerator.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016-2018 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
+
+#include "threads/Thread.h"
+
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IInputReceiver;
+
+class CRumbleGenerator : public CThread
+{
+public:
+ CRumbleGenerator();
+
+ ~CRumbleGenerator() override { AbortRumble(); }
+
+ std::string ControllerID() const;
+
+ void NotifyUser(IInputReceiver* receiver);
+ bool DoTest(IInputReceiver* receiver);
+
+ void AbortRumble(void) { StopThread(); }
+
+protected:
+ // implementation of CThread
+ void Process() override;
+
+private:
+ enum RUMBLE_TYPE
+ {
+ RUMBLE_UNKNOWN,
+ RUMBLE_NOTIFICATION,
+ RUMBLE_TEST,
+ };
+
+ static std::vector<std::string> GetMotors(const std::string& controllerId);
+
+ // Construction param
+ const std::vector<std::string> m_motors;
+
+ // Test param
+ IInputReceiver* m_receiver = nullptr;
+ RUMBLE_TYPE m_type = RUMBLE_UNKNOWN;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/dialogs/CMakeLists.txt b/xbmc/input/joysticks/dialogs/CMakeLists.txt
new file mode 100644
index 0000000..73adcec
--- /dev/null
+++ b/xbmc/input/joysticks/dialogs/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SOURCES GUIDialogNewJoystick.cpp)
+
+set(HEADERS GUIDialogNewJoystick.h)
+
+core_add_library(input_joystick_dialogs)
diff --git a/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.cpp b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.cpp
new file mode 100644
index 0000000..b66ce89
--- /dev/null
+++ b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016-2018 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 "GUIDialogNewJoystick.h"
+
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+CGUIDialogNewJoystick::CGUIDialogNewJoystick() : CThread("NewJoystickDlg")
+{
+}
+
+void CGUIDialogNewJoystick::ShowAsync()
+{
+ bool bShow = true;
+
+ if (IsRunning())
+ bShow = false;
+ else if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_INPUT_ASKNEWCONTROLLERS))
+ bShow = false;
+ else if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(
+ WINDOW_DIALOG_GAME_CONTROLLERS, false))
+ bShow = false;
+
+ if (bShow)
+ Create();
+}
+
+void CGUIDialogNewJoystick::Process()
+{
+ using namespace MESSAGING::HELPERS;
+
+ // "New controller detected"
+ // "A new controller has been detected. Configuration can be done at any time in "Settings ->
+ // System Settings -> Input". Would you like to configure it now?"
+ if (ShowYesNoDialogText(CVariant{35011}, CVariant{35012}) == DialogResponse::CHOICE_YES)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_GAME_CONTROLLERS);
+ }
+ else
+ {
+ CServiceBroker::GetSettingsComponent()->GetSettings()->SetBool(
+ CSettings::SETTING_INPUT_ASKNEWCONTROLLERS, false);
+ }
+}
diff --git a/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.h b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.h
new file mode 100644
index 0000000..334191f
--- /dev/null
+++ b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016-2018 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
+
+#include "threads/Thread.h"
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CGUIDialogNewJoystick : protected CThread
+{
+public:
+ CGUIDialogNewJoystick();
+ ~CGUIDialogNewJoystick() override = default;
+
+ void ShowAsync();
+
+protected:
+ // implementation of CThread
+ void Process() override;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/generic/ButtonMapping.cpp b/xbmc/input/joysticks/generic/ButtonMapping.cpp
new file mode 100644
index 0000000..665a233
--- /dev/null
+++ b/xbmc/input/joysticks/generic/ButtonMapping.cpp
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2014-2018 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 "ButtonMapping.h"
+
+#include "ServiceBroker.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "games/controllers/input/PhysicalFeature.h"
+#include "input/IKeymap.h"
+#include "input/InputTranslator.h"
+#include "input/Key.h"
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/JoystickTranslator.h"
+#include "input/joysticks/JoystickUtils.h"
+#include "input/joysticks/interfaces/IButtonMap.h"
+#include "input/joysticks/interfaces/IButtonMapper.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <cmath>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+#define MAPPING_COOLDOWN_MS 50 // Guard against repeated input
+#define AXIS_THRESHOLD 0.75f // Axis must exceed this value to be mapped
+#define TRIGGER_DELAY_MS \
+ 200 // Delay trigger detection to handle anomalous triggers with non-zero center
+
+// --- CPrimitiveDetector ------------------------------------------------------
+
+CPrimitiveDetector::CPrimitiveDetector(CButtonMapping* buttonMapping)
+ : m_buttonMapping(buttonMapping)
+{
+}
+
+bool CPrimitiveDetector::MapPrimitive(const CDriverPrimitive& primitive)
+{
+ if (primitive.IsValid())
+ return m_buttonMapping->MapPrimitive(primitive);
+
+ return false;
+}
+
+// --- CButtonDetector ---------------------------------------------------------
+
+CButtonDetector::CButtonDetector(CButtonMapping* buttonMapping, unsigned int buttonIndex)
+ : CPrimitiveDetector(buttonMapping), m_buttonIndex(buttonIndex)
+{
+}
+
+bool CButtonDetector::OnMotion(bool bPressed)
+{
+ if (bPressed)
+ return MapPrimitive(CDriverPrimitive(PRIMITIVE_TYPE::BUTTON, m_buttonIndex));
+
+ return false;
+}
+
+// --- CHatDetector ------------------------------------------------------------
+
+CHatDetector::CHatDetector(CButtonMapping* buttonMapping, unsigned int hatIndex)
+ : CPrimitiveDetector(buttonMapping), m_hatIndex(hatIndex)
+{
+}
+
+bool CHatDetector::OnMotion(HAT_STATE state)
+{
+ return MapPrimitive(CDriverPrimitive(m_hatIndex, static_cast<HAT_DIRECTION>(state)));
+}
+
+// --- CAxisDetector -----------------------------------------------------------
+
+CAxisDetector::CAxisDetector(CButtonMapping* buttonMapping,
+ unsigned int axisIndex,
+ const AxisConfiguration& config)
+ : CPrimitiveDetector(buttonMapping),
+ m_axisIndex(axisIndex),
+ m_config(config),
+ m_state(AXIS_STATE::INACTIVE),
+ m_type(AXIS_TYPE::UNKNOWN),
+ m_initialPositionKnown(false),
+ m_initialPosition(0.0f),
+ m_initialPositionChanged(false)
+{
+}
+
+bool CAxisDetector::OnMotion(float position)
+{
+ DetectType(position);
+
+ if (m_type != AXIS_TYPE::UNKNOWN)
+ {
+ // Update position if this axis is an anomalous trigger
+ if (m_type == AXIS_TYPE::OFFSET)
+ position = (position - m_config.center) / m_config.range;
+
+ // Reset state if position crosses zero
+ if (m_state == AXIS_STATE::MAPPED)
+ {
+ SEMIAXIS_DIRECTION activatedDir = m_activatedPrimitive.SemiAxisDirection();
+ SEMIAXIS_DIRECTION newDir = CJoystickTranslator::PositionToSemiAxisDirection(position);
+
+ if (activatedDir != newDir)
+ m_state = AXIS_STATE::INACTIVE;
+ }
+
+ // Check if axis has become activated
+ if (m_state == AXIS_STATE::INACTIVE)
+ {
+ if (std::abs(position) >= AXIS_THRESHOLD)
+ m_state = AXIS_STATE::ACTIVATED;
+
+ if (m_state == AXIS_STATE::ACTIVATED)
+ {
+ // Range is set later for anomalous triggers
+ m_activatedPrimitive =
+ CDriverPrimitive(m_axisIndex, m_config.center,
+ CJoystickTranslator::PositionToSemiAxisDirection(position), 1);
+ m_activationTimeMs = std::chrono::steady_clock::now();
+ }
+ }
+ }
+
+ return true;
+}
+
+void CAxisDetector::ProcessMotion()
+{
+ // Process newly-activated axis
+ if (m_state == AXIS_STATE::ACTIVATED)
+ {
+ // Ignore anomalous triggers for a bit so we can detect the full range
+ bool bIgnore = false;
+ if (m_type == AXIS_TYPE::OFFSET)
+ {
+ auto now = std::chrono::steady_clock::now();
+ auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - m_activationTimeMs);
+
+ if (duration.count() < TRIGGER_DELAY_MS)
+ bIgnore = true;
+ }
+
+ if (!bIgnore)
+ {
+ // Update driver primitive's range if we're mapping an anomalous trigger
+ if (m_type == AXIS_TYPE::OFFSET)
+ {
+ m_activatedPrimitive =
+ CDriverPrimitive(m_activatedPrimitive.Index(), m_activatedPrimitive.Center(),
+ m_activatedPrimitive.SemiAxisDirection(), m_config.range);
+ }
+
+ // Map primitive
+ if (!MapPrimitive(m_activatedPrimitive))
+ {
+ if (m_type == AXIS_TYPE::OFFSET)
+ CLog::Log(LOGDEBUG, "Mapping offset axis {} failed", m_axisIndex);
+ else
+ CLog::Log(LOGDEBUG, "Mapping normal axis {} failed", m_axisIndex);
+ }
+
+ m_state = AXIS_STATE::MAPPED;
+ }
+ }
+}
+
+void CAxisDetector::SetEmitted(const CDriverPrimitive& activePrimitive)
+{
+ m_state = AXIS_STATE::MAPPED;
+ m_activatedPrimitive = activePrimitive;
+}
+
+void CAxisDetector::DetectType(float position)
+{
+ // Some platforms don't report a value until the axis is first changed.
+ // Detection relies on an initial value, so this axis will be disabled until
+ // the user begins button mapping again.
+ if (m_config.bLateDiscovery)
+ return;
+
+ // Update range if a range of > 1 is observed
+ if (std::abs(position - m_config.center) > 1.0f)
+ m_config.range = 2;
+
+ if (m_type != AXIS_TYPE::UNKNOWN)
+ return;
+
+ if (m_config.bKnown)
+ {
+ if (m_config.center == 0)
+ m_type = AXIS_TYPE::NORMAL;
+ else
+ m_type = AXIS_TYPE::OFFSET;
+ }
+
+ if (m_type != AXIS_TYPE::UNKNOWN)
+ return;
+
+ if (!m_initialPositionKnown)
+ {
+ m_initialPositionKnown = true;
+ m_initialPosition = position;
+ }
+
+ if (position != m_initialPosition)
+ m_initialPositionChanged = true;
+
+ if (m_initialPositionChanged)
+ {
+ // Calculate center based on initial position.
+ if (m_initialPosition < -0.5f)
+ {
+ m_config.center = -1;
+ m_type = AXIS_TYPE::OFFSET;
+ CLog::Log(LOGDEBUG, "Anomalous trigger detected on axis {} with center {}", m_axisIndex,
+ m_config.center);
+ }
+ else if (m_initialPosition > 0.5f)
+ {
+ m_config.center = 1;
+ m_type = AXIS_TYPE::OFFSET;
+ CLog::Log(LOGDEBUG, "Anomalous trigger detected on axis {} with center {}", m_axisIndex,
+ m_config.center);
+ }
+ else
+ {
+ m_type = AXIS_TYPE::NORMAL;
+ CLog::Log(LOGDEBUG, "Normal axis detected on axis {}", m_axisIndex);
+ }
+ }
+}
+
+// --- CKeyDetector ---------------------------------------------------------
+
+CKeyDetector::CKeyDetector(CButtonMapping* buttonMapping, XBMCKey keycode)
+ : CPrimitiveDetector(buttonMapping), m_keycode(keycode)
+{
+}
+
+bool CKeyDetector::OnMotion(bool bPressed)
+{
+ if (bPressed)
+ return MapPrimitive(CDriverPrimitive(m_keycode));
+
+ return false;
+}
+
+// --- CMouseButtonDetector ----------------------------------------------------
+
+CMouseButtonDetector::CMouseButtonDetector(CButtonMapping* buttonMapping,
+ MOUSE::BUTTON_ID buttonIndex)
+ : CPrimitiveDetector(buttonMapping), m_buttonIndex(buttonIndex)
+{
+}
+
+bool CMouseButtonDetector::OnMotion(bool bPressed)
+{
+ if (bPressed)
+ return MapPrimitive(CDriverPrimitive(m_buttonIndex));
+
+ return false;
+}
+
+// --- CPointerDetector --------------------------------------------------------
+
+CPointerDetector::CPointerDetector(CButtonMapping* buttonMapping)
+ : CPrimitiveDetector(buttonMapping)
+{
+}
+
+bool CPointerDetector::OnMotion(int x, int y)
+{
+ if (!m_bStarted)
+ {
+ m_bStarted = true;
+ m_startX = x;
+ m_startY = y;
+ m_frameCount = 0;
+ }
+
+ if (m_frameCount++ >= MIN_FRAME_COUNT)
+ {
+ int dx = x - m_startX;
+ int dy = y - m_startY;
+
+ INPUT::INTERCARDINAL_DIRECTION dir = GetPointerDirection(dx, dy);
+
+ CDriverPrimitive primitive(static_cast<RELATIVE_POINTER_DIRECTION>(dir));
+ if (primitive.IsValid())
+ {
+ if (MapPrimitive(primitive))
+ m_bStarted = false;
+ }
+ }
+
+ return true;
+}
+
+KODI::INPUT::INTERCARDINAL_DIRECTION CPointerDetector::GetPointerDirection(int x, int y)
+{
+ using namespace INPUT;
+
+ // Translate from left-handed coordinate system to right-handed coordinate system
+ y *= -1;
+
+ return CInputTranslator::VectorToIntercardinalDirection(static_cast<float>(x),
+ static_cast<float>(y));
+}
+
+// --- CButtonMapping ----------------------------------------------------------
+
+CButtonMapping::CButtonMapping(IButtonMapper* buttonMapper, IButtonMap* buttonMap, IKeymap* keymap)
+ : m_buttonMapper(buttonMapper), m_buttonMap(buttonMap), m_keymap(keymap), m_frameCount(0)
+{
+ assert(m_buttonMapper != nullptr);
+ assert(m_buttonMap != nullptr);
+
+ // Make sure axes mapped to Select are centered before they can be mapped.
+ // This ensures that they are not immediately mapped to the first button.
+ if (m_keymap)
+ {
+ using namespace GAME;
+
+ CControllerManager& controllerManager = CServiceBroker::GetGameControllerManager();
+ ControllerPtr controller = controllerManager.GetController(m_keymap->ControllerID());
+
+ const auto& features = controller->Features();
+ for (const auto& feature : features)
+ {
+ bool bIsSelectAction = false;
+
+ const auto& actions =
+ m_keymap->GetActions(CJoystickUtils::MakeKeyName(feature.Name())).actions;
+ if (!actions.empty() && actions.begin()->actionId == ACTION_SELECT_ITEM)
+ bIsSelectAction = true;
+
+ if (!bIsSelectAction)
+ continue;
+
+ CDriverPrimitive primitive;
+ if (!m_buttonMap->GetScalar(feature.Name(), primitive))
+ continue;
+
+ if (primitive.Type() != PRIMITIVE_TYPE::SEMIAXIS)
+ continue;
+
+ // Set initial config, as detection will fail because axis is already activated
+ AxisConfiguration axisConfig;
+ axisConfig.bKnown = true;
+ axisConfig.center = primitive.Center();
+ axisConfig.range = primitive.Range();
+
+ GetAxis(primitive.Index(), static_cast<float>(primitive.Center()), axisConfig)
+ .SetEmitted(primitive);
+ }
+ }
+}
+
+bool CButtonMapping::OnButtonMotion(unsigned int buttonIndex, bool bPressed)
+{
+ if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::BUTTON))
+ return false;
+
+ return GetButton(buttonIndex).OnMotion(bPressed);
+}
+
+bool CButtonMapping::OnHatMotion(unsigned int hatIndex, HAT_STATE state)
+{
+ if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::HAT))
+ return false;
+
+ return GetHat(hatIndex).OnMotion(state);
+}
+
+bool CButtonMapping::OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range)
+{
+ if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::SEMIAXIS))
+ return false;
+
+ return GetAxis(axisIndex, position).OnMotion(position);
+}
+
+void CButtonMapping::OnInputFrame(void)
+{
+ for (auto& axis : m_axes)
+ axis.second.ProcessMotion();
+
+ m_buttonMapper->OnEventFrame(m_buttonMap, IsMapping());
+
+ m_frameCount++;
+}
+
+bool CButtonMapping::OnKeyPress(const CKey& key)
+{
+ if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::KEY))
+ return false;
+
+ return GetKey(static_cast<XBMCKey>(key.GetKeycode())).OnMotion(true);
+}
+
+bool CButtonMapping::OnPosition(int x, int y)
+{
+ if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::RELATIVE_POINTER))
+ return false;
+
+ return GetPointer().OnMotion(x, y);
+}
+
+bool CButtonMapping::OnButtonPress(MOUSE::BUTTON_ID button)
+{
+ if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::MOUSE_BUTTON))
+ return false;
+
+ return GetMouseButton(button).OnMotion(true);
+}
+
+void CButtonMapping::OnButtonRelease(MOUSE::BUTTON_ID button)
+{
+ if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::MOUSE_BUTTON))
+ return;
+
+ GetMouseButton(button).OnMotion(false);
+}
+
+void CButtonMapping::SaveButtonMap()
+{
+ m_buttonMap->SaveButtonMap();
+}
+
+void CButtonMapping::ResetIgnoredPrimitives()
+{
+ std::vector<CDriverPrimitive> empty;
+ m_buttonMap->SetIgnoredPrimitives(empty);
+}
+
+void CButtonMapping::RevertButtonMap()
+{
+ m_buttonMap->RevertButtonMap();
+}
+
+bool CButtonMapping::MapPrimitive(const CDriverPrimitive& primitive)
+{
+ bool bHandled = false;
+
+ if (m_buttonMap->IsIgnored(primitive))
+ {
+ bHandled = true;
+ }
+ else
+ {
+ auto now = std::chrono::steady_clock::now();
+
+ bool bTimeoutElapsed = true;
+
+ if (m_buttonMapper->NeedsCooldown())
+ bTimeoutElapsed = (now >= m_lastAction + std::chrono::milliseconds(MAPPING_COOLDOWN_MS));
+
+ if (bTimeoutElapsed)
+ {
+ bHandled = m_buttonMapper->MapPrimitive(m_buttonMap, m_keymap, primitive);
+
+ if (bHandled)
+ m_lastAction = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastAction);
+
+ CLog::Log(LOGDEBUG, "Button mapping: rapid input after {}ms dropped for profile \"{}\"",
+ duration.count(), m_buttonMapper->ControllerID());
+ bHandled = true;
+ }
+ }
+
+ return bHandled;
+}
+
+bool CButtonMapping::IsMapping() const
+{
+ for (auto itAxis : m_axes)
+ {
+ if (itAxis.second.IsMapping())
+ return true;
+ }
+
+ return false;
+}
+
+CButtonDetector& CButtonMapping::GetButton(unsigned int buttonIndex)
+{
+ auto itButton = m_buttons.find(buttonIndex);
+
+ if (itButton == m_buttons.end())
+ {
+ m_buttons.insert(std::make_pair(buttonIndex, CButtonDetector(this, buttonIndex)));
+ itButton = m_buttons.find(buttonIndex);
+ }
+
+ return itButton->second;
+}
+
+CHatDetector& CButtonMapping::GetHat(unsigned int hatIndex)
+{
+ auto itHat = m_hats.find(hatIndex);
+
+ if (itHat == m_hats.end())
+ {
+ m_hats.insert(std::make_pair(hatIndex, CHatDetector(this, hatIndex)));
+ itHat = m_hats.find(hatIndex);
+ }
+
+ return itHat->second;
+}
+
+CAxisDetector& CButtonMapping::GetAxis(
+ unsigned int axisIndex,
+ float position,
+ const AxisConfiguration& initialConfig /* = AxisConfiguration() */)
+{
+ auto itAxis = m_axes.find(axisIndex);
+
+ if (itAxis == m_axes.end())
+ {
+ AxisConfiguration config(initialConfig);
+
+ if (m_frameCount >= 2)
+ {
+ config.bLateDiscovery = true;
+ OnLateDiscovery(axisIndex);
+ }
+
+ // Report axis
+ CLog::Log(LOGDEBUG, "Axis {} discovered at position {:.4f} after {} frames", axisIndex,
+ position, static_cast<unsigned long>(m_frameCount));
+
+ m_axes.insert(std::make_pair(axisIndex, CAxisDetector(this, axisIndex, config)));
+ itAxis = m_axes.find(axisIndex);
+ }
+
+ return itAxis->second;
+}
+
+CKeyDetector& CButtonMapping::GetKey(XBMCKey keycode)
+{
+ auto itKey = m_keys.find(keycode);
+
+ if (itKey == m_keys.end())
+ {
+ m_keys.insert(std::make_pair(keycode, CKeyDetector(this, keycode)));
+ itKey = m_keys.find(keycode);
+ }
+
+ return itKey->second;
+}
+
+CMouseButtonDetector& CButtonMapping::GetMouseButton(MOUSE::BUTTON_ID buttonIndex)
+{
+ auto itButton = m_mouseButtons.find(buttonIndex);
+
+ if (itButton == m_mouseButtons.end())
+ {
+ m_mouseButtons.insert(std::make_pair(buttonIndex, CMouseButtonDetector(this, buttonIndex)));
+ itButton = m_mouseButtons.find(buttonIndex);
+ }
+
+ return itButton->second;
+}
+
+CPointerDetector& CButtonMapping::GetPointer()
+{
+ if (!m_pointer)
+ m_pointer.reset(new CPointerDetector(this));
+
+ return *m_pointer;
+}
+
+void CButtonMapping::OnLateDiscovery(unsigned int axisIndex)
+{
+ m_buttonMapper->OnLateAxis(m_buttonMap, axisIndex);
+}
diff --git a/xbmc/input/joysticks/generic/ButtonMapping.h b/xbmc/input/joysticks/generic/ButtonMapping.h
new file mode 100644
index 0000000..3f76767
--- /dev/null
+++ b/xbmc/input/joysticks/generic/ButtonMapping.h
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/interfaces/IButtonMapCallback.h"
+#include "input/joysticks/interfaces/IDriverHandler.h"
+#include "input/keyboard/interfaces/IKeyboardDriverHandler.h"
+#include "input/mouse/MouseTypes.h"
+#include "input/mouse/interfaces/IMouseDriverHandler.h"
+
+#include <chrono>
+#include <map>
+#include <memory>
+#include <stdint.h>
+
+class IKeymap;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CButtonMapping;
+class IButtonMap;
+class IButtonMapper;
+
+/*!
+ * \brief Detects and dispatches mapping events
+ *
+ * A mapping event usually occurs when a driver primitive is pressed or
+ * exceeds a certain threshold.
+ *
+ * Detection can be quite complicated due to driver bugs, so each type of
+ * driver primitive is given its own detector class inheriting from this one.
+ */
+class CPrimitiveDetector
+{
+protected:
+ CPrimitiveDetector(CButtonMapping* buttonMapping);
+
+ /*!
+ * \brief Dispatch a mapping event
+ *
+ * \return True if the primitive was mapped, false otherwise
+ */
+ bool MapPrimitive(const CDriverPrimitive& primitive);
+
+private:
+ CButtonMapping* const m_buttonMapping;
+};
+
+/*!
+ * \brief Detects when a button should be mapped
+ */
+class CButtonDetector : public CPrimitiveDetector
+{
+public:
+ CButtonDetector(CButtonMapping* buttonMapping, unsigned int buttonIndex);
+
+ /*!
+ * \brief Button state has been updated
+ *
+ * \param bPressed The new state
+ *
+ * \return True if this press was handled, false if it should fall through
+ * to the next driver handler
+ */
+ bool OnMotion(bool bPressed);
+
+private:
+ // Construction parameters
+ const unsigned int m_buttonIndex;
+};
+
+/*!
+ * \brief Detects when a D-pad direction should be mapped
+ */
+class CHatDetector : public CPrimitiveDetector
+{
+public:
+ CHatDetector(CButtonMapping* buttonMapping, unsigned int hatIndex);
+
+ /*!
+ * \brief Hat state has been updated
+ *
+ * \param state The new state
+ *
+ * \return True if state is a cardinal direction, false otherwise
+ */
+ bool OnMotion(HAT_STATE state);
+
+private:
+ // Construction parameters
+ const unsigned int m_hatIndex;
+};
+
+struct AxisConfiguration
+{
+ bool bKnown = false;
+ int center = 0;
+ unsigned int range = 1;
+ bool bLateDiscovery = false;
+};
+
+/*!
+ * \brief Detects when an axis should be mapped
+ */
+class CAxisDetector : public CPrimitiveDetector
+{
+public:
+ CAxisDetector(CButtonMapping* buttonMapping,
+ unsigned int axisIndex,
+ const AxisConfiguration& config);
+
+ /*!
+ * \brief Axis state has been updated
+ *
+ * \param position The new state
+ *
+ * \return Always true - axis motion events are always absorbed while button mapping
+ */
+ bool OnMotion(float position);
+
+ /*!
+ * \brief Called once per frame
+ *
+ * If an axis was activated, the button mapping command will be emitted
+ * here.
+ */
+ void ProcessMotion();
+
+ /*!
+ * \brief Check if the axis was mapped and is still in motion
+ *
+ * \return True between when the axis is mapped and when it crosses zero
+ */
+ bool IsMapping() const { return m_state == AXIS_STATE::MAPPED; }
+
+ /*!
+ * \brief Set the state such that this axis has generated a mapping event
+ *
+ * If an axis is mapped to the Select action, it may be pressed when button
+ * mapping begins. This function is used to indicate that the axis shouldn't
+ * be mapped until after it crosses zero again.
+ */
+ void SetEmitted(const CDriverPrimitive& activePrimitive);
+
+private:
+ enum class AXIS_STATE
+ {
+ /*!
+ * \brief Axis is inactive (position is less than threshold)
+ */
+ INACTIVE,
+
+ /*!
+ * \brief Axis is activated (position has exceeded threshold)
+ */
+ ACTIVATED,
+
+ /*!
+ * \brief Axis has generated a mapping event, but has not been centered yet
+ */
+ MAPPED,
+ };
+
+ enum class AXIS_TYPE
+ {
+ /*!
+ * \brief Axis type is initially unknown
+ */
+ UNKNOWN,
+
+ /*!
+ * \brief Axis is centered about 0
+ *
+ * - If the axis is an analog stick, it can travel to -1 or +1.
+ * - If the axis is a pressure-sensitive button or a normal trigger,
+ * it can travel to +1.
+ * - If the axis is a DirectInput trigger, then it is possible that two
+ * triggers can be on the same axis in opposite directions.
+ * - Normally, D-pads appear as a hat or four buttons. However, some
+ * D-pads are reported as two axes that can have the discrete values
+ * -1, 0 or 1. This is called a "discrete D-pad".
+ */
+ NORMAL,
+
+ /*!
+ * \brief Axis is centered about -1 or 1
+ *
+ * - On OSX, with the cocoa driver, triggers are centered about -1 and
+ * travel to +1. In this case, the range is 2 and the direction is
+ * positive.
+ * - The author of SDL has observed triggers centered at +1 and travel
+ * to 0. In this case, the range is 1 and the direction is negative.
+ */
+ OFFSET,
+ };
+
+ void DetectType(float position);
+
+ // Construction parameters
+ const unsigned int m_axisIndex;
+ AxisConfiguration m_config; // mutable
+
+ // State variables
+ AXIS_STATE m_state;
+ CDriverPrimitive m_activatedPrimitive;
+ AXIS_TYPE m_type;
+ bool m_initialPositionKnown; // set to true on first motion
+ float m_initialPosition; // set to position of first motion
+ bool m_initialPositionChanged; // set to true when position differs from the initial position
+ std::chrono::time_point<std::chrono::steady_clock>
+ m_activationTimeMs; // only used to delay anomalous trigger mapping to detect full range
+};
+
+/*!
+ * \brief Detects when a keyboard key should be mapped
+ */
+class CKeyDetector : public CPrimitiveDetector
+{
+public:
+ CKeyDetector(CButtonMapping* buttonMapping, XBMCKey keycode);
+
+ /*!
+ * \brief Key state has been updated
+ *
+ * \param bPressed The new state
+ *
+ * \return True if this press was handled, false if it should fall through
+ * to the next driver handler
+ */
+ bool OnMotion(bool bPressed);
+
+private:
+ // Construction parameters
+ const XBMCKey m_keycode;
+};
+
+/*!
+ * \brief Detects when a mouse button should be mapped
+ */
+class CMouseButtonDetector : public CPrimitiveDetector
+{
+public:
+ CMouseButtonDetector(CButtonMapping* buttonMapping, MOUSE::BUTTON_ID buttonIndex);
+
+ /*!
+ * \brief Button state has been updated
+ *
+ * \param bPressed The new state
+ *
+ * \return True if this press was handled, false if it should fall through
+ * to the next driver handler
+ */
+ bool OnMotion(bool bPressed);
+
+private:
+ // Construction parameters
+ const MOUSE::BUTTON_ID m_buttonIndex;
+};
+
+/*!
+ * \brief Detects when a mouse button should be mapped
+ */
+class CPointerDetector : public CPrimitiveDetector
+{
+public:
+ CPointerDetector(CButtonMapping* buttonMapping);
+
+ /*!
+ * \brief Pointer position has been updated
+ *
+ * \param x The new x coordinate
+ * \param y The new y coordinate
+ *
+ * \return Always true - pointer motion events are always absorbed while
+ * button mapping
+ */
+ bool OnMotion(int x, int y);
+
+private:
+ // Utility function
+ static INPUT::INTERCARDINAL_DIRECTION GetPointerDirection(int x, int y);
+
+ static const unsigned int MIN_FRAME_COUNT = 10;
+
+ // State variables
+ bool m_bStarted = false;
+ int m_startX = 0;
+ int m_startY = 0;
+ unsigned int m_frameCount = 0;
+};
+
+/*!
+ * \ingroup joystick
+ * \brief Generic implementation of a class that provides button mapping by
+ * translating driver events to button mapping commands
+ *
+ * Button mapping commands are invoked instantly for buttons and hats.
+ *
+ * Button mapping commands are deferred for a short while after an axis is
+ * activated, and only one button mapping command will be invoked per
+ * activation.
+ */
+class CButtonMapping : public IDriverHandler,
+ public KEYBOARD::IKeyboardDriverHandler,
+ public MOUSE::IMouseDriverHandler,
+ public IButtonMapCallback
+{
+public:
+ /*!
+ * \brief Constructor for CButtonMapping
+ *
+ * \param buttonMapper Carries out button-mapping commands using <buttonMap>
+ * \param buttonMap The button map given to <buttonMapper> on each command
+ */
+ CButtonMapping(IButtonMapper* buttonMapper, IButtonMap* buttonMap, IKeymap* keymap);
+
+ ~CButtonMapping() override = default;
+
+ // implementation of IDriverHandler
+ bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override;
+ bool OnHatMotion(unsigned int hatIndex, HAT_STATE state) override;
+ bool OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range) override;
+ void OnInputFrame() override;
+
+ // implementation of IKeyboardDriverHandler
+ bool OnKeyPress(const CKey& key) override;
+ void OnKeyRelease(const CKey& key) override {}
+
+ // implementation of IMouseDriverHandler
+ bool OnPosition(int x, int y) override;
+ bool OnButtonPress(MOUSE::BUTTON_ID button) override;
+ void OnButtonRelease(MOUSE::BUTTON_ID button) override;
+
+ // implementation of IButtonMapCallback
+ void SaveButtonMap() override;
+ void ResetIgnoredPrimitives() override;
+ void RevertButtonMap() override;
+
+ /*!
+ * \brief Process the primitive mapping command
+ *
+ * First, this function checks if the input should be dropped. This can
+ * happen if the input is ignored or the cooldown period is active. If the
+ * input is dropped, this returns true with no effect, effectively absorbing
+ * the input. Otherwise, the mapping command is sent to m_buttonMapper.
+ *
+ * \param primitive The primitive being mapped
+ * \return True if the mapping command was handled, false otherwise
+ */
+ bool MapPrimitive(const CDriverPrimitive& primitive);
+
+private:
+ bool IsMapping() const;
+
+ void OnLateDiscovery(unsigned int axisIndex);
+
+ CButtonDetector& GetButton(unsigned int buttonIndex);
+ CHatDetector& GetHat(unsigned int hatIndex);
+ CAxisDetector& GetAxis(unsigned int axisIndex,
+ float position,
+ const AxisConfiguration& initialConfig = AxisConfiguration());
+ CKeyDetector& GetKey(XBMCKey keycode);
+ CMouseButtonDetector& GetMouseButton(MOUSE::BUTTON_ID buttonIndex);
+ CPointerDetector& GetPointer();
+
+ // Construction parameters
+ IButtonMapper* const m_buttonMapper;
+ IButtonMap* const m_buttonMap;
+ IKeymap* const m_keymap;
+
+ std::map<unsigned int, CButtonDetector> m_buttons;
+ std::map<unsigned int, CHatDetector> m_hats;
+ std::map<unsigned int, CAxisDetector> m_axes;
+ std::map<XBMCKey, CKeyDetector> m_keys;
+ std::map<MOUSE::BUTTON_ID, CMouseButtonDetector> m_mouseButtons;
+ std::unique_ptr<CPointerDetector> m_pointer;
+ std::chrono::time_point<std::chrono::steady_clock> m_lastAction;
+ uint64_t m_frameCount;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/generic/CMakeLists.txt b/xbmc/input/joysticks/generic/CMakeLists.txt
new file mode 100644
index 0000000..f44258a
--- /dev/null
+++ b/xbmc/input/joysticks/generic/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES ButtonMapping.cpp
+ DriverReceiving.cpp
+ FeatureHandling.cpp
+ InputHandling.cpp)
+
+set(HEADERS ButtonMapping.h
+ DriverReceiving.h
+ FeatureHandling.h
+ InputHandling.h)
+
+core_add_library(input_joystick_generic)
diff --git a/xbmc/input/joysticks/generic/DriverReceiving.cpp b/xbmc/input/joysticks/generic/DriverReceiving.cpp
new file mode 100644
index 0000000..bf52f4f
--- /dev/null
+++ b/xbmc/input/joysticks/generic/DriverReceiving.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016-2018 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 "DriverReceiving.h"
+
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/interfaces/IButtonMap.h"
+#include "input/joysticks/interfaces/IDriverReceiver.h"
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+CDriverReceiving::CDriverReceiving(IDriverReceiver* receiver, IButtonMap* buttonMap)
+ : m_receiver(receiver), m_buttonMap(buttonMap)
+{
+}
+
+bool CDriverReceiving::SetRumbleState(const FeatureName& feature, float magnitude)
+{
+ bool bHandled = false;
+
+ if (m_receiver != nullptr && m_buttonMap != nullptr)
+ {
+ CDriverPrimitive primitive;
+ if (m_buttonMap->GetScalar(feature, primitive))
+ {
+ if (primitive.Type() == PRIMITIVE_TYPE::MOTOR)
+ bHandled = m_receiver->SetMotorState(primitive.Index(), magnitude);
+ }
+ }
+
+ return bHandled;
+}
diff --git a/xbmc/input/joysticks/generic/DriverReceiving.h b/xbmc/input/joysticks/generic/DriverReceiving.h
new file mode 100644
index 0000000..758b560
--- /dev/null
+++ b/xbmc/input/joysticks/generic/DriverReceiving.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+#include "input/joysticks/interfaces/IInputReceiver.h"
+
+#include <map>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IDriverReceiver;
+class IButtonMap;
+
+/*!
+ * \ingroup joystick
+ * \brief Class to translate input events from higher-level features to driver primitives
+ *
+ * A button map is used to translate controller features to driver primitives.
+ * The button map has been abstracted away behind the IButtonMap interface
+ * so that it can be provided by an add-on.
+ */
+class CDriverReceiving : public IInputReceiver
+{
+public:
+ CDriverReceiving(IDriverReceiver* receiver, IButtonMap* buttonMap);
+
+ ~CDriverReceiving() override = default;
+
+ // implementation of IInputReceiver
+ bool SetRumbleState(const FeatureName& feature, float magnitude) override;
+
+private:
+ IDriverReceiver* const m_receiver;
+ IButtonMap* const m_buttonMap;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/generic/FeatureHandling.cpp b/xbmc/input/joysticks/generic/FeatureHandling.cpp
new file mode 100644
index 0000000..639ae5b
--- /dev/null
+++ b/xbmc/input/joysticks/generic/FeatureHandling.cpp
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2014-2018 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 "FeatureHandling.h"
+
+#include "ServiceBroker.h"
+#include "games/controllers/Controller.h"
+#include "games/controllers/ControllerManager.h"
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/interfaces/IButtonMap.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "utils/log.h"
+
+#include <vector>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+#define ANALOG_DIGITAL_THRESHOLD 0.5f
+#define DISCRETE_ANALOG_RAMPUP_TIME_MS 1500
+#define DISCRETE_ANALOG_START_VALUE 0.3f
+
+// --- CJoystickFeature --------------------------------------------------------
+
+CJoystickFeature::CJoystickFeature(const FeatureName& name,
+ IInputHandler* handler,
+ IButtonMap* buttonMap)
+ : m_name(name),
+ m_handler(handler),
+ m_buttonMap(buttonMap),
+ m_bEnabled(m_handler->HasFeature(name))
+{
+}
+
+bool CJoystickFeature::AcceptsInput(bool bActivation)
+{
+ bool bAcceptsInput = false;
+
+ if (m_bEnabled)
+ {
+ if (m_handler->AcceptsInput(m_name))
+ bAcceptsInput = true;
+ }
+
+ return bAcceptsInput;
+}
+
+void CJoystickFeature::ResetMotion()
+{
+ m_motionStartTimeMs = {};
+}
+
+void CJoystickFeature::StartMotion()
+{
+ m_motionStartTimeMs = std::chrono::steady_clock::now();
+}
+
+bool CJoystickFeature::InMotion() const
+{
+ return m_motionStartTimeMs.time_since_epoch().count() > 0;
+}
+
+unsigned int CJoystickFeature::MotionTimeMs() const
+{
+ if (!InMotion())
+ return 0;
+
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_motionStartTimeMs);
+
+ return duration.count();
+}
+
+// --- CScalarFeature ----------------------------------------------------------
+
+CScalarFeature::CScalarFeature(const FeatureName& name,
+ IInputHandler* handler,
+ IButtonMap* buttonMap)
+ : CJoystickFeature(name, handler, buttonMap),
+ m_bDigitalState(false),
+ m_analogState(0.0f),
+ m_bActivated(false),
+ m_bDiscrete(true)
+{
+ GAME::ControllerPtr controller =
+ CServiceBroker::GetGameControllerManager().GetController(handler->ControllerID());
+ if (controller)
+ m_inputType = controller->GetInputType(name);
+}
+
+bool CScalarFeature::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed)
+{
+ // Feature must accept input to be considered handled
+ bool bHandled = AcceptsInput(bPressed);
+
+ if (m_inputType == INPUT_TYPE::DIGITAL)
+ bHandled &= OnDigitalMotion(bPressed);
+ else if (m_inputType == INPUT_TYPE::ANALOG)
+ bHandled &= OnAnalogMotion(bPressed ? 1.0f : 0.0f);
+
+ return bHandled;
+}
+
+bool CScalarFeature::OnAnalogMotion(const CDriverPrimitive& source, float magnitude)
+{
+ // Update activated status
+ if (magnitude > 0.0f)
+ m_bActivated = true;
+
+ // Update discrete status
+ if (magnitude != 0.0f && magnitude != 1.0f)
+ m_bDiscrete = false;
+
+ // Feature must accept input to be considered handled
+ bool bHandled = AcceptsInput(magnitude > 0.0f);
+
+ if (m_inputType == INPUT_TYPE::DIGITAL)
+ bHandled &= OnDigitalMotion(magnitude >= ANALOG_DIGITAL_THRESHOLD);
+ else if (m_inputType == INPUT_TYPE::ANALOG)
+ bHandled &= OnAnalogMotion(magnitude);
+
+ return bHandled;
+}
+
+void CScalarFeature::ProcessMotions(void)
+{
+ if (m_inputType == INPUT_TYPE::DIGITAL && m_bDigitalState)
+ ProcessDigitalMotion();
+ else if (m_inputType == INPUT_TYPE::ANALOG)
+ ProcessAnalogMotion();
+}
+
+bool CScalarFeature::OnDigitalMotion(bool bPressed)
+{
+ bool bHandled = false;
+
+ if (m_bDigitalState != bPressed)
+ {
+ m_bDigitalState = bPressed;
+
+ // Motion is initiated in ProcessMotions()
+ ResetMotion();
+
+ bHandled = m_bInitialPressHandled = m_handler->OnButtonPress(m_name, bPressed);
+
+ if (m_bDigitalState)
+ CLog::Log(LOGDEBUG, "FEATURE [ {} ] on {} pressed ({})", m_name, m_handler->ControllerID(),
+ bHandled ? "handled" : "ignored");
+ else
+ CLog::Log(LOGDEBUG, "FEATURE [ {} ] on {} released", m_name, m_handler->ControllerID());
+ }
+ else if (m_bDigitalState)
+ {
+ bHandled = m_bInitialPressHandled;
+ }
+
+ return bHandled;
+}
+
+bool CScalarFeature::OnAnalogMotion(float magnitude)
+{
+ const bool bActivated = (magnitude != 0.0f);
+
+ // Update analog state
+ m_analogState = magnitude;
+
+ // Update motion time
+ if (!bActivated)
+ ResetMotion();
+ else if (!InMotion())
+ StartMotion();
+
+ // Log activation/deactivation
+ if (m_bDigitalState != bActivated)
+ {
+ m_bDigitalState = bActivated;
+ CLog::Log(LOGDEBUG, "FEATURE [ {} ] on {} {}", m_name, m_handler->ControllerID(),
+ bActivated ? "activated" : "deactivated");
+ }
+
+ return true;
+}
+
+void CScalarFeature::ProcessDigitalMotion()
+{
+ if (!InMotion())
+ {
+ // Button was just pressed, record start time and exit (button press event
+ // was already sent this frame)
+ StartMotion();
+ }
+ else
+ {
+ // Button has been pressed more than one event frame
+ const unsigned int elapsed = MotionTimeMs();
+ m_handler->OnButtonHold(m_name, elapsed);
+ }
+}
+
+void CScalarFeature::ProcessAnalogMotion()
+{
+ float magnitude = m_analogState;
+
+ // Calculate time elapsed since motion began
+ unsigned int elapsed = MotionTimeMs();
+
+ // If analog value is discrete, ramp up magnitude
+ if (m_bActivated && m_bDiscrete)
+ {
+ if (elapsed < DISCRETE_ANALOG_RAMPUP_TIME_MS)
+ {
+ magnitude *= static_cast<float>(elapsed) / DISCRETE_ANALOG_RAMPUP_TIME_MS;
+ if (magnitude < DISCRETE_ANALOG_START_VALUE)
+ magnitude = DISCRETE_ANALOG_START_VALUE;
+ }
+ }
+
+ m_handler->OnButtonMotion(m_name, magnitude, elapsed);
+}
+
+// --- CAxisFeature ------------------------------------------------------------
+
+CAxisFeature::CAxisFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap)
+ : CJoystickFeature(name, handler, buttonMap), m_state(0.0f)
+{
+}
+
+bool CAxisFeature::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed)
+{
+ return OnAnalogMotion(source, bPressed ? 1.0f : 0.0f);
+}
+
+void CAxisFeature::ProcessMotions(void)
+{
+ const float newState = m_axis.GetPosition();
+
+ const bool bActivated = (newState != 0.0f);
+
+ if (!AcceptsInput(bActivated))
+ return;
+
+ const bool bWasActivated = (m_state != 0.0f);
+
+ if (!bActivated && bWasActivated)
+ CLog::Log(LOGDEBUG, "Feature [ {} ] on {} deactivated", m_name, m_handler->ControllerID());
+ else if (bActivated && !bWasActivated)
+ {
+ CLog::Log(LOGDEBUG, "Feature [ {} ] on {} activated {}", m_name, m_handler->ControllerID(),
+ newState > 0.0f ? "positive" : "negative");
+ }
+
+ if (bActivated || bWasActivated)
+ {
+ m_state = newState;
+
+ unsigned int motionTimeMs = 0;
+
+ if (bActivated)
+ {
+ if (!InMotion())
+ StartMotion();
+ else
+ motionTimeMs = MotionTimeMs();
+ }
+ else
+ ResetMotion();
+
+ switch (m_buttonMap->GetFeatureType(m_name))
+ {
+ case FEATURE_TYPE::WHEEL:
+ m_handler->OnWheelMotion(m_name, newState, motionTimeMs);
+ break;
+ case FEATURE_TYPE::THROTTLE:
+ m_handler->OnThrottleMotion(m_name, newState, motionTimeMs);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+// --- CWheel ------------------------------------------------------------------
+
+CWheel::CWheel(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap)
+ : CAxisFeature(name, handler, buttonMap)
+{
+}
+
+bool CWheel::OnAnalogMotion(const CDriverPrimitive& source, float magnitude)
+{
+ WHEEL_DIRECTION direction = WHEEL_DIRECTION::NONE;
+
+ std::vector<WHEEL_DIRECTION> dirs = {
+ WHEEL_DIRECTION::RIGHT,
+ WHEEL_DIRECTION::LEFT,
+ };
+
+ CDriverPrimitive primitive;
+ for (auto dir : dirs)
+ {
+ if (m_buttonMap->GetWheel(m_name, dir, primitive) && primitive == source)
+ {
+ direction = dir;
+ break;
+ }
+ }
+
+ // Feature must accept input to be considered handled
+ bool bHandled = AcceptsInput(magnitude > 0.0f);
+
+ switch (direction)
+ {
+ case WHEEL_DIRECTION::RIGHT:
+ m_axis.SetPositiveDistance(magnitude);
+ break;
+ case WHEEL_DIRECTION::LEFT:
+ m_axis.SetNegativeDistance(magnitude);
+ break;
+ default:
+ // Just in case, avoid sticking
+ m_axis.Reset();
+ break;
+ }
+
+ return bHandled;
+}
+
+// --- CThrottle ---------------------------------------------------------------
+
+CThrottle::CThrottle(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap)
+ : CAxisFeature(name, handler, buttonMap)
+{
+}
+
+bool CThrottle::OnAnalogMotion(const CDriverPrimitive& source, float magnitude)
+{
+ THROTTLE_DIRECTION direction = THROTTLE_DIRECTION::NONE;
+
+ std::vector<THROTTLE_DIRECTION> dirs = {
+ THROTTLE_DIRECTION::UP,
+ THROTTLE_DIRECTION::DOWN,
+ };
+
+ CDriverPrimitive primitive;
+ for (auto dir : dirs)
+ {
+ if (m_buttonMap->GetThrottle(m_name, dir, primitive) && primitive == source)
+ {
+ direction = dir;
+ break;
+ }
+ }
+
+ // Feature must accept input to be considered handled
+ bool bHandled = AcceptsInput(magnitude > 0.0f);
+
+ switch (direction)
+ {
+ case THROTTLE_DIRECTION::UP:
+ m_axis.SetPositiveDistance(magnitude);
+ break;
+ case THROTTLE_DIRECTION::DOWN:
+ m_axis.SetNegativeDistance(magnitude);
+ break;
+ default:
+ // Just in case, avoid sticking
+ m_axis.Reset();
+ break;
+ }
+
+ return bHandled;
+}
+
+// --- CAnalogStick ------------------------------------------------------------
+
+CAnalogStick::CAnalogStick(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap)
+ : CJoystickFeature(name, handler, buttonMap), m_vertState(0.0f), m_horizState(0.0f)
+{
+}
+
+bool CAnalogStick::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed)
+{
+ return OnAnalogMotion(source, bPressed ? 1.0f : 0.0f);
+}
+
+bool CAnalogStick::OnAnalogMotion(const CDriverPrimitive& source, float magnitude)
+{
+ ANALOG_STICK_DIRECTION direction = ANALOG_STICK_DIRECTION::NONE;
+
+ std::vector<ANALOG_STICK_DIRECTION> dirs = {
+ ANALOG_STICK_DIRECTION::UP,
+ ANALOG_STICK_DIRECTION::DOWN,
+ ANALOG_STICK_DIRECTION::RIGHT,
+ ANALOG_STICK_DIRECTION::LEFT,
+ };
+
+ CDriverPrimitive primitive;
+ for (auto dir : dirs)
+ {
+ if (m_buttonMap->GetAnalogStick(m_name, dir, primitive) && primitive == source)
+ {
+ direction = dir;
+ break;
+ }
+ }
+
+ // Feature must accept input to be considered handled
+ bool bHandled = AcceptsInput(magnitude > 0.0f);
+
+ switch (direction)
+ {
+ case ANALOG_STICK_DIRECTION::UP:
+ m_vertAxis.SetPositiveDistance(magnitude);
+ break;
+ case ANALOG_STICK_DIRECTION::DOWN:
+ m_vertAxis.SetNegativeDistance(magnitude);
+ break;
+ case ANALOG_STICK_DIRECTION::RIGHT:
+ m_horizAxis.SetPositiveDistance(magnitude);
+ break;
+ case ANALOG_STICK_DIRECTION::LEFT:
+ m_horizAxis.SetNegativeDistance(magnitude);
+ break;
+ default:
+ // Just in case, avoid sticking
+ m_vertAxis.Reset();
+ m_horizAxis.Reset();
+ break;
+ }
+
+ return bHandled;
+}
+
+void CAnalogStick::ProcessMotions(void)
+{
+ const float newVertState = m_vertAxis.GetPosition();
+ const float newHorizState = m_horizAxis.GetPosition();
+
+ const bool bActivated = (newVertState != 0.0f || newHorizState != 0.0f);
+
+ if (!AcceptsInput(bActivated))
+ return;
+
+ const bool bWasActivated = (m_vertState != 0.0f || m_horizState != 0.0f);
+
+ if (bActivated ^ bWasActivated)
+ {
+ CLog::Log(LOGDEBUG, "Feature [ {} ] on {} {}", m_name, m_handler->ControllerID(),
+ bActivated ? "activated" : "deactivated");
+ }
+
+ if (bActivated || bWasActivated)
+ {
+ m_vertState = newVertState;
+ m_horizState = newHorizState;
+
+ unsigned int motionTimeMs = 0;
+
+ if (bActivated)
+ {
+ if (!InMotion())
+ StartMotion();
+ else
+ motionTimeMs = MotionTimeMs();
+ }
+ else
+ {
+ ResetMotion();
+ }
+
+ m_handler->OnAnalogStickMotion(m_name, newHorizState, newVertState, motionTimeMs);
+ }
+}
+
+// --- CAccelerometer ----------------------------------------------------------
+
+CAccelerometer::CAccelerometer(const FeatureName& name,
+ IInputHandler* handler,
+ IButtonMap* buttonMap)
+ : CJoystickFeature(name, handler, buttonMap),
+ m_xAxisState(0.0f),
+ m_yAxisState(0.0f),
+ m_zAxisState(0.0f)
+{
+}
+
+bool CAccelerometer::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed)
+{
+ return OnAnalogMotion(source, bPressed ? 1.0f : 0.0f);
+}
+
+bool CAccelerometer::OnAnalogMotion(const CDriverPrimitive& source, float magnitude)
+{
+ // Feature must accept input to be considered handled
+ bool bHandled = AcceptsInput(true);
+
+ CDriverPrimitive positiveX;
+ CDriverPrimitive positiveY;
+ CDriverPrimitive positiveZ;
+
+ m_buttonMap->GetAccelerometer(m_name, positiveX, positiveY, positiveZ);
+
+ if (source == positiveX)
+ {
+ m_xAxis.SetPositiveDistance(magnitude);
+ }
+ else if (source == positiveY)
+ {
+ m_yAxis.SetPositiveDistance(magnitude);
+ }
+ else if (source == positiveZ)
+ {
+ m_zAxis.SetPositiveDistance(magnitude);
+ }
+ else
+ {
+ // Just in case, avoid sticking
+ m_xAxis.Reset();
+ m_xAxis.Reset();
+ m_yAxis.Reset();
+ }
+
+ return bHandled;
+}
+
+void CAccelerometer::ProcessMotions(void)
+{
+ const float newXAxis = m_xAxis.GetPosition();
+ const float newYAxis = m_yAxis.GetPosition();
+ const float newZAxis = m_zAxis.GetPosition();
+
+ const bool bActivated = (newXAxis != 0.0f || newYAxis != 0.0f || newZAxis != 0.0f);
+
+ if (!AcceptsInput(bActivated))
+ return;
+
+ const bool bWasActivated = (m_xAxisState != 0.0f || m_yAxisState != 0.0f || m_zAxisState != 0.0f);
+
+ if (bActivated || bWasActivated)
+ {
+ m_xAxisState = newXAxis;
+ m_yAxisState = newYAxis;
+ m_zAxisState = newZAxis;
+ m_handler->OnAccelerometerMotion(m_name, newXAxis, newYAxis, newZAxis);
+ }
+}
diff --git a/xbmc/input/joysticks/generic/FeatureHandling.h b/xbmc/input/joysticks/generic/FeatureHandling.h
new file mode 100644
index 0000000..1a42d5c
--- /dev/null
+++ b/xbmc/input/joysticks/generic/FeatureHandling.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+
+#include <chrono>
+#include <memory>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CDriverPrimitive;
+class IInputHandler;
+class IButtonMap;
+
+class CJoystickFeature;
+using FeaturePtr = std::shared_ptr<CJoystickFeature>;
+
+/*!
+ * \ingroup joystick
+ * \brief Base class for joystick features
+ *
+ * See list of feature types in JoystickTypes.h.
+ */
+class CJoystickFeature
+{
+public:
+ CJoystickFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap);
+ virtual ~CJoystickFeature() = default;
+
+ /*!
+ * \brief A digital motion has occurred
+ *
+ * \param source The source of the motion. Must be digital (button or hat)
+ * \param bPressed True for press motion, false for release motion
+ *
+ * \return true if the motion was handled, false otherwise
+ */
+ virtual bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) = 0;
+
+ /*!
+ * \brief An analog motion has occurred
+ *
+ * \param source The source of the motion. Must be a semiaxis
+ * \param magnitude The magnitude of the press or motion in the interval [0.0, 1.0]
+ *
+ * For semiaxes, the magnitude is the force or travel distance in the
+ * direction of the semiaxis. If the value is in the opposite direction,
+ * the magnitude is 0.0.
+ *
+ * For example, if the analog stick goes left, the negative semiaxis will
+ * have a value of 1.0 and the positive semiaxis will have a value of 0.0.
+ */
+ virtual bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) = 0;
+
+ /*!
+ * \brief Process the motions that have occurred since the last invocation
+ *
+ * This allows features with motion on multiple driver primitives to call
+ * their handler once all driver primitives are accounted for.
+ */
+ virtual void ProcessMotions(void) = 0;
+
+ /*!
+ * \brief Check if the input handler is accepting input
+ *
+ * \param bActivation True if the motion is activating (true or positive),
+ * false if the motion is deactivating (false or zero)
+ *
+ * \return True if input should be sent to the input handler, false otherwise
+ */
+ bool AcceptsInput(bool bActivation);
+
+protected:
+ /*!
+ * \brief Reset motion timer
+ */
+ void ResetMotion();
+
+ /*!
+ * \brief Start the motion timer
+ */
+ void StartMotion();
+
+ /*!
+ * \brief Check if the feature is in motion
+ */
+ bool InMotion() const;
+
+ /*!
+ * \brief Get the time for which the feature has been in motion
+ */
+ unsigned int MotionTimeMs() const;
+
+ const FeatureName m_name;
+ IInputHandler* const m_handler;
+ IButtonMap* const m_buttonMap;
+ const bool m_bEnabled;
+
+private:
+ std::chrono::time_point<std::chrono::steady_clock> m_motionStartTimeMs;
+};
+
+class CScalarFeature : public CJoystickFeature
+{
+public:
+ CScalarFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap);
+ ~CScalarFeature() override = default;
+
+ // implementation of CJoystickFeature
+ bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override;
+ bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override;
+ void ProcessMotions() override;
+
+private:
+ bool OnDigitalMotion(bool bPressed);
+ bool OnAnalogMotion(float magnitude);
+
+ void ProcessDigitalMotion();
+ void ProcessAnalogMotion();
+
+ // State variables
+ INPUT_TYPE m_inputType = INPUT_TYPE::UNKNOWN;
+ bool m_bDigitalState;
+ bool m_bInitialPressHandled = false;
+
+ // Analog state variables
+ float m_analogState; // The current magnitude
+ float m_bActivated; // Set to true when first activated (magnitude > 0.0)
+ bool m_bDiscrete; // Set to false when a non-discrete axis is detected
+};
+
+/*!
+ * \ingroup joystick
+ * \brief Axis of a feature (analog stick, accelerometer, etc)
+ *
+ * Axes are composed of two driver primitives, one for the positive semiaxis
+ * and one for the negative semiaxis.
+ *
+ * This effectively means that an axis is two-dimensional, with each dimension
+ * either:
+ *
+ * - a digital value (0.0 or 1.0)
+ * - an analog value (continuous in the interval [0.0, 1.0])
+ */
+class CFeatureAxis
+{
+public:
+ CFeatureAxis(void) { Reset(); }
+
+ /*!
+ * \brief Set value of positive axis
+ */
+ void SetPositiveDistance(float distance) { m_positiveDistance = distance; }
+
+ /*!
+ * \brief Set value of negative axis
+ */
+ void SetNegativeDistance(float distance) { m_negativeDistance = distance; }
+
+ /*!
+ * \brief Get the final value of this axis.
+ *
+ * This axis is two-dimensional, so we need to compress these into a single
+ * dimension. This is done by subtracting the negative from the positive.
+ * Some examples:
+ *
+ * Positive axis: 1.0 (User presses right or analog stick moves right)
+ * Negative axis: 0.0
+ * -------------------
+ * Pos - Neg: 1.0 (Emulated analog stick moves right)
+ *
+ *
+ * Positive axis: 0.0
+ * Negative axis: 1.0 (User presses left or analog stick moves left)
+ * -------------------
+ * Pos - Neg: -1.0 (Emulated analog stick moves left)
+ *
+ *
+ * Positive axis: 1.0 (User presses both buttons)
+ * Negative axis: 1.0
+ * -------------------
+ * Pos - Neg: 0.0 (Emulated analog stick is centered)
+ *
+ */
+ float GetPosition(void) const { return m_positiveDistance - m_negativeDistance; }
+
+ /*!
+ * \brief Reset both positive and negative values to zero
+ */
+ void Reset(void) { m_positiveDistance = m_negativeDistance = 0.0f; }
+
+protected:
+ float m_positiveDistance;
+ float m_negativeDistance;
+};
+
+class CAxisFeature : public CJoystickFeature
+{
+public:
+ CAxisFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap);
+ ~CAxisFeature() override = default;
+
+ // partial implementation of CJoystickFeature
+ bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override;
+ void ProcessMotions() override;
+
+protected:
+ CFeatureAxis m_axis;
+
+ float m_state;
+};
+
+class CWheel : public CAxisFeature
+{
+public:
+ CWheel(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap);
+ ~CWheel() override = default;
+
+ // partial implementation of CJoystickFeature
+ bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override;
+};
+
+class CThrottle : public CAxisFeature
+{
+public:
+ CThrottle(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap);
+ ~CThrottle() override = default;
+
+ // partial implementation of CJoystickFeature
+ bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override;
+};
+
+class CAnalogStick : public CJoystickFeature
+{
+public:
+ CAnalogStick(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap);
+ ~CAnalogStick() override = default;
+
+ // implementation of CJoystickFeature
+ bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override;
+ bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override;
+ void ProcessMotions() override;
+
+protected:
+ CFeatureAxis m_vertAxis;
+ CFeatureAxis m_horizAxis;
+
+ float m_vertState;
+ float m_horizState;
+};
+
+class CAccelerometer : public CJoystickFeature
+{
+public:
+ CAccelerometer(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap);
+ ~CAccelerometer() override = default;
+
+ // implementation of CJoystickFeature
+ bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override;
+ bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override;
+ void ProcessMotions() override;
+
+protected:
+ CFeatureAxis m_xAxis;
+ CFeatureAxis m_yAxis;
+ CFeatureAxis m_zAxis;
+
+ float m_xAxisState;
+ float m_yAxisState;
+ float m_zAxisState;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/generic/InputHandling.cpp b/xbmc/input/joysticks/generic/InputHandling.cpp
new file mode 100644
index 0000000..f6cae6d
--- /dev/null
+++ b/xbmc/input/joysticks/generic/InputHandling.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2014-2018 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 "InputHandling.h"
+
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/JoystickUtils.h"
+#include "input/joysticks/dialogs/GUIDialogNewJoystick.h"
+#include "input/joysticks/interfaces/IButtonMap.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "utils/log.h"
+
+#include <array>
+#include <cmath>
+#include <tuple>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+CGUIDialogNewJoystick* const CInputHandling::m_dialog = new CGUIDialogNewJoystick;
+
+CInputHandling::CInputHandling(IInputHandler* handler, IButtonMap* buttonMap)
+ : m_handler(handler), m_buttonMap(buttonMap)
+{
+}
+
+CInputHandling::~CInputHandling(void) = default;
+
+bool CInputHandling::OnButtonMotion(unsigned int buttonIndex, bool bPressed)
+{
+ return OnDigitalMotion(CDriverPrimitive(PRIMITIVE_TYPE::BUTTON, buttonIndex), bPressed);
+}
+
+bool CInputHandling::OnHatMotion(unsigned int hatIndex, HAT_STATE state)
+{
+ bool bHandled = false;
+
+ bHandled |=
+ OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::UP), state & HAT_DIRECTION::UP);
+ bHandled |= OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::RIGHT),
+ state & HAT_DIRECTION::RIGHT);
+ bHandled |=
+ OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::DOWN), state & HAT_DIRECTION::DOWN);
+ bHandled |=
+ OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::LEFT), state & HAT_DIRECTION::LEFT);
+
+ return bHandled;
+}
+
+bool CInputHandling::OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range)
+{
+ bool bHandled = false;
+
+ if (center != 0)
+ {
+ float translatedPostion = std::min((position - center) / range, 1.0f);
+
+ // Calculate the direction the trigger travels from the center point
+ SEMIAXIS_DIRECTION dir;
+ if (center > 0)
+ dir = SEMIAXIS_DIRECTION::NEGATIVE;
+ else
+ dir = SEMIAXIS_DIRECTION::POSITIVE;
+
+ CDriverPrimitive offsetSemiaxis(axisIndex, center, dir, range);
+
+ bHandled = OnAnalogMotion(offsetSemiaxis, translatedPostion);
+ }
+ else
+ {
+ CDriverPrimitive positiveSemiaxis(axisIndex, 0, SEMIAXIS_DIRECTION::POSITIVE, 1);
+ CDriverPrimitive negativeSemiaxis(axisIndex, 0, SEMIAXIS_DIRECTION::NEGATIVE, 1);
+
+ bHandled |= OnAnalogMotion(positiveSemiaxis, position > 0.0f ? position : 0.0f);
+ bHandled |= OnAnalogMotion(negativeSemiaxis, position < 0.0f ? -position : 0.0f);
+ }
+
+ return bHandled;
+}
+
+void CInputHandling::OnInputFrame(void)
+{
+ // Handle driver input
+ for (auto& it : m_features)
+ it.second->ProcessMotions();
+
+ // Handle higher-level controller input
+ m_handler->OnInputFrame();
+}
+
+bool CInputHandling::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed)
+{
+ bool bHandled = false;
+
+ FeatureName featureName;
+ if (m_buttonMap->GetFeature(source, featureName))
+ {
+ auto it = m_features.find(featureName);
+ if (it == m_features.end())
+ {
+ FeaturePtr feature(CreateFeature(featureName));
+ if (feature)
+ std::tie(it, std::ignore) = m_features.insert({featureName, std::move(feature)});
+ }
+
+ if (it != m_features.end())
+ bHandled = it->second->OnDigitalMotion(source, bPressed);
+ }
+ else if (bPressed)
+ {
+ // If button didn't resolve to a feature, check if the button map is empty
+ // and ask the user if they would like to start mapping the controller
+ if (m_buttonMap->IsEmpty())
+ {
+ CLog::Log(LOGDEBUG, "Empty button map detected for {}", m_buttonMap->ControllerID());
+ m_dialog->ShowAsync();
+ }
+ }
+
+ return bHandled;
+}
+
+bool CInputHandling::OnAnalogMotion(const CDriverPrimitive& source, float magnitude)
+{
+ bool bHandled = false;
+
+ FeatureName featureName;
+ if (m_buttonMap->GetFeature(source, featureName))
+ {
+ auto it = m_features.find(featureName);
+ if (it == m_features.end())
+ {
+ FeaturePtr feature(CreateFeature(featureName));
+ if (feature)
+ std::tie(it, std::ignore) = m_features.insert({featureName, std::move(feature)});
+ }
+
+ if (it != m_features.end())
+ bHandled = it->second->OnAnalogMotion(source, magnitude);
+ }
+
+ return bHandled;
+}
+
+CJoystickFeature* CInputHandling::CreateFeature(const FeatureName& featureName)
+{
+ CJoystickFeature* feature = nullptr;
+
+ switch (m_buttonMap->GetFeatureType(featureName))
+ {
+ case FEATURE_TYPE::SCALAR:
+ {
+ feature = new CScalarFeature(featureName, m_handler, m_buttonMap);
+ break;
+ }
+ case FEATURE_TYPE::ANALOG_STICK:
+ {
+ feature = new CAnalogStick(featureName, m_handler, m_buttonMap);
+ break;
+ }
+ case FEATURE_TYPE::ACCELEROMETER:
+ {
+ feature = new CAccelerometer(featureName, m_handler, m_buttonMap);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return feature;
+}
diff --git a/xbmc/input/joysticks/generic/InputHandling.h b/xbmc/input/joysticks/generic/InputHandling.h
new file mode 100644
index 0000000..772d9d1
--- /dev/null
+++ b/xbmc/input/joysticks/generic/InputHandling.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "FeatureHandling.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "input/joysticks/interfaces/IDriverHandler.h"
+
+#include <map>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CDriverPrimitive;
+class CGUIDialogNewJoystick;
+class IInputHandler;
+class IButtonMap;
+
+/*!
+ * \ingroup joystick
+ * \brief Class to translate input from the driver into higher-level features
+ *
+ * Raw driver input arrives for three elements: buttons, hats and axes. When
+ * driver input is handled by this class, it translates the raw driver
+ * elements into physical joystick features, such as buttons, analog sticks,
+ * etc.
+ *
+ * A button map is used to translate driver primitives to controller features.
+ * The button map has been abstracted away behind the IButtonMap
+ * interface so that it can be provided by an add-on.
+ */
+class CInputHandling : public IDriverHandler
+{
+public:
+ CInputHandling(IInputHandler* handler, IButtonMap* buttonMap);
+
+ ~CInputHandling() override;
+
+ // implementation of IDriverHandler
+ bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override;
+ bool OnHatMotion(unsigned int hatIndex, HAT_STATE state) override;
+ bool OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range) override;
+ void OnInputFrame() override;
+
+private:
+ bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed);
+ bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude);
+
+ CJoystickFeature* CreateFeature(const FeatureName& featureName);
+
+ IInputHandler* const m_handler;
+ IButtonMap* const m_buttonMap;
+
+ std::map<FeatureName, FeaturePtr> m_features;
+
+ static CGUIDialogNewJoystick* const m_dialog;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IButtonMap.h b/xbmc/input/joysticks/interfaces/IButtonMap.h
new file mode 100644
index 0000000..0b4b7d5
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IButtonMap.h
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "input/joysticks/DriverPrimitive.h"
+#include "input/joysticks/JoystickTypes.h"
+
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \ingroup joystick
+ * \brief Button map interface to translate between the driver's raw
+ * button/hat/axis elements and physical joystick features.
+ *
+ * \sa IButtonMapper
+ */
+class IButtonMap
+{
+public:
+ virtual ~IButtonMap() = default;
+
+ /*!
+ * \brief The add-on ID of the game controller associated with this button map
+ *
+ * The controller ID provided by the implementation serves as the context
+ * for the feature names below.
+ *
+ * \return The ID of this button map's game controller add-on
+ */
+ virtual std::string ControllerID(void) const = 0;
+
+ /*!
+ * \brief The Location of the peripheral associated with this button map
+ *
+ * \return The peripheral's location
+ */
+ virtual std::string Location(void) const = 0;
+
+ /*!
+ * \brief Load the button map into memory
+ *
+ * \return True if button map is ready to start translating buttons, false otherwise
+ */
+ virtual bool Load(void) = 0;
+
+ /*!
+ * \brief Reset the button map to its defaults, or clear button map if no defaults
+ */
+ virtual void Reset(void) = 0;
+
+ /*!
+ * \brief Check if the button map is empty
+ *
+ * \return True if the button map is empty, false if it has features
+ */
+ virtual bool IsEmpty(void) const = 0;
+
+ /*!
+ * \brief Get the ID of the controller profile that best represents the
+ * appearance of the peripheral
+ *
+ * \return The controller ID, or empty if the appearance is unknown
+ */
+ virtual std::string GetAppearance() const = 0;
+
+ /*!
+ * \brief Set the ID of the controller that best represents the appearance
+ * of the peripheral
+ *
+ * \param controllerId The controller ID, or empty to unset the appearance
+ *
+ * \return True if the appearance was set, false on error
+ */
+ virtual bool SetAppearance(const std::string& controllerId) const = 0;
+
+ /*!
+ * \brief Get the feature associated with a driver primitive
+ *
+ * Multiple primitives can be mapped to the same feature. For example,
+ * analog sticks use one primitive for each direction.
+ *
+ * \param primitive The driver primitive
+ * \param feature The name of the resolved joystick feature, or
+ * invalid if false is returned
+ *
+ * \return True if the driver primitive is associated with a feature, false otherwise
+ */
+ virtual bool GetFeature(const CDriverPrimitive& primitive, FeatureName& feature) = 0;
+
+ /*!
+ * \brief Get the type of the feature for the given name
+ *
+ * \param feature The feature to look up
+ *
+ * \return The feature's type
+ */
+ virtual FEATURE_TYPE GetFeatureType(const FeatureName& feature) = 0;
+
+ /*!
+ * \brief Get the driver primitive for a scalar feature
+ *
+ * When a feature can be represented by a single driver primitive, it is
+ * called a scalar feature.
+ *
+ * - This includes buttons and triggers, because they can be mapped to a
+ * single button/hat/semiaxis
+ *
+ * - This does not include analog sticks, because they require two axes
+ * and four driver primitives (one for each semiaxis)
+ *
+ * \param feature Must be a scalar feature (a feature that only
+ * requires a single driver primitive)
+ * \param primitive The resolved driver primitive
+ *
+ * \return True if the feature resolved to a driver primitive, false if the
+ * feature didn't resolve or isn't a scalar feature
+ */
+ virtual bool GetScalar(const FeatureName& feature, CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Add or update a scalar feature
+ *
+ * \param feature Must be a scalar feature
+ * \param primitive The feature's driver primitive
+ *
+ * \return True if the feature was updated, false if the feature is
+ * unchanged or failure occurs
+ */
+ virtual void AddScalar(const FeatureName& feature, const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Get an analog stick direction from the button map
+ *
+ * \param feature Must be an analog stick or this will return false
+ * \param direction The direction whose primitive is to be retrieved
+ * \param[out] primitive The primitive mapped to the specified direction
+ *
+ * \return True if the feature and direction resolved to a driver primitive
+ */
+ virtual bool GetAnalogStick(const FeatureName& feature,
+ ANALOG_STICK_DIRECTION direction,
+ CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Add or update an analog stick direction
+ *
+ * \param feature Must be an analog stick or this will return false
+ * \param direction The direction being mapped
+ * \param primitive The driver primitive for the specified analog stick and direction
+ *
+ * \return True if the analog stick was updated, false otherwise
+ */
+ virtual void AddAnalogStick(const FeatureName& feature,
+ ANALOG_STICK_DIRECTION direction,
+ const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Get a relative pointer direction from the button map
+ *
+ * \param feature Must be a relative pointer stick or this will return false
+ * \param direction The direction whose primitive is to be retrieved
+ * \param[out] primitive The primitive mapped to the specified direction
+ *
+ * \return True if the feature and direction resolved to a driver primitive
+ */
+ virtual bool GetRelativePointer(const FeatureName& feature,
+ RELATIVE_POINTER_DIRECTION direction,
+ CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Add or update a relative pointer direction
+ *
+ * \param feature Must be a relative pointer or this will return false
+ * \param direction The direction being mapped
+ * \param primitive The driver primitive for the specified analog stick and direction
+ *
+ * \return True if the analog stick was updated, false otherwise
+ */
+ virtual void AddRelativePointer(const FeatureName& feature,
+ RELATIVE_POINTER_DIRECTION direction,
+ const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Get an accelerometer from the button map
+ *
+ * \param feature Must be an accelerometer or this will return false
+ * \param positiveX The semiaxis mapped to the positive X direction (possibly unknown)
+ * \param positiveY The semiaxis mapped to the positive Y direction (possibly unknown)
+ * \param positiveZ The semiaxis mapped to the positive Z direction (possibly unknown)
+ *
+ * \return True if the feature resolved to an accelerometer with at least 1 known axis
+ */
+ virtual bool GetAccelerometer(const FeatureName& feature,
+ CDriverPrimitive& positiveX,
+ CDriverPrimitive& positiveY,
+ CDriverPrimitive& positiveZ) = 0;
+
+ /*!
+ * \brief Get or update an accelerometer
+ *
+ * \param feature Must be an accelerometer or this will return false
+ * \param positiveX The semiaxis corresponding to the positive X direction
+ * \param positiveY The semiaxis corresponding to the positive Y direction
+ * \param positiveZ The semiaxis corresponding to the positive Z direction
+ *
+ * The driver primitives must be mapped to a semiaxis or this function will fail.
+ *
+ * \return True if the accelerometer was updated, false if unchanged or failure occurred
+ */
+ virtual void AddAccelerometer(const FeatureName& feature,
+ const CDriverPrimitive& positiveX,
+ const CDriverPrimitive& positiveY,
+ const CDriverPrimitive& positiveZ) = 0;
+
+ /*!
+ * \brief Get a wheel direction from the button map
+ *
+ * \param feature Must be a wheel or this will return false
+ * \param direction The direction whose primitive is to be retrieved
+ * \param[out] primitive The primitive mapped to the specified direction
+ *
+ * \return True if the feature and direction resolved to a driver primitive
+ */
+ virtual bool GetWheel(const FeatureName& feature,
+ WHEEL_DIRECTION direction,
+ CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Add or update a wheel direction
+ *
+ * \param feature Must be a wheel or this will return false
+ * \param direction The direction being mapped
+ * \param primitive The driver primitive for the specified analog stick and direction
+ *
+ * \return True if the analog stick was updated, false otherwise
+ */
+ virtual void AddWheel(const FeatureName& feature,
+ WHEEL_DIRECTION direction,
+ const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Get a throttle direction from the button map
+ *
+ * \param feature Must be a throttle or this will return false
+ * \param direction The direction whose primitive is to be retrieved
+ * \param[out] primitive The primitive mapped to the specified direction
+ *
+ * \return True if the feature and direction resolved to a driver primitive
+ */
+ virtual bool GetThrottle(const FeatureName& feature,
+ THROTTLE_DIRECTION direction,
+ CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Add or update a throttle direction
+ *
+ * \param feature Must be a throttle or this will return false
+ * \param direction The direction being mapped
+ * \param primitive The driver primitive for the specified analog stick and direction
+ *
+ * \return True if the analog stick was updated, false otherwise
+ */
+ virtual void AddThrottle(const FeatureName& feature,
+ THROTTLE_DIRECTION direction,
+ const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Get the driver primitive for a keyboard key
+ *
+ * \param feature Must be a key
+ * \param primitive The resolved driver primitive
+ *
+ * \return True if the feature resolved to a driver primitive, false if the
+ * feature didn't resolve or isn't a scalar feature
+ */
+ virtual bool GetKey(const FeatureName& feature, CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Add or update a key
+ *
+ * \param feature Must be a key
+ * \param primitive The feature's driver primitive
+ *
+ * \return True if the feature was updated, false if the feature is
+ * unchanged or failure occurs
+ */
+ virtual void AddKey(const FeatureName& feature, const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Set a list of driver primitives to be ignored
+ *
+ * This is necessary to prevent features from interfering with the button
+ * mapping process. This includes accelerometers, as well as PS4 triggers
+ * which send both a button press and an analog value.
+ *
+ * \param primitives The driver primitives to be ignored
+ */
+ virtual void SetIgnoredPrimitives(const std::vector<CDriverPrimitive>& primitives) = 0;
+
+ /*!
+ * \brief Check if a primitive is in the list of primitives to be ignored
+ *
+ * \param primitive The primitive to check
+ *
+ * \return True if the primitive should be ignored in the mapping process
+ */
+ virtual bool IsIgnored(const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Get the properties of an axis
+ *
+ * \param axisIndex The index of the axis to check
+ * \param center[out] The center, if known
+ * \param range[out] The range, if known
+ *
+ * \return True if the properties are known, false otherwise
+ */
+ virtual bool GetAxisProperties(unsigned int axisIndex, int& center, unsigned int& range) = 0;
+
+ /*!
+ * \brief Save the button map
+ */
+ virtual void SaveButtonMap() = 0;
+
+ /*!
+ * \brief Revert changes to the button map since the last time it was loaded
+ * or committed to disk
+ */
+ virtual void RevertButtonMap() = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IButtonMapCallback.h b/xbmc/input/joysticks/interfaces/IButtonMapCallback.h
new file mode 100644
index 0000000..40744f8
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IButtonMapCallback.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016-2018 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 JOYSTICK
+{
+/*!
+ * \brief Interface for handling button maps
+ */
+class IButtonMapCallback
+{
+public:
+ virtual ~IButtonMapCallback() = default;
+
+ /*!
+ * \brief Save the button map
+ */
+ virtual void SaveButtonMap() = 0;
+
+ /*!
+ * \brief Clear the list of ignored driver primitives
+ *
+ * Called if the user begins capturing primitives to be ignored, and
+ * no primitives are captured before the dialog is accepted by the user.
+ *
+ * In this case, the button mapper won't have been given access to the
+ * button map, so a callback is needed to indicate that no primitives were
+ * captured and the user accepted this.
+ */
+ virtual void ResetIgnoredPrimitives() = 0;
+
+ /*!
+ * \brief Revert changes to the button map since the last time it was loaded
+ * or committed to disk
+ */
+ virtual void RevertButtonMap() = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IButtonMapper.h b/xbmc/input/joysticks/interfaces/IButtonMapper.h
new file mode 100644
index 0000000..d0784df
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IButtonMapper.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+
+#include <map>
+#include <string>
+
+class IKeymap;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class CDriverPrimitive;
+class IButtonMap;
+class IButtonMapCallback;
+
+/*!
+ * \ingroup joystick
+ * \brief Button mapper interface to assign the driver's raw button/hat/axis
+ * elements to physical joystick features using a provided button map.
+ *
+ * \sa IButtonMap
+ */
+class IButtonMapper
+{
+public:
+ IButtonMapper() = default;
+
+ virtual ~IButtonMapper() = default;
+
+ /*!
+ * \brief The add-on ID of the game controller associated with this button mapper
+ *
+ * \return The ID of the add-on extending kodi.game.controller
+ */
+ virtual std::string ControllerID(void) const = 0;
+
+ /*!
+ * \brief Return true if the button mapper wants a cooldown between button
+ * mapping commands
+ *
+ * \return True to only send button mapping commands that occur after a small
+ * timeout from the previous command.
+ */
+ virtual bool NeedsCooldown(void) const = 0;
+
+ /*!
+ * \brief Return true if the button mapper accepts primitives of the given type
+ *
+ * \param type The primitive type
+ *
+ * \return True if the button mapper can map the primitive type, false otherwise
+ */
+ virtual bool AcceptsPrimitive(PRIMITIVE_TYPE type) const = 0;
+
+ /*!
+ * \brief Handle button/hat press or axis threshold
+ *
+ * \param buttonMap The button map being manipulated
+ * \param keymap An interface capable of translating features to Kodi actions
+ * \param primitive The driver primitive
+ *
+ * Called in the same thread as \ref IButtonMapper::OnFrame.
+ *
+ * \return True if driver primitive was mapped to a feature
+ */
+ virtual bool MapPrimitive(IButtonMap* buttonMap,
+ IKeymap* keyMap,
+ const CDriverPrimitive& primitive) = 0;
+
+ /*!
+ * \brief Called once per event frame to notify the implementation of motion status
+ *
+ * \param buttonMap The button map passed to MapPrimitive() (shall not be modified)
+ * \param bMotion True if a previously-mapped axis is still in motion
+ *
+ * This allows the implementer to wait for an axis to be centered before
+ * allowing it to be used as Kodi input.
+ *
+ * If mapping finishes on an axis, then the axis will still be pressed and
+ * sending input every frame when the mapping ends. For example, when the
+ * right analog stick is the last feature to be mapped, it is still pressed
+ * when mapping ends and immediately sends Volume Down actions.
+ *
+ * The fix is to allow implementers to wait until all axes are motionless
+ * before detaching themselves.
+ *
+ * Called in the same thread as \ref IButtonMapper::MapPrimitive.
+ */
+ virtual void OnEventFrame(const IButtonMap* buttonMap, bool bMotion) = 0;
+
+ /*!
+ * \brief Called when an axis has been detected after mapping began
+ *
+ * \param axisIndex The index of the axis being discovered
+ *
+ * Some joystick drivers don't report an initial value for analog axes.
+ *
+ * Called in the same thread as \ref IButtonMapper::MapPrimitive.
+ */
+ virtual void OnLateAxis(const IButtonMap* buttonMap, unsigned int axisIndex) = 0;
+
+ // Button map callback interface
+ void SetButtonMapCallback(const std::string& deviceLocation, IButtonMapCallback* callback)
+ {
+ m_callbacks[deviceLocation] = callback;
+ }
+ void ResetButtonMapCallbacks(void) { m_callbacks.clear(); }
+ std::map<std::string, IButtonMapCallback*>& ButtonMapCallbacks(void) { return m_callbacks; }
+
+private:
+ std::map<std::string, IButtonMapCallback*> m_callbacks; // Device location -> callback
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IButtonSequence.h b/xbmc/input/joysticks/interfaces/IButtonSequence.h
new file mode 100644
index 0000000..5c642e6
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IButtonSequence.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IButtonSequence
+{
+public:
+ virtual ~IButtonSequence() = default;
+
+ virtual bool OnButtonPress(const FeatureName& feature) = 0;
+
+ /*!
+ * \brief Returns true if a sequence is being captured to prevent input
+ * from falling through to the application
+ */
+ virtual bool IsCapturing() = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IDriverHandler.h b/xbmc/input/joysticks/interfaces/IDriverHandler.h
new file mode 100644
index 0000000..48e4da5
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IDriverHandler.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \brief Interface defining methods to handle joystick events for raw driver
+ * elements (buttons, hats, axes)
+ */
+class IDriverHandler
+{
+public:
+ virtual ~IDriverHandler() = default;
+
+ /*!
+ * \brief Handle button motion
+ *
+ * \param buttonIndex The index of the button as reported by the driver
+ * \param bPressed true for press motion, false for release motion
+ *
+ * \return True if a press was handled, false otherwise
+ */
+ virtual bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) = 0;
+
+ /*!
+ * \brief Handle hat motion
+ *
+ * \param hatIndex The index of the hat as reported by the driver
+ * \param state The direction the hat is now being pressed
+ *
+ * \return True if the new direction was handled, false otherwise
+ */
+ virtual bool OnHatMotion(unsigned int hatIndex, HAT_STATE state) = 0;
+
+ /*!
+ * \brief Handle axis motion
+ *
+ * If a joystick feature requires multiple axes (analog sticks, accelerometers),
+ * they can be buffered for later processing.
+ *
+ * \param axisIndex The index of the axis as reported by the driver
+ * \param position The position of the axis in the closed interval [-1.0, 1.0]
+ * \param center The center point of the axis (either -1, 0 or 1)
+ * \param range The maximum distance the axis can move (either 1 or 2)
+ *
+ * \return True if the motion was handled, false otherwise
+ */
+ virtual bool OnAxisMotion(unsigned int axisIndex,
+ float position,
+ int center,
+ unsigned int range) = 0;
+
+ /*!
+ * \brief Handle buffered input motion for features that require multiple axes
+ *
+ * OnInputFrame() is called at the end of the frame when all axis motions
+ * have been reported. This has several uses, including:
+ *
+ * - Combining multiple axes into a single analog stick or accelerometer event
+ * - Imitating an analog feature with a digital button so that events can be
+ * dispatched every frame.
+ */
+ virtual void OnInputFrame(void) = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IDriverReceiver.h b/xbmc/input/joysticks/interfaces/IDriverReceiver.h
new file mode 100644
index 0000000..2a4ff6c
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IDriverReceiver.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016-2018 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 JOYSTICK
+{
+/*!
+ * \ingroup joystick
+ * \brief Interface for sending input events to joystick drivers
+ */
+class IDriverReceiver
+{
+public:
+ virtual ~IDriverReceiver() = default;
+
+ /*!
+ * \brief Set the value of a rumble motor
+ *
+ * \param motorIndex The driver index of the motor to rumble
+ * \param magnitude The motor's new magnitude of vibration in the closed interval [0, 1]
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool SetMotorState(unsigned int motorIndex, float magnitude) = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IInputHandler.h b/xbmc/input/joysticks/interfaces/IInputHandler.h
new file mode 100644
index 0000000..2ddbf5c
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IInputHandler.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+
+#include <string>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IInputReceiver;
+
+/*!
+ * \ingroup joystick
+ * \brief Interface for handling input events for game controllers
+ */
+class IInputHandler
+{
+public:
+ virtual ~IInputHandler() = default;
+
+ /*!
+ * \brief The add-on ID of the game controller associated with this input handler
+ *
+ * \return The ID of the add-on extending kodi.game.controller
+ */
+ virtual std::string ControllerID(void) const = 0;
+
+ /*!
+ * \brief Return true if the input handler accepts the given feature
+ *
+ * \param feature A feature belonging to the controller specified by ControllerID()
+ *
+ * \return True if the feature is used for input, false otherwise
+ */
+ virtual bool HasFeature(const FeatureName& feature) const = 0;
+
+ /*!
+ * \brief Return true if the input handler is currently accepting input for the
+ * given feature
+ *
+ * \param feature A feature belonging to the controller specified by ControllerID()
+ *
+ * \return True if the feature is currently accepting input, false otherwise
+ *
+ * This does not prevent the input events from being called, but can return
+ * false to indicate that input wasn't handled for the specified feature.
+ */
+ virtual bool AcceptsInput(const FeatureName& feature) const = 0;
+
+ /*!
+ * \brief A digital button has been pressed or released
+ *
+ * \param feature The feature being pressed
+ * \param bPressed True if pressed, false if released
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool OnButtonPress(const FeatureName& feature, bool bPressed) = 0;
+
+ /*!
+ * \brief A digital button has been pressed for more than one event frame
+ *
+ * \param feature The feature being held
+ * \param holdTimeMs The time elapsed since the initial press (ms)
+ *
+ * If OnButtonPress() returns true for the initial press, then this callback
+ * is invoked on subsequent frames until the button is released.
+ */
+ virtual void OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs) = 0;
+
+ /*!
+ * \brief An analog button (trigger or a pressure-sensitive button) has changed state
+ *
+ * \param feature The feature changing state
+ * \param magnitude The button pressure or trigger travel distance in the
+ * closed interval [0, 1]
+ * \param motionTimeMs The time elapsed since the magnitude was 0
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool OnButtonMotion(const FeatureName& feature,
+ float magnitude,
+ unsigned int motionTimeMs) = 0;
+
+ /*!
+ * \brief An analog stick has moved
+ *
+ * \param feature The analog stick being moved
+ * \param x The x coordinate in the closed interval [-1, 1]
+ * \param y The y coordinate in the closed interval [-1, 1]
+ * \param motionTimeMs The time elapsed since this analog stick was centered,
+ * or 0 if the analog stick is centered
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool OnAnalogStickMotion(const FeatureName& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs) = 0;
+
+ /*!
+ * \brief An accelerometer's state has changed
+ *
+ * \param feature The accelerometer being accelerated
+ * \param x The x coordinate in the closed interval [-1, 1]
+ * \param y The y coordinate in the closed interval [-1, 1]
+ * \param z The z coordinate in the closed interval [-1, 1]
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z)
+ {
+ return false;
+ }
+
+ /*!
+ * \brief A wheel has changed state
+ *
+ * Left is negative position, right is positive position
+ *
+ * \param feature The wheel changing state
+ * \param position The position in the closed interval [-1, 1]
+ * \param motionTimeMs The time elapsed since the position was 0
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool OnWheelMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs) = 0;
+
+ /*!
+ * \brief A throttle has changed state
+ *
+ * Up is positive position, down is negative position.
+ *
+ * \param feature The wheel changing state
+ * \param position The position in the closed interval [-1, 1]
+ * \param motionTimeMs The time elapsed since the position was 0
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool OnThrottleMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs) = 0;
+
+ /*!
+ * \brief Called at the end of the frame that provided input
+ */
+ virtual void OnInputFrame() = 0;
+
+ // Input receiver interface
+ void SetInputReceiver(IInputReceiver* receiver) { m_receiver = receiver; }
+ void ResetInputReceiver(void) { m_receiver = nullptr; }
+ IInputReceiver* InputReceiver(void) { return m_receiver; }
+
+private:
+ IInputReceiver* m_receiver = nullptr;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IInputProvider.h b/xbmc/input/joysticks/interfaces/IInputProvider.h
new file mode 100644
index 0000000..09a0366
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IInputProvider.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017-2018 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 JOYSTICK
+{
+class IInputHandler;
+
+/*!
+ * \ingroup joystick
+ * \brief Interface for classes that can provide input
+ */
+class IInputProvider
+{
+public:
+ virtual ~IInputProvider() = default;
+
+ /*!
+ * \brief Register a handler for the provided input
+ *
+ * \param handler The handler to receive input provided by this class
+ * \param bPromiscuous If true, receives all input (including handled input)
+ * in the background
+ */
+ virtual void RegisterInputHandler(IInputHandler* handler, bool bPromiscuous) = 0;
+
+ /*!
+ * \brief Unregister a handler
+ *
+ * \param handler The handler that was receiving input
+ */
+ virtual void UnregisterInputHandler(IInputHandler* handler) = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IInputReceiver.h b/xbmc/input/joysticks/interfaces/IInputReceiver.h
new file mode 100644
index 0000000..400da3f
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IInputReceiver.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \ingroup joystick
+ * \brief Interface for sending input events to game controllers
+ */
+class IInputReceiver
+{
+public:
+ virtual ~IInputReceiver() = default;
+
+ /*!
+ * \brief Set the value of a rumble motor
+ *
+ * \param feature The name of the motor to rumble
+ * \param magnitude The motor's new magnitude of vibration in the closed interval [0, 1]
+ *
+ * \return True if the event was handled otherwise false
+ */
+ virtual bool SetRumbleState(const FeatureName& feature, float magnitude) = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IKeyHandler.h b/xbmc/input/joysticks/interfaces/IKeyHandler.h
new file mode 100644
index 0000000..30f49c5
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IKeyHandler.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015-2018 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 JOYSTICK
+{
+/*!
+ * \ingroup joystick
+ * \brief Interface for handling keymap keys
+ *
+ * Keys can be mapped to analog actions (e.g. "AnalogSeekForward") or digital
+ * actions (e.g. "Up").
+ */
+class IKeyHandler
+{
+public:
+ virtual ~IKeyHandler() = default;
+
+ /*!
+ * \brief Return true if the key is "pressed" (has a magnitude greater
+ * than 0.5)
+ *
+ * \return True if the key is "pressed", false otherwise
+ */
+ virtual bool IsPressed() const = 0;
+
+ /*!
+ * \brief A key mapped to a digital feature has been pressed or released
+ *
+ * \param bPressed true if the key's button/axis is activated, false if deactivated
+ * \param holdTimeMs The held time in ms for pressed buttons, or 0 for released
+ *
+ * \return True if the key is mapped to an action, false otherwise
+ */
+ virtual bool OnDigitalMotion(bool bPressed, unsigned int holdTimeMs) = 0;
+
+ /*!
+ * \brief Callback for keys mapped to analog features
+ *
+ * \param magnitude The amount of the analog action
+ * \param motionTimeMs The time since the magnitude was 0
+ *
+ * \return True if the key is mapped to an action, false otherwise
+ */
+ virtual bool OnAnalogMotion(float magnitude, unsigned int motionTimeMs) = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/interfaces/IKeymapHandler.h b/xbmc/input/joysticks/interfaces/IKeymapHandler.h
new file mode 100644
index 0000000..f0af427
--- /dev/null
+++ b/xbmc/input/joysticks/interfaces/IKeymapHandler.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017-2018 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
+
+#include <set>
+#include <string>
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+/*!
+ * \ingroup joystick
+ * \brief Interface for a class working with a keymap
+ */
+class IKeymapHandler
+{
+public:
+ virtual ~IKeymapHandler() = default;
+
+ /*!
+ * \brief Get the pressed state of the given keys
+ *
+ * \param keyNames The key names
+ *
+ * \return True if all keys are pressed or no keys are given, false otherwise
+ */
+ virtual bool HotkeysPressed(const std::set<std::string>& keyNames) const = 0;
+
+ /*!
+ * \brief Get the key name of the last button pressed
+ *
+ * \return The key name of the last-pressed button, or empty if no button
+ * is pressed
+ */
+ virtual std::string GetLastPressed() const = 0;
+
+ /*!
+ * \brief Called when a key has emitted an action after bring pressed
+ *
+ * \param keyName the key name that emitted the action
+ */
+ virtual void OnPress(const std::string& keyName) = 0;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/keymaps/CMakeLists.txt b/xbmc/input/joysticks/keymaps/CMakeLists.txt
new file mode 100644
index 0000000..c854b36
--- /dev/null
+++ b/xbmc/input/joysticks/keymaps/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES KeyHandler.cpp
+ KeymapHandler.cpp
+ KeymapHandling.cpp
+)
+
+set(HEADERS KeyHandler.h
+ KeymapHandler.h
+ KeymapHandling.h
+)
+
+core_add_library(input_joystick_keymaps)
diff --git a/xbmc/input/joysticks/keymaps/KeyHandler.cpp b/xbmc/input/joysticks/keymaps/KeyHandler.cpp
new file mode 100644
index 0000000..cd0f30a
--- /dev/null
+++ b/xbmc/input/joysticks/keymaps/KeyHandler.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2017-2018 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 "KeyHandler.h"
+
+#include "input/IKeymap.h"
+#include "input/actions/ActionIDs.h"
+#include "input/actions/ActionTranslator.h"
+#include "input/joysticks/JoystickUtils.h"
+#include "input/joysticks/interfaces/IKeymapHandler.h"
+#include "interfaces/IActionListener.h"
+
+#include <algorithm>
+#include <assert.h>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+#define DIGITAL_ANALOG_THRESHOLD 0.5f
+
+#define HOLD_TIMEOUT_MS 500
+#define REPEAT_TIMEOUT_MS 50
+
+CKeyHandler::CKeyHandler(const std::string& keyName,
+ IActionListener* actionHandler,
+ const IKeymap* keymap,
+ IKeymapHandler* keymapHandler)
+ : m_keyName(keyName),
+ m_actionHandler(actionHandler),
+ m_keymap(keymap),
+ m_keymapHandler(keymapHandler)
+{
+ assert(m_actionHandler != nullptr);
+ assert(m_keymap != nullptr);
+ assert(m_keymapHandler != nullptr);
+
+ Reset();
+}
+
+void CKeyHandler::Reset()
+{
+ m_bHeld = false;
+ m_magnitude = 0.0f;
+ m_holdStartTimeMs = 0;
+ m_lastHoldTimeMs = 0;
+ m_bActionSent = false;
+ m_lastActionMs = 0;
+ m_activeWindowId = -1;
+ m_lastAction = CAction();
+}
+
+bool CKeyHandler::OnDigitalMotion(bool bPressed, unsigned int holdTimeMs)
+{
+ return OnAnalogMotion(bPressed ? 1.0f : 0.0f, holdTimeMs);
+}
+
+bool CKeyHandler::OnAnalogMotion(float magnitude, unsigned int motionTimeMs)
+{
+ // Don't send deactivation event more than once
+ if (m_magnitude == 0.0f && magnitude == 0.0f)
+ return false;
+
+ // Get actions for the key
+ const auto& actionGroup = m_keymap->GetActions(m_keyName);
+ const int windowId = actionGroup.windowId;
+ const auto& actions = actionGroup.actions;
+
+ // Calculate press state
+ const bool bPressed = IsPressed(magnitude);
+ const bool bJustPressed = bPressed && !m_bHeld;
+
+ if (bJustPressed)
+ {
+ // Reset key if just pressed
+ Reset();
+
+ // Record hold start time if just pressed
+ m_holdStartTimeMs = motionTimeMs;
+
+ // Record window ID
+ if (windowId >= 0)
+ m_activeWindowId = windowId;
+ }
+
+ // Calculate holdtime relative to when magnitude crossed the threshold
+ unsigned int holdTimeMs = 0;
+ if (bPressed)
+ holdTimeMs = motionTimeMs - m_holdStartTimeMs;
+
+ // Give priority to actions with hotkeys
+ std::vector<const KeymapAction*> actionsWithHotkeys;
+
+ for (const auto& action : actions)
+ {
+ if (!action.hotkeys.empty())
+ actionsWithHotkeys.emplace_back(&action);
+ }
+
+ CAction dispatchAction =
+ ProcessActions(std::move(actionsWithHotkeys), windowId, magnitude, holdTimeMs);
+
+ // If that failed, try again with all actions
+ if (dispatchAction.GetID() == ACTION_NONE)
+ {
+ std::vector<const KeymapAction*> allActions;
+
+ allActions.reserve(actions.size());
+ for (const auto& action : actions)
+ allActions.emplace_back(&action);
+
+ dispatchAction = ProcessActions(std::move(allActions), windowId, magnitude, holdTimeMs);
+ }
+
+ // If specific action was sent last frame but not this one, send a release event
+ if (dispatchAction.GetID() != m_lastAction.GetID())
+ {
+ if (CActionTranslator::IsAnalog(m_lastAction.GetID()) && m_lastAction.GetAmount() > 0.0f)
+ {
+ m_lastAction.ClearAmount();
+ m_actionHandler->OnAction(m_lastAction);
+ }
+ }
+
+ // Dispatch action
+ bool bHandled = false;
+ if (dispatchAction.GetID() != ACTION_NONE)
+ {
+ m_actionHandler->OnAction(dispatchAction);
+ bHandled = true;
+ }
+
+ m_bHeld = bPressed;
+ m_magnitude = magnitude;
+ m_lastHoldTimeMs = holdTimeMs;
+ m_lastAction = dispatchAction;
+
+ return bHandled;
+}
+
+CAction CKeyHandler::ProcessActions(std::vector<const KeymapAction*> actions,
+ int windowId,
+ float magnitude,
+ unsigned int holdTimeMs)
+{
+ CAction dispatchAction;
+
+ // Filter out actions without pressed hotkeys
+ actions.erase(std::remove_if(actions.begin(), actions.end(),
+ [this](const KeymapAction* action) {
+ return !m_keymapHandler->HotkeysPressed(action->hotkeys);
+ }),
+ actions.end());
+
+ if (actions.empty())
+ return false;
+
+ // Actions are sorted by holdtime, so the final action is the one with the
+ // greatest holdtime
+ const KeymapAction& finalAction = **actions.rbegin();
+ const unsigned int maxHoldTimeMs = finalAction.holdTimeMs;
+
+ const bool bHasDelay = (maxHoldTimeMs > 0);
+ if (!bHasDelay)
+ {
+ dispatchAction = ProcessAction(finalAction, windowId, magnitude, holdTimeMs);
+ }
+ else
+ {
+ // If holdtime has exceeded the last action, execute it now
+ if (holdTimeMs >= finalAction.holdTimeMs)
+ {
+ // Force holdtime to zero for the initial press
+ if (!m_bActionSent)
+ holdTimeMs = 0;
+ else
+ holdTimeMs -= finalAction.holdTimeMs;
+
+ dispatchAction = ProcessAction(finalAction, windowId, magnitude, holdTimeMs);
+ }
+ else
+ {
+ // Calculate press state
+ const bool bPressed = IsPressed(magnitude);
+ const bool bJustReleased = m_bHeld && !bPressed;
+
+ // If button was just released, send a release action
+ if (bJustReleased)
+ dispatchAction = ProcessRelease(actions, windowId);
+ }
+ }
+
+ return dispatchAction;
+}
+
+CAction CKeyHandler::ProcessRelease(std::vector<const KeymapAction*> actions, int windowId)
+{
+ CAction dispatchAction;
+
+ // Use previous holdtime from before button release
+ const unsigned int holdTimeMs = m_lastHoldTimeMs;
+
+ // Send an action on release if one occurs before the holdtime
+ for (auto it = actions.begin(); it != actions.end();)
+ {
+ const KeymapAction& action = **it;
+
+ unsigned int thisHoldTime = (*it)->holdTimeMs;
+
+ ++it;
+ if (it == actions.end())
+ break;
+
+ unsigned int nextHoldTime = (*it)->holdTimeMs;
+
+ if (thisHoldTime <= holdTimeMs && holdTimeMs < nextHoldTime)
+ {
+ dispatchAction = ProcessAction(action, windowId, 1.0f, 0);
+ break;
+ }
+ }
+
+ return dispatchAction;
+}
+
+CAction CKeyHandler::ProcessAction(const KeymapAction& action,
+ int windowId,
+ float magnitude,
+ unsigned int holdTimeMs)
+{
+ CAction dispatchAction;
+
+ bool bSendAction = false;
+
+ if (windowId != m_activeWindowId)
+ {
+ // Don't send actions if the window has changed since being pressed
+ }
+ else if (CActionTranslator::IsAnalog(action.actionId))
+ {
+ bSendAction = true;
+ }
+ else if (IsPressed(magnitude))
+ {
+ // Dispatch action if button was pressed this frame
+ if (holdTimeMs == 0)
+ bSendAction = true;
+ else
+ bSendAction = SendRepeatAction(holdTimeMs);
+ }
+
+ if (bSendAction)
+ {
+ const CAction guiAction(action.actionId, magnitude, 0.0f, action.actionString, holdTimeMs);
+ m_keymapHandler->OnPress(m_keyName);
+ m_bActionSent = true;
+ m_lastActionMs = holdTimeMs;
+ dispatchAction = guiAction;
+ }
+
+ return dispatchAction;
+}
+
+bool CKeyHandler::SendRepeatAction(unsigned int holdTimeMs)
+{
+ bool bSendRepeat = true;
+
+ // Don't send a repeat action if the last key has changed
+ if (m_keymapHandler->GetLastPressed() != m_keyName)
+ bSendRepeat = false;
+
+ // Ensure initial timeout has elapsed
+ else if (holdTimeMs < HOLD_TIMEOUT_MS)
+ bSendRepeat = false;
+
+ // Ensure repeat timeout has elapsed
+ else if (holdTimeMs < m_lastActionMs + REPEAT_TIMEOUT_MS)
+ bSendRepeat = false;
+
+ return bSendRepeat;
+}
+
+bool CKeyHandler::IsPressed(float magnitude)
+{
+ return magnitude >= DIGITAL_ANALOG_THRESHOLD;
+}
diff --git a/xbmc/input/joysticks/keymaps/KeyHandler.h b/xbmc/input/joysticks/keymaps/KeyHandler.h
new file mode 100644
index 0000000..3b5a46c
--- /dev/null
+++ b/xbmc/input/joysticks/keymaps/KeyHandler.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017-2018 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
+
+#include "input/actions/Action.h"
+#include "input/joysticks/JoystickTypes.h"
+#include "input/joysticks/interfaces/IKeyHandler.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+class CAction;
+class IActionListener;
+class IKeymap;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IKeymapHandler;
+
+/*!
+ * \ingroup joystick
+ * \brief
+ */
+class CKeyHandler : public IKeyHandler
+{
+public:
+ CKeyHandler(const std::string& keyName,
+ IActionListener* actionHandler,
+ const IKeymap* keymap,
+ IKeymapHandler* keymapHandler);
+
+ ~CKeyHandler() override = default;
+
+ // implementation of IKeyHandler
+ bool IsPressed() const override { return m_bHeld; }
+ bool OnDigitalMotion(bool bPressed, unsigned int holdTimeMs) override;
+ bool OnAnalogMotion(float magnitude, unsigned int motionTimeMs) override;
+
+private:
+ void Reset();
+
+ /*!
+ * \brief Process actions to see if an action should be dispatched
+ *
+ * \param actions All actions from the keymap defined for the current window
+ * \param windowId The current window ID
+ * \param magnitude The magnitude or distance of the feature being handled
+ * \param holdTimeMs The time which the feature has been past the hold threshold
+ *
+ * \return The action to dispatch, or action with ID ACTION_NONE if no action should be dispatched
+ */
+ CAction ProcessActions(std::vector<const KeymapAction*> actions,
+ int windowId,
+ float magnitude,
+ unsigned int holdTimeMs);
+
+ /*!
+ * \brief Process actions after release event to see if an action should be dispatched
+ *
+ * \param actions All actions from the keymap defined for the current window
+ * \param windowId The current window ID
+ *
+ * \return The action to dispatch, or action with ID ACTION_NONE if no action should be dispatched
+ */
+ CAction ProcessRelease(std::vector<const KeymapAction*> actions, int windowId);
+
+ /*!
+ * \brief Process an action to see if it should be dispatched
+ *
+ * \param action The action chosen to be dispatched
+ * \param windowId The current window ID
+ * \param magnitude The magnitude or distance of the feature being handled
+ * \param holdTimeMs The time which the feature has been past the hold threshold
+ *
+ * \return The action to dispatch, or action with ID ACTION_NONE if no action should be dispatched
+ */
+ CAction ProcessAction(const KeymapAction& action,
+ int windowId,
+ float magnitude,
+ unsigned int holdTimeMs);
+
+ // Check criteria for sending a repeat action
+ bool SendRepeatAction(unsigned int holdTimeMs);
+
+ // Helper function
+ static bool IsPressed(float magnitude);
+
+ // Construction parameters
+ const std::string m_keyName;
+ IActionListener* const m_actionHandler;
+ const IKeymap* const m_keymap;
+ IKeymapHandler* const m_keymapHandler;
+
+ // State variables
+ bool m_bHeld;
+ float m_magnitude;
+ unsigned int m_holdStartTimeMs;
+ unsigned int m_lastHoldTimeMs;
+ bool m_bActionSent;
+ unsigned int m_lastActionMs;
+ int m_activeWindowId = -1; // Window that activated the key
+ CAction m_lastAction;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/keymaps/KeymapHandler.cpp b/xbmc/input/joysticks/keymaps/KeymapHandler.cpp
new file mode 100644
index 0000000..24e2a0b
--- /dev/null
+++ b/xbmc/input/joysticks/keymaps/KeymapHandler.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2017-2018 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 "KeymapHandler.h"
+
+#include "KeyHandler.h"
+#include "games/controllers/Controller.h"
+#include "input/IKeymap.h"
+#include "input/IKeymapEnvironment.h"
+#include "input/InputTranslator.h"
+#include "input/joysticks/JoystickEasterEgg.h"
+#include "input/joysticks/JoystickTranslator.h"
+#include "input/joysticks/JoystickUtils.h"
+#include "input/joysticks/interfaces/IKeyHandler.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <cmath>
+#include <utility>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+CKeymapHandler::CKeymapHandler(IActionListener* actionHandler, const IKeymap* keymap)
+ : m_actionHandler(actionHandler), m_keymap(keymap)
+{
+ assert(m_actionHandler != nullptr);
+ assert(m_keymap != nullptr);
+
+ if (m_keymap->Environment()->UseEasterEgg())
+ m_easterEgg.reset(new CJoystickEasterEgg(ControllerID()));
+}
+
+bool CKeymapHandler::HotkeysPressed(const std::set<std::string>& keyNames) const
+{
+ bool bHotkeysPressed = true;
+
+ for (const auto& hotkey : keyNames)
+ {
+ auto it = m_keyHandlers.find(hotkey);
+ if (it == m_keyHandlers.end() || !it->second->IsPressed())
+ {
+ bHotkeysPressed = false;
+ break;
+ }
+ }
+
+ return bHotkeysPressed;
+}
+
+std::string CKeymapHandler::ControllerID() const
+{
+ return m_keymap->ControllerID();
+}
+
+bool CKeymapHandler::AcceptsInput(const FeatureName& feature) const
+{
+ if (HasAction(CJoystickUtils::MakeKeyName(feature)))
+ return true;
+
+ for (auto dir : CJoystickUtils::GetAnalogStickDirections())
+ {
+ if (HasAction(CJoystickUtils::MakeKeyName(feature, dir)))
+ return true;
+ }
+
+ return false;
+}
+
+bool CKeymapHandler::OnButtonPress(const FeatureName& feature, bool bPressed)
+{
+ if (bPressed && m_easterEgg && m_easterEgg->OnButtonPress(feature))
+ return true;
+
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ return handler->OnDigitalMotion(bPressed, 0);
+}
+
+void CKeymapHandler::OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs)
+{
+ if (m_easterEgg && m_easterEgg->IsCapturing())
+ return;
+
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ handler->OnDigitalMotion(true, holdTimeMs);
+}
+
+bool CKeymapHandler::OnButtonMotion(const FeatureName& feature,
+ float magnitude,
+ unsigned int motionTimeMs)
+{
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ return handler->OnAnalogMotion(magnitude, motionTimeMs);
+}
+
+bool CKeymapHandler::OnAnalogStickMotion(const FeatureName& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs)
+{
+ using namespace INPUT;
+
+ bool bHandled = false;
+
+ // Calculate the direction of the stick's position
+ const ANALOG_STICK_DIRECTION analogStickDir = CInputTranslator::VectorToCardinalDirection(x, y);
+
+ // Calculate the magnitude projected onto that direction
+ const float magnitude = std::max(std::fabs(x), std::fabs(y));
+
+ // Deactivate directions in which the stick is not pointing first
+ for (auto dir : CJoystickUtils::GetAnalogStickDirections())
+ {
+ if (dir != analogStickDir)
+ DeactivateDirection(feature, dir);
+ }
+
+ // Now activate direction the analog stick is pointing
+ if (analogStickDir != ANALOG_STICK_DIRECTION::NONE)
+ bHandled = ActivateDirection(feature, magnitude, analogStickDir, motionTimeMs);
+
+ return bHandled;
+}
+
+bool CKeymapHandler::OnWheelMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ bool bHandled = false;
+
+ // Calculate the direction of the wheel's position
+ const WHEEL_DIRECTION direction = CJoystickTranslator::PositionToWheelDirection(position);
+
+ // Calculate the magnitude projected onto that direction
+ const float magnitude = std::fabs(position);
+
+ // Deactivate directions in which the wheel is not pointing first
+ for (auto dir : CJoystickUtils::GetWheelDirections())
+ {
+ if (dir != direction)
+ DeactivateDirection(feature, dir);
+ }
+
+ // Now activate direction in which the wheel is positioned
+ if (direction != WHEEL_DIRECTION::NONE)
+ bHandled = ActivateDirection(feature, magnitude, direction, motionTimeMs);
+
+ return bHandled;
+}
+
+bool CKeymapHandler::OnThrottleMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs)
+{
+ bool bHandled = false;
+
+ // Calculate the direction of the throttle's position
+ const THROTTLE_DIRECTION direction = CJoystickTranslator::PositionToThrottleDirection(position);
+
+ // Calculate the magnitude projected onto that direction
+ const float magnitude = std::fabs(position);
+
+ // Deactivate directions in which the throttle is not pointing first
+ for (auto dir : CJoystickUtils::GetThrottleDirections())
+ {
+ if (dir != direction)
+ DeactivateDirection(feature, dir);
+ }
+
+ // Now activate direction in which the throttle is positioned
+ if (direction != THROTTLE_DIRECTION::NONE)
+ bHandled = ActivateDirection(feature, magnitude, direction, motionTimeMs);
+
+ return bHandled;
+}
+
+bool CKeymapHandler::OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z)
+{
+ return false; //! @todo implement
+}
+
+bool CKeymapHandler::ActivateDirection(const FeatureName& feature,
+ float magnitude,
+ ANALOG_STICK_DIRECTION dir,
+ unsigned int motionTimeMs)
+{
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ return handler->OnAnalogMotion(magnitude, motionTimeMs);
+}
+
+void CKeymapHandler::DeactivateDirection(const FeatureName& feature, ANALOG_STICK_DIRECTION dir)
+{
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ handler->OnAnalogMotion(0.0f, 0);
+}
+
+bool CKeymapHandler::ActivateDirection(const FeatureName& feature,
+ float magnitude,
+ WHEEL_DIRECTION dir,
+ unsigned int motionTimeMs)
+{
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ return handler->OnAnalogMotion(magnitude, motionTimeMs);
+}
+
+void CKeymapHandler::DeactivateDirection(const FeatureName& feature, WHEEL_DIRECTION dir)
+{
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ handler->OnAnalogMotion(0.0f, 0);
+}
+
+bool CKeymapHandler::ActivateDirection(const FeatureName& feature,
+ float magnitude,
+ THROTTLE_DIRECTION dir,
+ unsigned int motionTimeMs)
+{
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ return handler->OnAnalogMotion(magnitude, motionTimeMs);
+}
+
+void CKeymapHandler::DeactivateDirection(const FeatureName& feature, THROTTLE_DIRECTION dir)
+{
+ const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir);
+
+ IKeyHandler* handler = GetKeyHandler(keyName);
+ handler->OnAnalogMotion(0.0f, 0);
+}
+
+IKeyHandler* CKeymapHandler::GetKeyHandler(const std::string& keyName)
+{
+ auto it = m_keyHandlers.find(keyName);
+ if (it == m_keyHandlers.end())
+ {
+ std::unique_ptr<IKeyHandler> handler(new CKeyHandler(keyName, m_actionHandler, m_keymap, this));
+ m_keyHandlers.insert(std::make_pair(keyName, std::move(handler)));
+ it = m_keyHandlers.find(keyName);
+ }
+
+ return it->second.get();
+}
+
+bool CKeymapHandler::HasAction(const std::string& keyName) const
+{
+ bool bHasAction = false;
+
+ const auto& actions = m_keymap->GetActions(keyName).actions;
+ for (const auto& action : actions)
+ {
+ if (HotkeysPressed(action.hotkeys))
+ {
+ bHasAction = true;
+ break;
+ }
+ }
+
+ return bHasAction;
+}
diff --git a/xbmc/input/joysticks/keymaps/KeymapHandler.h b/xbmc/input/joysticks/keymaps/KeymapHandler.h
new file mode 100644
index 0000000..a2b1d4f
--- /dev/null
+++ b/xbmc/input/joysticks/keymaps/KeymapHandler.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017-2018 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
+
+#include "input/joysticks/JoystickTypes.h"
+#include "input/joysticks/interfaces/IButtonSequence.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "input/joysticks/interfaces/IKeymapHandler.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+class IActionListener;
+class IKeymap;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IKeyHandler;
+
+/*!
+ * \ingroup joystick
+ * \brief
+ */
+class CKeymapHandler : public IKeymapHandler, public IInputHandler
+{
+public:
+ CKeymapHandler(IActionListener* actionHandler, const IKeymap* keymap);
+
+ ~CKeymapHandler() override = default;
+
+ // implementation of IKeymapHandler
+ bool HotkeysPressed(const std::set<std::string>& keyNames) const override;
+ std::string GetLastPressed() const override { return m_lastPressed; }
+ void OnPress(const std::string& keyName) override { m_lastPressed = keyName; }
+
+ // implementation of IInputHandler
+ std::string ControllerID() const override;
+ bool HasFeature(const FeatureName& feature) const override { return true; }
+ bool AcceptsInput(const FeatureName& feature) const override;
+ bool OnButtonPress(const FeatureName& feature, bool bPressed) override;
+ void OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs) override;
+ bool OnButtonMotion(const FeatureName& feature,
+ float magnitude,
+ unsigned int motionTimeMs) override;
+ bool OnAnalogStickMotion(const FeatureName& feature,
+ float x,
+ float y,
+ unsigned int motionTimeMs) override;
+ bool OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z) override;
+ bool OnWheelMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ bool OnThrottleMotion(const FeatureName& feature,
+ float position,
+ unsigned int motionTimeMs) override;
+ void OnInputFrame() override {}
+
+protected:
+ // Keep track of cheat code presses
+ std::unique_ptr<IButtonSequence> m_easterEgg;
+
+private:
+ // Analog stick helper functions
+ bool ActivateDirection(const FeatureName& feature,
+ float magnitude,
+ ANALOG_STICK_DIRECTION dir,
+ unsigned int motionTimeMs);
+ void DeactivateDirection(const FeatureName& feature, ANALOG_STICK_DIRECTION dir);
+
+ // Wheel helper functions
+ bool ActivateDirection(const FeatureName& feature,
+ float magnitude,
+ WHEEL_DIRECTION dir,
+ unsigned int motionTimeMs);
+ void DeactivateDirection(const FeatureName& feature, WHEEL_DIRECTION dir);
+
+ // Throttle helper functions
+ bool ActivateDirection(const FeatureName& feature,
+ float magnitude,
+ THROTTLE_DIRECTION dir,
+ unsigned int motionTimeMs);
+ void DeactivateDirection(const FeatureName& feature, THROTTLE_DIRECTION dir);
+
+ // Helper functions
+ IKeyHandler* GetKeyHandler(const std::string& keyName);
+ bool HasAction(const std::string& keyName) const;
+
+ // Construction parameters
+ IActionListener* const m_actionHandler;
+ const IKeymap* const m_keymap;
+
+ // Handlers for individual keys
+ std::map<std::string, std::unique_ptr<IKeyHandler>> m_keyHandlers; // Key name -> handler
+
+ // Last pressed key
+ std::string m_lastPressed;
+};
+} // namespace JOYSTICK
+} // namespace KODI
diff --git a/xbmc/input/joysticks/keymaps/KeymapHandling.cpp b/xbmc/input/joysticks/keymaps/KeymapHandling.cpp
new file mode 100644
index 0000000..b87ea68
--- /dev/null
+++ b/xbmc/input/joysticks/keymaps/KeymapHandling.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017-2018 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 "KeymapHandling.h"
+
+#include "KeymapHandler.h"
+#include "ServiceBroker.h"
+#include "input/ButtonTranslator.h"
+#include "input/InputManager.h"
+#include "input/Keymap.h"
+#include "input/joysticks/interfaces/IInputHandler.h"
+#include "input/joysticks/interfaces/IInputProvider.h"
+
+#include <algorithm>
+#include <utility>
+
+using namespace KODI;
+using namespace JOYSTICK;
+
+CKeymapHandling::CKeymapHandling(IInputProvider* inputProvider,
+ bool pPromiscuous,
+ const IKeymapEnvironment* environment)
+ : m_inputProvider(inputProvider), m_pPromiscuous(pPromiscuous), m_environment(environment)
+{
+ LoadKeymaps();
+ CServiceBroker::GetInputManager().RegisterObserver(this);
+}
+
+CKeymapHandling::~CKeymapHandling()
+{
+ CServiceBroker::GetInputManager().UnregisterObserver(this);
+ UnloadKeymaps();
+}
+
+IInputReceiver* CKeymapHandling::GetInputReceiver(const std::string& controllerId) const
+{
+ auto it = std::find_if(m_inputHandlers.begin(), m_inputHandlers.end(),
+ [&controllerId](const std::unique_ptr<IInputHandler>& inputHandler) {
+ return inputHandler->ControllerID() == controllerId;
+ });
+
+ if (it != m_inputHandlers.end())
+ return (*it)->InputReceiver();
+
+ return nullptr;
+}
+
+IKeymap* CKeymapHandling::GetKeymap(const std::string& controllerId) const
+{
+ auto it = std::find_if(m_keymaps.begin(), m_keymaps.end(),
+ [&controllerId](const std::unique_ptr<IKeymap>& keymap) {
+ return keymap->ControllerID() == controllerId;
+ });
+
+ if (it != m_keymaps.end())
+ return it->get();
+
+ return nullptr;
+}
+
+void CKeymapHandling::Notify(const Observable& obs, const ObservableMessage msg)
+{
+ if (msg == ObservableMessageButtonMapsChanged)
+ LoadKeymaps();
+}
+
+void CKeymapHandling::LoadKeymaps()
+{
+ UnloadKeymaps();
+
+ auto& inputManager = CServiceBroker::GetInputManager();
+
+ for (auto& windowKeymap : inputManager.GetJoystickKeymaps())
+ {
+ // Create keymap
+ std::unique_ptr<IKeymap> keymap(new CKeymap(std::move(windowKeymap), m_environment));
+
+ // Create keymap handler
+ std::unique_ptr<IInputHandler> inputHandler(new CKeymapHandler(&inputManager, keymap.get()));
+
+ // Register the handler with the input provider
+ m_inputProvider->RegisterInputHandler(inputHandler.get(), m_pPromiscuous);
+
+ // Save the keymap and handler
+ m_keymaps.emplace_back(std::move(keymap));
+ m_inputHandlers.emplace_back(std::move(inputHandler));
+ }
+}
+
+void CKeymapHandling::UnloadKeymaps()
+{
+ if (m_inputProvider != nullptr)
+ {
+ for (auto it = m_inputHandlers.rbegin(); it != m_inputHandlers.rend(); ++it)
+ m_inputProvider->UnregisterInputHandler(it->get());
+ }
+ m_inputHandlers.clear();
+ m_keymaps.clear();
+}
diff --git a/xbmc/input/joysticks/keymaps/KeymapHandling.h b/xbmc/input/joysticks/keymaps/KeymapHandling.h
new file mode 100644
index 0000000..fdc64b7
--- /dev/null
+++ b/xbmc/input/joysticks/keymaps/KeymapHandling.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017-2018 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
+
+#include "utils/Observer.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class IKeymap;
+class IKeymapEnvironment;
+
+namespace KODI
+{
+namespace JOYSTICK
+{
+class IInputHandler;
+class IInputProvider;
+class IInputReceiver;
+
+/*!
+ * \ingroup joystick
+ * \brief
+ */
+class CKeymapHandling : public Observer
+{
+public:
+ CKeymapHandling(IInputProvider* inputProvider,
+ bool pPromiscuous,
+ const IKeymapEnvironment* environment);
+
+ ~CKeymapHandling() override;
+
+ /*!
+ * \brief Unregister the input provider
+ *
+ * Call this if the input provider is invalidated, such as if a user
+ * disconnects a controller. This prevents accessing the invalidated
+ * input provider when keymaps are unloaded upon destruction.
+ */
+ void UnregisterInputProvider() { m_inputProvider = nullptr; }
+
+ /*!
+ * \brief
+ */
+ IInputReceiver* GetInputReceiver(const std::string& controllerId) const;
+
+ /*!
+ * \brief
+ */
+ IKeymap* GetKeymap(const std::string& controllerId) const;
+
+ // implementation of Observer
+ void Notify(const Observable& obs, const ObservableMessage msg) override;
+
+private:
+ void LoadKeymaps();
+ void UnloadKeymaps();
+
+ // Construction parameter
+ IInputProvider* m_inputProvider;
+ const bool m_pPromiscuous;
+ const IKeymapEnvironment* const m_environment;
+
+ std::vector<std::unique_ptr<IKeymap>> m_keymaps;
+ std::vector<std::unique_ptr<IInputHandler>> m_inputHandlers;
+};
+} // namespace JOYSTICK
+} // namespace KODI