diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/input/joysticks/generic/ButtonMapping.h | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/input/joysticks/generic/ButtonMapping.h')
-rw-r--r-- | xbmc/input/joysticks/generic/ButtonMapping.h | 393 |
1 files changed, 393 insertions, 0 deletions
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 |