Merge pull request #26273 from 78andyp/blurayfixes2
[xbmc.git] / xbmc / platform / android / peripherals / AndroidJoystickState.cpp
blob1cb203984ec4251c4e9241c398539ef1445ea03d
1 /*
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.
7 */
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"
20 #include <algorithm>
21 #include <mutex>
22 #include <utility>
24 #include <android/input.h>
25 #include <androidjni/View.h>
27 using namespace KODI;
28 using namespace PERIPHERALS;
30 namespace
32 // clang-format off
33 static const std::vector<int> ButtonKeycodes{
34 // add the usual suspects
35 AKEYCODE_BUTTON_A,
36 AKEYCODE_BUTTON_B,
37 AKEYCODE_BUTTON_C,
38 AKEYCODE_BUTTON_X,
39 AKEYCODE_BUTTON_Y,
40 AKEYCODE_BUTTON_Z,
41 AKEYCODE_BACK,
42 AKEYCODE_MENU,
43 AKEYCODE_HOME,
44 AKEYCODE_BUTTON_SELECT,
45 AKEYCODE_BUTTON_MODE,
46 AKEYCODE_BUTTON_START,
47 AKEYCODE_BUTTON_L1,
48 AKEYCODE_BUTTON_R1,
49 AKEYCODE_BUTTON_L2,
50 AKEYCODE_BUTTON_R2,
51 AKEYCODE_BUTTON_THUMBL,
52 AKEYCODE_BUTTON_THUMBR,
53 AKEYCODE_DPAD_UP,
54 AKEYCODE_DPAD_RIGHT,
55 AKEYCODE_DPAD_DOWN,
56 AKEYCODE_DPAD_LEFT,
57 AKEYCODE_DPAD_CENTER,
58 // add generic gamepad buttons for controllers that Android doesn't know
59 // how to map
60 AKEYCODE_BUTTON_1,
61 AKEYCODE_BUTTON_2,
62 AKEYCODE_BUTTON_3,
63 AKEYCODE_BUTTON_4,
64 AKEYCODE_BUTTON_5,
65 AKEYCODE_BUTTON_6,
66 AKEYCODE_BUTTON_7,
67 AKEYCODE_BUTTON_8,
68 AKEYCODE_BUTTON_9,
69 AKEYCODE_BUTTON_10,
70 AKEYCODE_BUTTON_11,
71 AKEYCODE_BUTTON_12,
72 AKEYCODE_BUTTON_13,
73 AKEYCODE_BUTTON_14,
74 AKEYCODE_BUTTON_15,
75 AKEYCODE_BUTTON_16,
76 // only add additional buttons at the end of the list
78 // clang-format on
80 // clang-format off
81 static const std::vector<int> AxisIDs{
82 AMOTION_EVENT_AXIS_HAT_X,
83 AMOTION_EVENT_AXIS_HAT_Y,
84 AMOTION_EVENT_AXIS_X,
85 AMOTION_EVENT_AXIS_Y,
86 AMOTION_EVENT_AXIS_Z,
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,
114 // clang-format on
116 static void MapAxisIds(int axisId,
117 int primaryAxisId,
118 int secondaryAxisId,
119 std::vector<int>& axisIds)
121 if (axisId != primaryAxisId && axisId != secondaryAxisId)
122 return;
124 if (axisIds.empty())
126 axisIds.emplace_back(primaryAxisId);
127 axisIds.emplace_back(secondaryAxisId);
130 if (axisIds.size() > 1)
131 return;
133 if (axisId == primaryAxisId)
134 axisIds.emplace_back(secondaryAxisId);
135 else if (axisId == secondaryAxisId)
136 axisIds.insert(axisIds.begin(), primaryAxisId);
138 } // namespace
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()
151 Deinitialize();
154 bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice)
156 if (!inputDevice)
157 return false;
159 const std::string deviceName = inputDevice.getName();
161 // get the device ID
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))
172 CLog::Log(LOGDEBUG,
173 "CAndroidJoystickState: axis {} has unexpected source {} for input device \"{}\" "
174 "with ID {}",
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
188 // generic axis
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))
196 continue;
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));
206 else
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());
219 return false;
222 // log positive results and assign results to buttons
223 for (unsigned int i = 0; i < ButtonKeycodes.size(); ++i)
225 if (results[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 \"{}\" "
239 "with ID {}",
240 deviceName, m_deviceId);
241 return false;
244 m_analogState.assign(GetAxisCount(), 0.0f);
246 return true;
249 void CAndroidJoystickState::Deinitialize(void)
251 m_buttons.clear();
252 m_axes.clear();
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() != GAME::DEFAULT_CONTROLLER_ID)
262 return false;
264 bool success = false;
266 // Map buttons
267 for (int buttonKeycode : ButtonKeycodes)
268 success |= MapButton(buttonMap, buttonKeycode);
270 // Map D-pad
271 success |= MapDpad(buttonMap, AMOTION_EVENT_AXIS_HAT_X, AMOTION_EVENT_AXIS_HAT_Y);
273 // Map triggers
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);
282 // Map analog sticks
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);
288 const std::string controllerId = GetAppearance();
290 // Handle PS controller triggers
291 if (controllerId == GAME::CONTROLLER_ID_PLAYSTATION)
293 size_t indexL2 = 0;
294 size_t indexR2 = 0;
295 if (GetAxesIndex({AKEYCODE_BUTTON_L2}, m_buttons, indexL2) &&
296 GetAxesIndex({AKEYCODE_BUTTON_R2}, m_buttons, indexR2))
298 CLog::Log(LOGDEBUG, "Detected dual-input triggers, ignoring digital buttons");
299 std::vector<JOYSTICK::CDriverPrimitive> ignoredPrimitives{
300 {JOYSTICK::PRIMITIVE_TYPE::BUTTON, static_cast<unsigned int>(indexL2)},
301 {JOYSTICK::PRIMITIVE_TYPE::BUTTON, static_cast<unsigned int>(indexR2)},
303 buttonMap.SetIgnoredPrimitives(ignoredPrimitives);
304 success = true;
308 if (!controllerId.empty())
310 CLog::Log(LOGDEBUG, "Setting appearance to {}", controllerId);
311 success |= buttonMap.SetAppearance(controllerId);
314 if (success)
316 // Save the buttonmap
317 buttonMap.SaveButtonMap();
320 return success;
323 std::string CAndroidJoystickState::GetAppearance() const
325 // If the controller has both L2/R2 buttons and LTRIGGER/RTRIGGER axes, it's
326 // probably a PS controller
327 size_t indexL2 = 0;
328 size_t indexR2 = 0;
329 size_t indexLTrigger = 0;
330 size_t indexRTrigger = 0;
331 if (GetAxesIndex({AKEYCODE_BUTTON_L2}, m_buttons, indexL2) &&
332 GetAxesIndex({AKEYCODE_BUTTON_R2}, m_buttons, indexR2) &&
333 GetAxesIndex({AMOTION_EVENT_AXIS_LTRIGGER}, m_axes, indexLTrigger) &&
334 GetAxesIndex({AMOTION_EVENT_AXIS_RTRIGGER}, m_axes, indexRTrigger))
336 return GAME::CONTROLLER_ID_PLAYSTATION;
339 return "";
342 bool CAndroidJoystickState::ProcessEvent(const AInputEvent* event)
344 int32_t type = AInputEvent_getType(event);
346 switch (type)
348 case AINPUT_EVENT_TYPE_KEY:
350 int32_t keycode = AKeyEvent_getKeyCode(event);
351 int32_t action = AKeyEvent_getAction(event);
353 JOYSTICK_STATE_BUTTON buttonState = JOYSTICK_STATE_BUTTON_UNPRESSED;
354 if (action == AKEY_EVENT_ACTION_DOWN)
355 buttonState = JOYSTICK_STATE_BUTTON_PRESSED;
357 CLog::Log(LOGDEBUG, "Android Key {} ({}) {}",
358 CAndroidJoystickTranslator::TranslateKeyCode(keycode), keycode,
359 (buttonState == JOYSTICK_STATE_BUTTON_UNPRESSED ? "released" : "pressed"));
361 bool result = SetButtonValue(keycode, buttonState);
363 return result;
366 case AINPUT_EVENT_TYPE_MOTION:
368 size_t count = AMotionEvent_getPointerCount(event);
370 bool success = false;
371 for (size_t pointer = 0; pointer < count; ++pointer)
373 // process all axes
374 for (const auto& axis : m_axes)
376 // get all potential values
377 std::vector<float> values;
378 values.reserve(axis.ids.size());
379 for (const auto& axisId : axis.ids)
380 values.emplace_back(AMotionEvent_getAxisValue(event, axisId, pointer));
382 // remove all zero values
383 values.erase(std::remove(values.begin(), values.end(), 0.0f), values.end());
385 float value = 0.0f;
386 // pick the first non-zero value
387 if (!values.empty())
388 value = values.front();
390 success |= SetAxisValue(axis.ids, value);
393 return success;
396 default:
397 CLog::Log(LOGWARNING,
398 "CAndroidJoystickState: unknown input event type {} from input device with ID {}",
399 type, m_deviceId);
400 break;
403 return false;
406 void CAndroidJoystickState::GetEvents(std::vector<kodi::addon::PeripheralEvent>& events)
408 GetButtonEvents(events);
409 GetAxisEvents(events);
412 void CAndroidJoystickState::GetButtonEvents(std::vector<kodi::addon::PeripheralEvent>& events)
414 std::unique_lock<CCriticalSection> lock(m_eventMutex);
416 // Only report a single event per button (avoids dropping rapid presses)
417 std::vector<kodi::addon::PeripheralEvent> repeatButtons;
419 for (const auto& digitalEvent : m_digitalEvents)
421 auto HasButton = [&digitalEvent](const kodi::addon::PeripheralEvent& event)
423 if (event.Type() == PERIPHERAL_EVENT_TYPE_DRIVER_BUTTON)
424 return event.DriverIndex() == digitalEvent.DriverIndex();
425 return false;
428 if (std::find_if(events.begin(), events.end(), HasButton) == events.end())
429 events.emplace_back(digitalEvent);
430 else
431 repeatButtons.emplace_back(digitalEvent);
434 m_digitalEvents.swap(repeatButtons);
437 void CAndroidJoystickState::GetAxisEvents(std::vector<kodi::addon::PeripheralEvent>& events) const
439 for (unsigned int i = 0; i < m_analogState.size(); i++)
440 events.emplace_back(m_deviceId, i, m_analogState[i]);
443 bool CAndroidJoystickState::SetButtonValue(int axisId, JOYSTICK_STATE_BUTTON buttonValue)
445 size_t buttonIndex = 0;
446 if (!GetAxesIndex({axisId}, m_buttons, buttonIndex) || buttonIndex >= GetButtonCount())
447 return false;
449 std::unique_lock<CCriticalSection> lock(m_eventMutex);
451 m_digitalEvents.emplace_back(m_deviceId, buttonIndex, buttonValue);
453 return true;
456 bool CAndroidJoystickState::SetAxisValue(const std::vector<int>& axisIds,
457 JOYSTICK_STATE_AXIS axisValue)
459 size_t axisIndex = 0;
460 if (!GetAxesIndex(axisIds, m_axes, axisIndex) || axisIndex >= GetAxisCount())
461 return false;
463 const JoystickAxis& axis = m_axes[axisIndex];
465 // make sure that the axis value is in the valid range
466 axisValue = Contain(axisValue, axis.min, axis.max);
467 // apply deadzoning
468 axisValue = Deadzone(axisValue, axis.flat);
469 // scale the axis value down to a value between -1.0f and 1.0f
470 axisValue = Scale(axisValue, axis.max, 1.0f);
472 m_analogState[axisIndex] = axisValue;
473 return true;
476 bool CAndroidJoystickState::MapButton(JOYSTICK::IButtonMap& buttonMap, int buttonKeycode) const
478 size_t buttonIndex = 0;
479 std::string featureName;
481 if (!GetAxesIndex({buttonKeycode}, m_buttons, buttonIndex))
482 return false;
484 // Check if button is already mapped
485 JOYSTICK::CDriverPrimitive buttonPrimitive{JOYSTICK::PRIMITIVE_TYPE::BUTTON,
486 static_cast<unsigned int>(buttonIndex)};
487 if (buttonMap.GetFeature(buttonPrimitive, featureName))
488 return false;
490 // Translate the button
491 std::string controllerButton = CAndroidJoystickTranslator::TranslateJoystickButton(buttonKeycode);
492 if (controllerButton.empty())
493 return false;
495 // Check if feature is already mapped
496 if (buttonMap.GetFeatureType(controllerButton) != JOYSTICK::FEATURE_TYPE::UNKNOWN)
497 return false;
499 // Map the button
500 CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", controllerButton,
501 buttonPrimitive.ToString());
502 buttonMap.AddScalar(controllerButton, buttonPrimitive);
504 return true;
507 bool CAndroidJoystickState::MapTrigger(JOYSTICK::IButtonMap& buttonMap,
508 int axisId,
509 const std::string& triggerName) const
511 size_t axisIndex = 0;
512 std::string featureName;
514 if (!GetAxesIndex({axisId}, m_axes, axisIndex))
515 return false;
517 const JOYSTICK::CDriverPrimitive semiaxis{static_cast<unsigned int>(axisIndex), 0,
518 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1};
519 if (buttonMap.GetFeature(semiaxis, featureName))
520 return false;
522 CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", triggerName, semiaxis.ToString());
523 buttonMap.AddScalar(triggerName, semiaxis);
525 return true;
528 bool CAndroidJoystickState::MapDpad(JOYSTICK::IButtonMap& buttonMap,
529 int horizAxisId,
530 int vertAxisId) const
532 bool success = false;
534 size_t axisIndex = 0;
535 std::string featureName;
537 // Map horizontal axis
538 if (GetAxesIndex({horizAxisId}, m_axes, axisIndex))
540 const JOYSTICK::CDriverPrimitive positiveSemiaxis{static_cast<unsigned int>(axisIndex), 0,
541 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1};
542 const JOYSTICK::CDriverPrimitive negativeSemiaxis{static_cast<unsigned int>(axisIndex), 0,
543 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1};
544 if (!buttonMap.GetFeature(positiveSemiaxis, featureName) &&
545 !buttonMap.GetFeature(negativeSemiaxis, featureName))
547 CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_LEFT,
548 negativeSemiaxis.ToString());
549 CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_RIGHT,
550 positiveSemiaxis.ToString());
551 buttonMap.AddScalar(GAME::CDefaultController::FEATURE_LEFT, negativeSemiaxis);
552 buttonMap.AddScalar(GAME::CDefaultController::FEATURE_RIGHT, positiveSemiaxis);
553 success |= true;
557 // Map vertical axis
558 if (GetAxesIndex({vertAxisId}, m_axes, axisIndex))
560 const JOYSTICK::CDriverPrimitive positiveSemiaxis{static_cast<unsigned int>(axisIndex), 0,
561 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1};
562 const JOYSTICK::CDriverPrimitive negativeSemiaxis{static_cast<unsigned int>(axisIndex), 0,
563 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1};
564 if (!buttonMap.GetFeature(positiveSemiaxis, featureName) &&
565 !buttonMap.GetFeature(negativeSemiaxis, featureName))
567 CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_UP,
568 negativeSemiaxis.ToString());
569 CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_DOWN,
570 positiveSemiaxis.ToString());
571 buttonMap.AddScalar(GAME::CDefaultController::FEATURE_DOWN, positiveSemiaxis);
572 buttonMap.AddScalar(GAME::CDefaultController::FEATURE_UP, negativeSemiaxis);
573 success |= true;
577 return success;
580 bool CAndroidJoystickState::MapAnalogStick(JOYSTICK::IButtonMap& buttonMap,
581 int horizAxisId,
582 int vertAxisId,
583 const std::string& analogStickName) const
585 size_t axisIndex1 = 0;
586 size_t axisIndex2 = 0;
587 std::string featureName;
589 if (!GetAxesIndex({horizAxisId}, m_axes, axisIndex1) ||
590 !GetAxesIndex({vertAxisId}, m_axes, axisIndex2))
591 return false;
593 const JOYSTICK::CDriverPrimitive upSemiaxis{static_cast<unsigned int>(axisIndex2), 0,
594 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1};
595 const JOYSTICK::CDriverPrimitive downSemiaxis{static_cast<unsigned int>(axisIndex2), 0,
596 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1};
597 const JOYSTICK::CDriverPrimitive leftSemiaxis{static_cast<unsigned int>(axisIndex1), 0,
598 JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1};
599 const JOYSTICK::CDriverPrimitive rightSemiaxis{static_cast<unsigned int>(axisIndex1), 0,
600 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1};
601 if (buttonMap.GetFeature(upSemiaxis, featureName) ||
602 buttonMap.GetFeature(downSemiaxis, featureName) ||
603 buttonMap.GetFeature(leftSemiaxis, featureName) ||
604 buttonMap.GetFeature(rightSemiaxis, featureName))
605 return false;
607 CLog::Log(LOGDEBUG, "Automatically mapping {} to [{}, {}, {}, {}]", analogStickName,
608 upSemiaxis.ToString(), downSemiaxis.ToString(), leftSemiaxis.ToString(),
609 rightSemiaxis.ToString());
610 buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::UP, upSemiaxis);
611 buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::DOWN, downSemiaxis);
612 buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::LEFT, leftSemiaxis);
613 buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::RIGHT, rightSemiaxis);
615 return true;
618 float CAndroidJoystickState::Contain(float value, float min, float max)
620 if (value < min)
621 return min;
622 if (value > max)
623 return max;
625 return value;
628 float CAndroidJoystickState::Scale(float value, float max, float scaledMax)
630 return value * (scaledMax / max);
633 float CAndroidJoystickState::Deadzone(float value, float deadzone)
635 if ((value > 0.0f && value < deadzone) || (value < 0.0f && value > -deadzone))
636 return 0.0f;
638 return value;
641 CAndroidJoystickState::JoystickAxes::const_iterator CAndroidJoystickState::GetAxis(
642 const std::vector<int>& axisIds, const JoystickAxes& axes)
644 return std::find_if(axes.cbegin(), axes.cend(),
645 [&axisIds](const JoystickAxis& axis)
647 std::vector<int> matches(std::max(axisIds.size(), axis.ids.size()));
648 const auto& matchesEnd =
649 std::set_intersection(axisIds.begin(), axisIds.end(), axis.ids.begin(),
650 axis.ids.end(), matches.begin());
651 matches.resize(matchesEnd - matches.begin());
652 return !matches.empty();
656 bool CAndroidJoystickState::ContainsAxis(int axisId, const JoystickAxes& axes)
658 return GetAxis({axisId}, axes) != axes.cend();
661 bool CAndroidJoystickState::GetAxesIndex(const std::vector<int>& axisIds,
662 const JoystickAxes& axes,
663 size_t& axesIndex)
665 auto axesIt = GetAxis(axisIds, axes);
666 if (axesIt == axes.end())
667 return false;
669 axesIndex = std::distance(axes.begin(), axesIt);
670 return true;