2 * Copyright (C) 2016-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "AndroidJoystickState.h"
11 #include "AndroidJoystickTranslator.h"
12 #include "games/controllers/ControllerIDs.h"
13 #include "games/controllers/DefaultController.h"
14 #include "input/joysticks/DriverPrimitive.h"
15 #include "input/joysticks/JoystickTypes.h"
16 #include "input/joysticks/interfaces/IButtonMap.h"
17 #include "utils/StringUtils.h"
18 #include "utils/log.h"
24 #include <android/input.h>
25 #include <androidjni/View.h>
28 using namespace PERIPHERALS
;
33 static const std::vector
<int> ButtonKeycodes
{
34 // add the usual suspects
44 AKEYCODE_BUTTON_SELECT
,
46 AKEYCODE_BUTTON_START
,
51 AKEYCODE_BUTTON_THUMBL
,
52 AKEYCODE_BUTTON_THUMBR
,
58 // add generic gamepad buttons for controllers that Android doesn't know
76 // only add additional buttons at the end of the list
81 static const std::vector
<int> AxisIDs
{
82 AMOTION_EVENT_AXIS_HAT_X
,
83 AMOTION_EVENT_AXIS_HAT_Y
,
87 AMOTION_EVENT_AXIS_RX
,
88 AMOTION_EVENT_AXIS_RY
,
89 AMOTION_EVENT_AXIS_RZ
,
90 AMOTION_EVENT_AXIS_LTRIGGER
,
91 AMOTION_EVENT_AXIS_RTRIGGER
,
92 AMOTION_EVENT_AXIS_GAS
,
93 AMOTION_EVENT_AXIS_BRAKE
,
94 AMOTION_EVENT_AXIS_THROTTLE
,
95 AMOTION_EVENT_AXIS_RUDDER
,
96 AMOTION_EVENT_AXIS_WHEEL
,
97 AMOTION_EVENT_AXIS_GENERIC_1
,
98 AMOTION_EVENT_AXIS_GENERIC_2
,
99 AMOTION_EVENT_AXIS_GENERIC_3
,
100 AMOTION_EVENT_AXIS_GENERIC_4
,
101 AMOTION_EVENT_AXIS_GENERIC_5
,
102 AMOTION_EVENT_AXIS_GENERIC_6
,
103 AMOTION_EVENT_AXIS_GENERIC_7
,
104 AMOTION_EVENT_AXIS_GENERIC_8
,
105 AMOTION_EVENT_AXIS_GENERIC_9
,
106 AMOTION_EVENT_AXIS_GENERIC_10
,
107 AMOTION_EVENT_AXIS_GENERIC_11
,
108 AMOTION_EVENT_AXIS_GENERIC_12
,
109 AMOTION_EVENT_AXIS_GENERIC_13
,
110 AMOTION_EVENT_AXIS_GENERIC_14
,
111 AMOTION_EVENT_AXIS_GENERIC_15
,
112 AMOTION_EVENT_AXIS_GENERIC_16
,
116 static void MapAxisIds(int axisId
,
119 std::vector
<int>& axisIds
)
121 if (axisId
!= primaryAxisId
&& axisId
!= secondaryAxisId
)
126 axisIds
.emplace_back(primaryAxisId
);
127 axisIds
.emplace_back(secondaryAxisId
);
130 if (axisIds
.size() > 1)
133 if (axisId
== primaryAxisId
)
134 axisIds
.emplace_back(secondaryAxisId
);
135 else if (axisId
== secondaryAxisId
)
136 axisIds
.insert(axisIds
.begin(), primaryAxisId
);
140 CAndroidJoystickState::CAndroidJoystickState(CAndroidJoystickState
&& other
) noexcept
141 : m_deviceId(other
.m_deviceId
),
142 m_buttons(std::move(other
.m_buttons
)),
143 m_axes(std::move(other
.m_axes
)),
144 m_analogState(std::move(other
.m_analogState
)),
145 m_digitalEvents(std::move(other
.m_digitalEvents
))
149 CAndroidJoystickState::~CAndroidJoystickState()
154 bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice
& inputDevice
)
159 const std::string deviceName
= inputDevice
.getName();
162 m_deviceId
= inputDevice
.getId();
164 // get all motion ranges to be able to count the number of available buttons, hats and axis'
165 const CJNIList
<CJNIViewInputDeviceMotionRange
> motionRanges
= inputDevice
.getMotionRanges();
166 for (int index
= 0; index
< motionRanges
.size(); ++index
)
168 const CJNIViewInputDeviceMotionRange motionRange
= motionRanges
.get(index
);
169 if (!motionRange
.isFromSource(CJNIViewInputDevice::SOURCE_JOYSTICK
) &&
170 !motionRange
.isFromSource(CJNIViewInputDevice::SOURCE_GAMEPAD
))
173 "CAndroidJoystickState: axis {} has unexpected source {} for input device \"{}\" "
175 motionRange
.getAxis(), motionRange
.getSource(), deviceName
, m_deviceId
);
178 int axisId
= motionRange
.getAxis();
179 JoystickAxis axis
{{axisId
},
180 motionRange
.getFlat(),
181 motionRange
.getFuzz(),
182 motionRange
.getMin(),
183 motionRange
.getMax(),
184 motionRange
.getRange(),
185 motionRange
.getResolution()};
187 // check if the axis ID belongs to a D-pad, analogue stick, trigger or
189 if (std::find(AxisIDs
.begin(), AxisIDs
.end(), axisId
) != AxisIDs
.end())
191 CLog::Log(LOGDEBUG
, "CAndroidJoystickState: axis found: {} ({})",
192 CAndroidJoystickTranslator::TranslateAxis(axisId
), axisId
);
194 // check if this axis is already known
195 if (ContainsAxis(axisId
, m_axes
))
198 // map AMOTION_EVENT_AXIS_GAS to AMOTION_EVENT_AXIS_RTRIGGER and
199 // AMOTION_EVENT_AXIS_BRAKE to AMOTION_EVENT_AXIS_LTRIGGER
200 // to avoid duplicate events on controllers sending both events
201 MapAxisIds(axisId
, AMOTION_EVENT_AXIS_LTRIGGER
, AMOTION_EVENT_AXIS_BRAKE
, axis
.ids
);
202 MapAxisIds(axisId
, AMOTION_EVENT_AXIS_RTRIGGER
, AMOTION_EVENT_AXIS_GAS
, axis
.ids
);
204 m_axes
.emplace_back(std::move(axis
));
207 CLog::Log(LOGWARNING
,
208 "CAndroidJoystickState: ignoring unknown axis {} on input device \"{}\" with ID {}",
209 axisId
, deviceName
, m_deviceId
);
212 // check for presence of buttons
213 auto results
= inputDevice
.hasKeys(ButtonKeycodes
);
215 if (results
.size() != ButtonKeycodes
.size())
217 CLog::Log(LOGERROR
, "CAndroidJoystickState: failed to get key status for {} buttons",
218 ButtonKeycodes
.size());
222 // log positive results and assign results to buttons
223 for (unsigned int i
= 0; i
< ButtonKeycodes
.size(); ++i
)
227 const int buttonKeycode
= ButtonKeycodes
[i
];
228 CLog::Log(LOGDEBUG
, "CAndroidJoystickState: button found: {} ({})",
229 CAndroidJoystickTranslator::TranslateKeyCode(buttonKeycode
), buttonKeycode
);
230 m_buttons
.emplace_back(JoystickAxis
{{buttonKeycode
}});
234 // check if there are no buttons or axes at all
235 if (GetButtonCount() == 0 && GetAxisCount() == 0)
237 CLog::Log(LOGWARNING
,
238 "CAndroidJoystickState: no buttons, hats or axes detected for input device \"{}\" "
240 deviceName
, m_deviceId
);
244 m_analogState
.assign(GetAxisCount(), 0.0f
);
249 void CAndroidJoystickState::Deinitialize(void)
254 m_analogState
.clear();
255 m_digitalEvents
.clear();
258 bool CAndroidJoystickState::InitializeButtonMap(JOYSTICK::IButtonMap
& buttonMap
) const
260 // We only map the default controller
261 if (buttonMap
.ControllerID() != DEFAULT_CONTROLLER_ID
)
264 bool success
= false;
267 for (int buttonKeycode
: ButtonKeycodes
)
268 success
|= MapButton(buttonMap
, buttonKeycode
);
271 success
|= MapDpad(buttonMap
, AMOTION_EVENT_AXIS_HAT_X
, AMOTION_EVENT_AXIS_HAT_Y
);
274 // Note: This should come after buttons, because the PS4 controller uses
275 // both a digital button and an analog axis for the triggers, and we want
276 // the analog axis to override the button for full range of motion.
277 success
|= MapTrigger(buttonMap
, AMOTION_EVENT_AXIS_LTRIGGER
,
278 GAME::CDefaultController::FEATURE_LEFT_TRIGGER
);
279 success
|= MapTrigger(buttonMap
, AMOTION_EVENT_AXIS_RTRIGGER
,
280 GAME::CDefaultController::FEATURE_RIGHT_TRIGGER
);
283 success
|= MapAnalogStick(buttonMap
, AMOTION_EVENT_AXIS_X
, AMOTION_EVENT_AXIS_Y
,
284 GAME::CDefaultController::FEATURE_LEFT_STICK
);
285 success
|= MapAnalogStick(buttonMap
, AMOTION_EVENT_AXIS_Z
, AMOTION_EVENT_AXIS_RZ
,
286 GAME::CDefaultController::FEATURE_RIGHT_STICK
);
290 // If the controller has both L2/R2 buttons and LTRIGGER/RTRIGGER axes, it's
291 // probably a PS controller
294 size_t indexLTrigger
= 0;
295 size_t indexRTrigger
= 0;
296 if (GetAxesIndex({AKEYCODE_BUTTON_L2
}, m_buttons
, indexL2
) &&
297 GetAxesIndex({AKEYCODE_BUTTON_R2
}, m_buttons
, indexR2
) &&
298 GetAxesIndex({AMOTION_EVENT_AXIS_LTRIGGER
}, m_axes
, indexLTrigger
) &&
299 GetAxesIndex({AMOTION_EVENT_AXIS_RTRIGGER
}, m_axes
, indexRTrigger
))
301 CLog::Log(LOGDEBUG
, "Detected dual-input triggers, ignoring digital buttons");
302 std::vector
<JOYSTICK::CDriverPrimitive
> ignoredPrimitives
{
303 {JOYSTICK::PRIMITIVE_TYPE::BUTTON
, static_cast<unsigned int>(indexL2
)},
304 {JOYSTICK::PRIMITIVE_TYPE::BUTTON
, static_cast<unsigned int>(indexR2
)},
306 buttonMap
.SetIgnoredPrimitives(ignoredPrimitives
);
308 CLog::Log(LOGDEBUG
, "Setting appearance to {}", GAME::CONTROLLER_ID_PLAYSTATION
);
309 buttonMap
.SetAppearance(GAME::CONTROLLER_ID_PLAYSTATION
);
312 // Save the buttonmap
313 buttonMap
.SaveButtonMap();
319 bool CAndroidJoystickState::ProcessEvent(const AInputEvent
* event
)
321 int32_t type
= AInputEvent_getType(event
);
325 case AINPUT_EVENT_TYPE_KEY
:
327 int32_t keycode
= AKeyEvent_getKeyCode(event
);
328 int32_t action
= AKeyEvent_getAction(event
);
330 JOYSTICK_STATE_BUTTON buttonState
= JOYSTICK_STATE_BUTTON_UNPRESSED
;
331 if (action
== AKEY_EVENT_ACTION_DOWN
)
332 buttonState
= JOYSTICK_STATE_BUTTON_PRESSED
;
334 CLog::Log(LOGDEBUG
, "Android Key {} ({}) {}",
335 CAndroidJoystickTranslator::TranslateKeyCode(keycode
), keycode
,
336 (buttonState
== JOYSTICK_STATE_BUTTON_UNPRESSED
? "released" : "pressed"));
338 bool result
= SetButtonValue(keycode
, buttonState
);
343 case AINPUT_EVENT_TYPE_MOTION
:
345 size_t count
= AMotionEvent_getPointerCount(event
);
347 bool success
= false;
348 for (size_t pointer
= 0; pointer
< count
; ++pointer
)
351 for (const auto& axis
: m_axes
)
353 // get all potential values
354 std::vector
<float> values
;
355 values
.reserve(axis
.ids
.size());
356 for (const auto& axisId
: axis
.ids
)
357 values
.emplace_back(AMotionEvent_getAxisValue(event
, axisId
, pointer
));
359 // remove all zero values
360 values
.erase(std::remove(values
.begin(), values
.end(), 0.0f
), values
.end());
363 // pick the first non-zero value
365 value
= values
.front();
367 success
|= SetAxisValue(axis
.ids
, value
);
374 CLog::Log(LOGWARNING
,
375 "CAndroidJoystickState: unknown input event type {} from input device with ID {}",
383 void CAndroidJoystickState::GetEvents(std::vector
<kodi::addon::PeripheralEvent
>& events
)
385 GetButtonEvents(events
);
386 GetAxisEvents(events
);
389 void CAndroidJoystickState::GetButtonEvents(std::vector
<kodi::addon::PeripheralEvent
>& events
)
391 std::unique_lock
<CCriticalSection
> lock(m_eventMutex
);
393 // Only report a single event per button (avoids dropping rapid presses)
394 std::vector
<kodi::addon::PeripheralEvent
> repeatButtons
;
396 for (const auto& digitalEvent
: m_digitalEvents
)
398 auto HasButton
= [&digitalEvent
](const kodi::addon::PeripheralEvent
& event
)
400 if (event
.Type() == PERIPHERAL_EVENT_TYPE_DRIVER_BUTTON
)
401 return event
.DriverIndex() == digitalEvent
.DriverIndex();
405 if (std::find_if(events
.begin(), events
.end(), HasButton
) == events
.end())
406 events
.emplace_back(digitalEvent
);
408 repeatButtons
.emplace_back(digitalEvent
);
411 m_digitalEvents
.swap(repeatButtons
);
414 void CAndroidJoystickState::GetAxisEvents(std::vector
<kodi::addon::PeripheralEvent
>& events
) const
416 for (unsigned int i
= 0; i
< m_analogState
.size(); i
++)
417 events
.emplace_back(m_deviceId
, i
, m_analogState
[i
]);
420 bool CAndroidJoystickState::SetButtonValue(int axisId
, JOYSTICK_STATE_BUTTON buttonValue
)
422 size_t buttonIndex
= 0;
423 if (!GetAxesIndex({axisId
}, m_buttons
, buttonIndex
) || buttonIndex
>= GetButtonCount())
426 std::unique_lock
<CCriticalSection
> lock(m_eventMutex
);
428 m_digitalEvents
.emplace_back(m_deviceId
, buttonIndex
, buttonValue
);
433 bool CAndroidJoystickState::SetAxisValue(const std::vector
<int>& axisIds
,
434 JOYSTICK_STATE_AXIS axisValue
)
436 size_t axisIndex
= 0;
437 if (!GetAxesIndex(axisIds
, m_axes
, axisIndex
) || axisIndex
>= GetAxisCount())
440 const JoystickAxis
& axis
= m_axes
[axisIndex
];
442 // make sure that the axis value is in the valid range
443 axisValue
= Contain(axisValue
, axis
.min
, axis
.max
);
445 axisValue
= Deadzone(axisValue
, axis
.flat
);
446 // scale the axis value down to a value between -1.0f and 1.0f
447 axisValue
= Scale(axisValue
, axis
.max
, 1.0f
);
449 m_analogState
[axisIndex
] = axisValue
;
453 bool CAndroidJoystickState::MapButton(JOYSTICK::IButtonMap
& buttonMap
, int buttonKeycode
) const
455 size_t buttonIndex
= 0;
456 std::string featureName
;
458 if (!GetAxesIndex({buttonKeycode
}, m_buttons
, buttonIndex
))
461 // Check if button is already mapped
462 JOYSTICK::CDriverPrimitive buttonPrimitive
{JOYSTICK::PRIMITIVE_TYPE::BUTTON
,
463 static_cast<unsigned int>(buttonIndex
)};
464 if (buttonMap
.GetFeature(buttonPrimitive
, featureName
))
467 // Translate the button
468 std::string controllerButton
= CAndroidJoystickTranslator::TranslateJoystickButton(buttonKeycode
);
469 if (controllerButton
.empty())
472 // Check if feature is already mapped
473 if (buttonMap
.GetFeatureType(controllerButton
) != JOYSTICK::FEATURE_TYPE::UNKNOWN
)
477 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", controllerButton
,
478 buttonPrimitive
.ToString());
479 buttonMap
.AddScalar(controllerButton
, buttonPrimitive
);
484 bool CAndroidJoystickState::MapTrigger(JOYSTICK::IButtonMap
& buttonMap
,
486 const std::string
& triggerName
) const
488 size_t axisIndex
= 0;
489 std::string featureName
;
491 if (!GetAxesIndex({axisId
}, m_axes
, axisIndex
))
494 const JOYSTICK::CDriverPrimitive semiaxis
{static_cast<unsigned int>(axisIndex
), 0,
495 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE
, 1};
496 if (buttonMap
.GetFeature(semiaxis
, featureName
))
499 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", triggerName
, semiaxis
.ToString());
500 buttonMap
.AddScalar(triggerName
, semiaxis
);
505 bool CAndroidJoystickState::MapDpad(JOYSTICK::IButtonMap
& buttonMap
,
507 int vertAxisId
) const
509 bool success
= false;
511 size_t axisIndex
= 0;
512 std::string featureName
;
514 // Map horizontal axis
515 if (GetAxesIndex({horizAxisId
}, m_axes
, axisIndex
))
517 const JOYSTICK::CDriverPrimitive positiveSemiaxis
{static_cast<unsigned int>(axisIndex
), 0,
518 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE
, 1};
519 const JOYSTICK::CDriverPrimitive negativeSemiaxis
{static_cast<unsigned int>(axisIndex
), 0,
520 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE
, 1};
521 if (!buttonMap
.GetFeature(positiveSemiaxis
, featureName
) &&
522 !buttonMap
.GetFeature(negativeSemiaxis
, featureName
))
524 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_LEFT
,
525 negativeSemiaxis
.ToString());
526 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_RIGHT
,
527 positiveSemiaxis
.ToString());
528 buttonMap
.AddScalar(GAME::CDefaultController::FEATURE_LEFT
, negativeSemiaxis
);
529 buttonMap
.AddScalar(GAME::CDefaultController::FEATURE_RIGHT
, positiveSemiaxis
);
535 if (GetAxesIndex({vertAxisId
}, m_axes
, axisIndex
))
537 const JOYSTICK::CDriverPrimitive positiveSemiaxis
{static_cast<unsigned int>(axisIndex
), 0,
538 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE
, 1};
539 const JOYSTICK::CDriverPrimitive negativeSemiaxis
{static_cast<unsigned int>(axisIndex
), 0,
540 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE
, 1};
541 if (!buttonMap
.GetFeature(positiveSemiaxis
, featureName
) &&
542 !buttonMap
.GetFeature(negativeSemiaxis
, featureName
))
544 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_UP
,
545 negativeSemiaxis
.ToString());
546 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_DOWN
,
547 positiveSemiaxis
.ToString());
548 buttonMap
.AddScalar(GAME::CDefaultController::FEATURE_DOWN
, positiveSemiaxis
);
549 buttonMap
.AddScalar(GAME::CDefaultController::FEATURE_UP
, negativeSemiaxis
);
557 bool CAndroidJoystickState::MapAnalogStick(JOYSTICK::IButtonMap
& buttonMap
,
560 const std::string
& analogStickName
) const
562 size_t axisIndex1
= 0;
563 size_t axisIndex2
= 0;
564 std::string featureName
;
566 if (!GetAxesIndex({horizAxisId
}, m_axes
, axisIndex1
) ||
567 !GetAxesIndex({vertAxisId
}, m_axes
, axisIndex2
))
570 const JOYSTICK::CDriverPrimitive upSemiaxis
{static_cast<unsigned int>(axisIndex2
), 0,
571 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE
, 1};
572 const JOYSTICK::CDriverPrimitive downSemiaxis
{static_cast<unsigned int>(axisIndex2
), 0,
573 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE
, 1};
574 const JOYSTICK::CDriverPrimitive leftSemiaxis
{static_cast<unsigned int>(axisIndex1
), 0,
575 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE
, 1};
576 const JOYSTICK::CDriverPrimitive rightSemiaxis
{static_cast<unsigned int>(axisIndex1
), 0,
577 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE
, 1};
578 if (buttonMap
.GetFeature(upSemiaxis
, featureName
) ||
579 buttonMap
.GetFeature(downSemiaxis
, featureName
) ||
580 buttonMap
.GetFeature(leftSemiaxis
, featureName
) ||
581 buttonMap
.GetFeature(rightSemiaxis
, featureName
))
584 CLog::Log(LOGDEBUG
, "Automatically mapping {} to [{}, {}, {}, {}]", analogStickName
,
585 upSemiaxis
.ToString(), downSemiaxis
.ToString(), leftSemiaxis
.ToString(),
586 rightSemiaxis
.ToString());
587 buttonMap
.AddAnalogStick(analogStickName
, JOYSTICK::ANALOG_STICK_DIRECTION::UP
, upSemiaxis
);
588 buttonMap
.AddAnalogStick(analogStickName
, JOYSTICK::ANALOG_STICK_DIRECTION::DOWN
, downSemiaxis
);
589 buttonMap
.AddAnalogStick(analogStickName
, JOYSTICK::ANALOG_STICK_DIRECTION::LEFT
, leftSemiaxis
);
590 buttonMap
.AddAnalogStick(analogStickName
, JOYSTICK::ANALOG_STICK_DIRECTION::RIGHT
, rightSemiaxis
);
595 float CAndroidJoystickState::Contain(float value
, float min
, float max
)
605 float CAndroidJoystickState::Scale(float value
, float max
, float scaledMax
)
607 return value
* (scaledMax
/ max
);
610 float CAndroidJoystickState::Deadzone(float value
, float deadzone
)
612 if ((value
> 0.0f
&& value
< deadzone
) || (value
< 0.0f
&& value
> -deadzone
))
618 CAndroidJoystickState::JoystickAxes::const_iterator
CAndroidJoystickState::GetAxis(
619 const std::vector
<int>& axisIds
, const JoystickAxes
& axes
)
621 return std::find_if(axes
.cbegin(), axes
.cend(),
622 [&axisIds
](const JoystickAxis
& axis
)
624 std::vector
<int> matches(std::max(axisIds
.size(), axis
.ids
.size()));
625 const auto& matchesEnd
=
626 std::set_intersection(axisIds
.begin(), axisIds
.end(), axis
.ids
.begin(),
627 axis
.ids
.end(), matches
.begin());
628 matches
.resize(matchesEnd
- matches
.begin());
629 return !matches
.empty();
633 bool CAndroidJoystickState::ContainsAxis(int axisId
, const JoystickAxes
& axes
)
635 return GetAxis({axisId
}, axes
) != axes
.cend();
638 bool CAndroidJoystickState::GetAxesIndex(const std::vector
<int>& axisIds
,
639 const JoystickAxes
& axes
,
642 auto axesIt
= GetAxis(axisIds
, axes
);
643 if (axesIt
== axes
.end())
646 axesIndex
= std::distance(axes
.begin(), axesIt
);