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() != GAME::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
);
288 const std::string controllerId
= GetAppearance();
290 // Handle PS controller triggers
291 if (controllerId
== GAME::CONTROLLER_ID_PLAYSTATION
)
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
);
308 if (!controllerId
.empty())
310 CLog::Log(LOGDEBUG
, "Setting appearance to {}", controllerId
);
311 success
|= buttonMap
.SetAppearance(controllerId
);
316 // Save the buttonmap
317 buttonMap
.SaveButtonMap();
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
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
;
342 bool CAndroidJoystickState::ProcessEvent(const AInputEvent
* event
)
344 int32_t type
= AInputEvent_getType(event
);
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
);
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
)
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());
386 // pick the first non-zero value
388 value
= values
.front();
390 success
|= SetAxisValue(axis
.ids
, value
);
397 CLog::Log(LOGWARNING
,
398 "CAndroidJoystickState: unknown input event type {} from input device with ID {}",
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();
428 if (std::find_if(events
.begin(), events
.end(), HasButton
) == events
.end())
429 events
.emplace_back(digitalEvent
);
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())
449 std::unique_lock
<CCriticalSection
> lock(m_eventMutex
);
451 m_digitalEvents
.emplace_back(m_deviceId
, buttonIndex
, buttonValue
);
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())
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
);
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
;
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
))
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
))
490 // Translate the button
491 std::string controllerButton
= CAndroidJoystickTranslator::TranslateJoystickButton(buttonKeycode
);
492 if (controllerButton
.empty())
495 // Check if feature is already mapped
496 if (buttonMap
.GetFeatureType(controllerButton
) != JOYSTICK::FEATURE_TYPE::UNKNOWN
)
500 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", controllerButton
,
501 buttonPrimitive
.ToString());
502 buttonMap
.AddScalar(controllerButton
, buttonPrimitive
);
507 bool CAndroidJoystickState::MapTrigger(JOYSTICK::IButtonMap
& buttonMap
,
509 const std::string
& triggerName
) const
511 size_t axisIndex
= 0;
512 std::string featureName
;
514 if (!GetAxesIndex({axisId
}, m_axes
, axisIndex
))
517 const JOYSTICK::CDriverPrimitive semiaxis
{static_cast<unsigned int>(axisIndex
), 0,
518 JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE
, 1};
519 if (buttonMap
.GetFeature(semiaxis
, featureName
))
522 CLog::Log(LOGDEBUG
, "Automatically mapping {} to {}", triggerName
, semiaxis
.ToString());
523 buttonMap
.AddScalar(triggerName
, semiaxis
);
528 bool CAndroidJoystickState::MapDpad(JOYSTICK::IButtonMap
& buttonMap
,
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
);
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
);
580 bool CAndroidJoystickState::MapAnalogStick(JOYSTICK::IButtonMap
& buttonMap
,
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
))
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
))
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
);
618 float CAndroidJoystickState::Contain(float value
, float min
, float max
)
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
))
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
,
665 auto axesIt
= GetAxis(axisIds
, axes
);
666 if (axesIt
== axes
.end())
669 axesIndex
= std::distance(axes
.begin(), axesIt
);