6#include <QtCore/QMetaObject>
7#include <QtCore/QThread>
34 switch (event->type) {
35 case SDL_EVENT_JOYSTICK_ADDED:
36 qCInfo(JoystickSDLLog) <<
"SDL event: Joystick added, instance ID:" <<
event->jdevice.which;
37 QMetaObject::invokeMethod(manager,
"_checkForAddedOrRemovedJoysticks", Qt::QueuedConnection);
39 case SDL_EVENT_JOYSTICK_REMOVED:
40 qCInfo(JoystickSDLLog) <<
"SDL event: Joystick removed, instance ID:" <<
event->jdevice.which;
41 QMetaObject::invokeMethod(manager,
"_checkForAddedOrRemovedJoysticks", Qt::QueuedConnection);
44 case SDL_EVENT_GAMEPAD_ADDED:
45 qCDebug(JoystickSDLLog) <<
"SDL event: Gamepad added (ignored, handled via joystick event), instance ID:" <<
event->gdevice.which;
47 case SDL_EVENT_GAMEPAD_REMOVED:
48 qCDebug(JoystickSDLLog) <<
"SDL event: Gamepad removed (ignored, handled via joystick event), instance ID:" <<
event->gdevice.which;
51 case SDL_EVENT_JOYSTICK_BATTERY_UPDATED:
52 qCDebug(JoystickSDLLog) <<
"Battery updated for joystick" <<
event->jbattery.which
53 <<
"state:" <<
event->jbattery.state
54 <<
"percent:" <<
event->jbattery.percent;
55 QMetaObject::invokeMethod(manager,
"_handleBatteryUpdated",
57 Q_ARG(
int,
static_cast<int>(event->jbattery.which)));
60 case SDL_EVENT_GAMEPAD_REMAPPED:
61 qCDebug(JoystickSDLLog) <<
"Gamepad remapped:" <<
event->gdevice.which;
62 QMetaObject::invokeMethod(manager,
"_handleGamepadRemapped",
64 Q_ARG(
int,
static_cast<int>(event->gdevice.which)));
67 case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
68 case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
69 case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
70 QMetaObject::invokeMethod(manager,
"_handleTouchpadEvent",
72 Q_ARG(
int,
static_cast<int>(event->gtouchpad.which)),
73 Q_ARG(
int, event->gtouchpad.touchpad),
74 Q_ARG(
int, event->gtouchpad.finger),
75 Q_ARG(
bool, event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP),
76 Q_ARG(
float, event->gtouchpad.x),
77 Q_ARG(
float, event->gtouchpad.y),
78 Q_ARG(
float, event->gtouchpad.pressure));
81 case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
82 QMetaObject::invokeMethod(manager,
"_handleSensorUpdate",
84 Q_ARG(
int,
static_cast<int>(event->gsensor.which)),
85 Q_ARG(
int, event->gsensor.sensor),
86 Q_ARG(
float, event->gsensor.data[0]),
87 Q_ARG(
float, event->gsensor.data[1]),
88 Q_ARG(
float, event->gsensor.data[2]));
91 case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
92 case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE:
93 QMetaObject::invokeMethod(manager,
"_handleUpdateComplete",
95 Q_ARG(
int,
static_cast<int>(event->common.type == SDL_EVENT_GAMEPAD_UPDATE_COMPLETE
96 ? event->gdevice.which :
event->jdevice.which)));
106JoystickSDL::JoystickSDL(
const QString &name,
const QList<int> &gamepadAxes,
const QList<int> &nonGamepadAxes,
int buttonCount,
int hatCount,
int instanceId, QObject *parent)
107 :
Joystick(name, gamepadAxes.length() + nonGamepadAxes.length()
108#ifdef TEST_WITH_VIRTUAL_AXES
111 , buttonCount, hatCount, parent)
112 , _gamepadAxes(gamepadAxes)
113 , _nonGamepadAxes(nonGamepadAxes)
114 , _instanceId(instanceId)
116 qCDebug(JoystickSDLLog) <<
this;
121 qCDebug(JoystickSDLLog) <<
this;
124quint64 JoystickSDL::_getProperties()
const
127 return SDL_GetGamepadProperties(_sdlGamepad);
130 return SDL_GetJoystickProperties(_sdlJoystick);
135bool JoystickSDL::_checkVirtualJoystick(
const char *methodName)
const
138 qCWarning(JoystickSDLLog) << methodName <<
"called with null joystick";
142 qCWarning(JoystickSDLLog) << methodName <<
"called on non-virtual joystick:" <<
_name;
148bool JoystickSDL::_hasGamepadCapability(
const char *propertyName)
const
151 SDL_PropertiesID props = SDL_GetGamepadProperties(_sdlGamepad);
152 return SDL_GetBooleanProperty(props, propertyName,
false);
172 if (deleteDiscoveryCache) {
180 Q_ASSERT(QThread::isMainThread());
182 QMap<QString, Joystick*> current;
184 qCDebug(JoystickSDLLog) <<
"Discovering joysticks";
192 SDL_JoystickID *ids = SDL_GetJoysticks(&count);
194 qCWarning(JoystickSDLLog) <<
"SDL_GetJoysticks failed:" << SDL_GetError();
198 qCDebug(JoystickSDLLog) <<
"SDL_GetJoysticks returned" << count <<
"joysticks";
199 for (
int n = 0; n < count; ++n) {
201 qCDebug(JoystickSDLLog) <<
" [" << n <<
"] ID:" << ids[n]
202 <<
"Name:" << joystickName
203 <<
"IsGamepad:" << SDL_IsGamepad(ids[n]);
206 for (
int n = 0; n < count; ++n) {
207 const SDL_JoystickID jid = ids[n];
209 if (baseName.isEmpty()) {
210 baseName = QStringLiteral(
"Joystick %1").arg(jid);
212 QString
name = baseName;
215 bool foundInCache =
false;
217 auto *cachedJs =
static_cast<JoystickSDL*
>(it.value());
218 if (
static_cast<SDL_JoystickID
>(cachedJs->instanceId()) == jid) {
219 if (cachedJs->name() == baseName) {
221 current[
name] = cachedJs;
227 cachedJs->deleteLater();
247 int duplicateIndex = 2;
249 name = QStringLiteral(
"%1 #%2").arg(baseName).arg(duplicateIndex++);
252 QList<int> gamepadAxes;
253 QSet<int> joyAxesMappedToGamepad;
255 if (SDL_IsGamepad(jid)) {
256 auto tmpGamepad = SDL_OpenGamepad(jid);
258 qCWarning(JoystickSDLLog) <<
"Failed to open gamepad" << jid << SDL_GetError();
263 for (
int i = 0; i < SDL_GAMEPAD_AXIS_COUNT; i++) {
264 if (SDL_GamepadHasAxis(tmpGamepad,
static_cast<SDL_GamepadAxis
>(i))) {
265 gamepadAxes.append(i);
272 int bindingCount = 0;
273 SDL_GamepadBinding **bindings = SDL_GetGamepadBindings(tmpGamepad, &bindingCount);
275 for (
int i = 0; i < bindingCount; ++i) {
276 SDL_GamepadBinding *binding = bindings[i];
277 if (binding && binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS && binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
278 joyAxesMappedToGamepad.insert(binding->input.axis.axis);
283 qCWarning(JoystickSDLLog) <<
"Failed to get bindings for" <<
name <<
"error:" << SDL_GetError();
286 SDL_CloseGamepad(tmpGamepad);
291 qCWarning(JoystickSDLLog) <<
"Failed to open joystick" << jid << SDL_GetError();
295 QList<int> nonGamepadAxes;
296 const int axisCount = SDL_GetNumJoystickAxes(tmpJoy);
298 if (!joyAxesMappedToGamepad.contains(i)) {
299 nonGamepadAxes.append(i);
303 const int buttonCount = SDL_GetNumJoystickButtons(tmpJoy);
304 const int hatCount = SDL_GetNumJoystickHats(tmpJoy);
305 SDL_CloseJoystick(tmpJoy);
307 qCDebug(JoystickSDLLog) <<
"Creating JoystickSDL for" <<
name <<
"jid:" << jid;
319 qCDebug(JoystickSDLLog) <<
"Discovered" << current.size() <<
"joysticks:";
320 for (
auto it = current.begin(); it != current.end(); ++it) {
322 qCDebug(JoystickSDLLog) <<
" " << it.key() <<
"instanceId:" << js->
instanceId();
326 joystick->deleteLater();
337bool JoystickSDL::_open()
340 _sdlGamepad = SDL_OpenGamepad(_instanceId);
342 qCWarning(JoystickSDLLog) <<
"SDL_OpenGamepad failed:" << SDL_GetError();
345 _sdlJoystick = SDL_GetGamepadJoystick(_sdlGamepad);
347 _sdlJoystick = SDL_OpenJoystick(_instanceId);
351 qCWarning(JoystickSDLLog) <<
"SDL_JoystickOpen failed:" << SDL_GetError();
355 qCDebug(JoystickSDLLog) <<
"Opened" << SDL_GetJoystickName(_sdlJoystick) <<
"joystick at" << _sdlJoystick;
360void JoystickSDL::_close()
363 qCWarning(JoystickSDLLog) <<
"Attempt to close null joystick!";
367 qCDebug(JoystickSDLLog) <<
"Closing joystick" <<
_name <<
"at" << _sdlJoystick;
370 SDL_CloseHaptic(_sdlHaptic);
371 _sdlHaptic =
nullptr;
375 SDL_CloseGamepad(_sdlGamepad);
377 SDL_CloseJoystick(_sdlJoystick);
380 _sdlJoystick =
nullptr;
381 _sdlGamepad =
nullptr;
384bool JoystickSDL::_update()
386 if (!_sdlJoystick || !SDL_JoystickConnected(_sdlJoystick)) {
387 qCWarning(JoystickSDLLog) <<
"Joystick disconnected during update:" <<
_name;
392 SDL_UpdateGamepads();
394 SDL_UpdateJoysticks();
404bool JoystickSDL::_getButton(
int idx)
const
407 if (_sdlGamepad && (idx >= 0) && (idx < SDL_GAMEPAD_BUTTON_COUNT)) {
408 return SDL_GetGamepadButton(_sdlGamepad,
static_cast<SDL_GamepadButton
>(idx));
412 if (_sdlJoystick && (idx >= 0) && (idx < SDL_GetNumJoystickButtons(_sdlJoystick))) {
413 return SDL_GetJoystickButton(_sdlJoystick, idx);
419int JoystickSDL::_getAxisValue(
int idx)
const
425#ifdef TEST_WITH_VIRTUAL_AXES
427 const int totalPhysicalAxes = _gamepadAxes.length() + _nonGamepadAxes.length();
428 if (idx >= totalPhysicalAxes && idx < totalPhysicalAxes + 2) {
430 bool button0Down =
false;
432 button0Down = SDL_GetGamepadButton(_sdlGamepad,
static_cast<SDL_GamepadButton
>(0));
433 }
else if (_sdlJoystick) {
434 button0Down = SDL_GetJoystickButton(_sdlJoystick, 0);
439 const int virtualAxisIdx = idx - totalPhysicalAxes;
440 if (virtualAxisIdx == 0) {
442 if (_sdlGamepad && _gamepadAxes.length() > 0) {
443 return SDL_GetGamepadAxis(_sdlGamepad,
static_cast<SDL_GamepadAxis
>(_gamepadAxes[0]));
444 }
else if (_sdlJoystick) {
445 return SDL_GetJoystickAxis(_sdlJoystick, 0);
449 if (_sdlGamepad && _gamepadAxes.length() > 1) {
450 return SDL_GetGamepadAxis(_sdlGamepad,
static_cast<SDL_GamepadAxis
>(_gamepadAxes[1]));
451 }
else if (_sdlJoystick) {
452 return SDL_GetJoystickAxis(_sdlJoystick, 1);
463 if (idx < _gamepadAxes.length()) {
464 return SDL_GetGamepadAxis(_sdlGamepad,
static_cast<SDL_GamepadAxis
>(_gamepadAxes[idx]));
466 const int nonGamepadIdx = idx -
static_cast<int>(_gamepadAxes.length());
467 if (nonGamepadIdx < _nonGamepadAxes.length()) {
468 return SDL_GetJoystickAxis(_sdlJoystick, _nonGamepadAxes[nonGamepadIdx]);
473 return SDL_GetJoystickAxis(_sdlJoystick, idx);
476bool JoystickSDL::_getHat(
int hat,
int idx)
const
478 static constexpr std::array<uint8_t, 4> hatButtons = {SDL_HAT_UP, SDL_HAT_DOWN, SDL_HAT_LEFT, SDL_HAT_RIGHT};
480 if (idx < 0 ||
static_cast<size_t>(idx) >= hatButtons.size()) {
484 return ((SDL_GetJoystickHat(_sdlJoystick, hat) & hatButtons[idx]) != 0);
493 const quint64 props = _getProperties();
494 return props != 0 && SDL_GetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN,
false);
499 const quint64 props = _getProperties();
500 return props != 0 && SDL_GetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN,
false);
505 const quint64 props = _getProperties();
509 return SDL_GetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN,
false) ||
510 SDL_GetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN,
false);
516 if (!SDL_RumbleGamepad(_sdlGamepad, lowFreq, highFreq, durationMs)) {
517 qCDebug(JoystickSDLLog) <<
"Rumble failed:" << SDL_GetError();
519 }
else if (_sdlJoystick) {
520 if (!SDL_RumbleJoystick(_sdlJoystick, lowFreq, highFreq, durationMs)) {
521 qCDebug(JoystickSDLLog) <<
"Rumble failed:" << SDL_GetError();
529 if (!SDL_RumbleGamepadTriggers(_sdlGamepad, left, right, durationMs)) {
530 qCDebug(JoystickSDLLog) <<
"Trigger rumble failed:" << SDL_GetError();
538 if (!SDL_SetGamepadLED(_sdlGamepad, red, green, blue)) {
539 qCDebug(JoystickSDLLog) <<
"Set LED failed:" << SDL_GetError();
541 }
else if (_sdlJoystick) {
542 if (!SDL_SetJoystickLED(_sdlJoystick, red, green, blue)) {
543 qCDebug(JoystickSDLLog) <<
"Set LED failed:" << SDL_GetError();
550 if (_sdlGamepad && !data.isEmpty()) {
551 if (!SDL_SendGamepadEffect(_sdlGamepad, data.constData(),
static_cast<int>(data.size()))) {
552 qCDebug(JoystickSDLLog) <<
"sendEffect failed:" << SDL_GetError();
567 SDL_GUID sdlGuid = SDL_GetJoystickGUID(_sdlJoystick);
569 SDL_GUIDToString(sdlGuid, guidStr,
sizeof(guidStr));
570 return QString::fromLatin1(guidStr);
578 return SDL_GetJoystickVendor(_sdlJoystick);
586 return SDL_GetJoystickProduct(_sdlJoystick);
594 const char *serialStr = SDL_GetJoystickSerial(_sdlJoystick);
596 return QString::fromUtf8(serialStr);
605 SDL_JoystickType type = SDL_GetJoystickType(_sdlJoystick);
607 case SDL_JOYSTICK_TYPE_GAMEPAD:
608 return QStringLiteral(
"Gamepad");
609 case SDL_JOYSTICK_TYPE_WHEEL:
610 return QStringLiteral(
"Wheel");
611 case SDL_JOYSTICK_TYPE_ARCADE_STICK:
612 return QStringLiteral(
"Arcade Stick");
613 case SDL_JOYSTICK_TYPE_FLIGHT_STICK:
614 return QStringLiteral(
"Flight Stick");
615 case SDL_JOYSTICK_TYPE_DANCE_PAD:
616 return QStringLiteral(
"Dance Pad");
617 case SDL_JOYSTICK_TYPE_GUITAR:
618 return QStringLiteral(
"Guitar");
619 case SDL_JOYSTICK_TYPE_DRUM_KIT:
620 return QStringLiteral(
"Drum Kit");
621 case SDL_JOYSTICK_TYPE_ARCADE_PAD:
622 return QStringLiteral(
"Arcade Pad");
623 case SDL_JOYSTICK_TYPE_THROTTLE:
624 return QStringLiteral(
"Throttle");
626 return QStringLiteral(
"Unknown");
635 const char *devicePath = SDL_GetJoystickPath(_sdlJoystick);
637 return QString::fromUtf8(devicePath);
645 return SDL_IsJoystickVirtual(_instanceId);
651 return SDL_GetJoystickFirmwareVersion(_sdlJoystick);
674 return SDL_GetGamepadPlayerIndex(_sdlGamepad);
677 return SDL_GetJoystickPlayerIndex(_sdlJoystick);
684 bool success =
false;
686 success = SDL_SetGamepadPlayerIndex(_sdlGamepad, index);
687 }
else if (_sdlJoystick) {
688 success = SDL_SetJoystickPlayerIndex(_sdlJoystick, index);
694 qCDebug(JoystickSDLLog) <<
"Failed to set player index:" << SDL_GetError();
706 SDL_GetJoystickPowerInfo(_sdlJoystick, &percent);
718 switch (SDL_GetJoystickPowerInfo(_sdlJoystick,
nullptr)) {
719 case SDL_POWERSTATE_ERROR:
720 return QStringLiteral(
"Error");
721 case SDL_POWERSTATE_UNKNOWN:
722 return QStringLiteral(
"Unknown");
723 case SDL_POWERSTATE_ON_BATTERY:
724 return QStringLiteral(
"On Battery");
725 case SDL_POWERSTATE_NO_BATTERY:
726 return QStringLiteral(
"No Battery");
727 case SDL_POWERSTATE_CHARGING:
728 return QStringLiteral(
"Charging");
729 case SDL_POWERSTATE_CHARGED:
730 return QStringLiteral(
"Charged");
742 return SDL_IsGamepad(_instanceId);
760 return tr(
"Axis %1").arg(axis);
764 if (_sdlGamepad && axis < _gamepadAxes.length()) {
765 const char *label = SDL_GetGamepadStringForAxis(
static_cast<SDL_GamepadAxis
>(_gamepadAxes[axis]));
767 return QString::fromUtf8(label);
772 const int nonGamepadIdx = axis - _gamepadAxes.length();
773 if (nonGamepadIdx >= 0 && nonGamepadIdx < _nonGamepadAxes.length()) {
775 return tr(
"Axis %1").arg(_nonGamepadAxes[nonGamepadIdx]);
778 return tr(
"Axis %1").arg(axis);
783 if (_sdlGamepad && button >= 0 && button < SDL_GAMEPAD_BUTTON_COUNT) {
784 const char *label = SDL_GetGamepadStringForButton(
static_cast<SDL_GamepadButton
>(button));
786 return QString::fromUtf8(label);
789 return tr(
"Button %1").arg(button);
799 char *mapping = SDL_GetGamepadMapping(_sdlGamepad);
801 QString result = QString::fromUtf8(mapping);
811 if (mapping.isEmpty()) {
815 int result = SDL_AddGamepadMapping(qPrintable(mapping));
817 qCWarning(JoystickSDLLog) <<
"Failed to add gamepad mapping:" << SDL_GetError();
821 qCDebug(JoystickSDLLog) <<
"Added gamepad mapping, result:" << result;
828 result[QStringLiteral(
"valid")] =
false;
835 if (_sdlGamepad && axis < _gamepadAxes.length()) {
836 const SDL_GamepadAxis gamepadAxis =
static_cast<SDL_GamepadAxis
>(_gamepadAxes[axis]);
838 return binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS &&
839 binding->output.axis.axis == gamepadAxis;
844 const int nonGamepadIdx = axis - _gamepadAxes.length();
845 if (nonGamepadIdx >= 0 && nonGamepadIdx < _nonGamepadAxes.length()) {
846 result[QStringLiteral(
"valid")] =
true;
847 result[QStringLiteral(
"inputType")] =
static_cast<int>(SDL_GAMEPAD_BINDTYPE_AXIS);
848 result[QStringLiteral(
"inputAxis")] = _nonGamepadAxes[nonGamepadIdx];
849 result[QStringLiteral(
"inputAxisMin")] = -32768;
850 result[QStringLiteral(
"inputAxisMax")] = 32767;
851 result[QStringLiteral(
"direct")] =
true;
860 if (!_sdlGamepad || button < 0 || button >= SDL_GAMEPAD_BUTTON_COUNT) {
862 result[QStringLiteral(
"valid")] =
false;
866 const SDL_GamepadButton gamepadButton =
static_cast<SDL_GamepadButton
>(button);
868 return binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&
869 binding->output.button == gamepadButton;
880 return SDL_GamepadHasSensor(_sdlGamepad, SDL_SENSOR_GYRO);
888 return SDL_GamepadHasSensor(_sdlGamepad, SDL_SENSOR_ACCEL);
896 if (!SDL_SetGamepadSensorEnabled(_sdlGamepad, SDL_SENSOR_GYRO, enabled)) {
897 qCWarning(JoystickSDLLog) <<
"Failed to" << (enabled ?
"enable" :
"disable") <<
"gyroscope:" << SDL_GetError();
908 if (!SDL_SetGamepadSensorEnabled(_sdlGamepad, SDL_SENSOR_ACCEL, enabled)) {
909 qCWarning(JoystickSDLLog) <<
"Failed to" << (enabled ?
"enable" :
"disable") <<
"accelerometer:" << SDL_GetError();
920 if (_gyroDataCached) {
921 return _cachedGyroData;
927 std::array<float, 3> data = {0.0f, 0.0f, 0.0f};
928 if (!SDL_GetGamepadSensorData(_sdlGamepad, SDL_SENSOR_GYRO, data.data(), 3)) {
930 if (SDL_GamepadSensorEnabled(_sdlGamepad, SDL_SENSOR_GYRO)) {
931 qCDebug(JoystickSDLLog) <<
"Failed to get gyroscope data:" << SDL_GetError();
935 return QVector3D(data[0], data[1], data[2]);
941 if (_accelDataCached) {
942 return _cachedAccelData;
948 std::array<float, 3> data = {0.0f, 0.0f, 0.0f};
949 if (!SDL_GetGamepadSensorData(_sdlGamepad, SDL_SENSOR_ACCEL, data.data(), 3)) {
951 if (SDL_GamepadSensorEnabled(_sdlGamepad, SDL_SENSOR_ACCEL)) {
952 qCDebug(JoystickSDLLog) <<
"Failed to get accelerometer data:" << SDL_GetError();
956 return QVector3D(data[0], data[1], data[2]);
962 return SDL_GetGamepadSensorDataRate(_sdlGamepad, SDL_SENSOR_GYRO);
970 return SDL_GetGamepadSensorDataRate(_sdlGamepad, SDL_SENSOR_ACCEL);
982 return SDL_GetNumGamepadTouchpads(_sdlGamepad);
990 return SDL_GetNumGamepadTouchpadFingers(_sdlGamepad, touchpad);
1000 float x = 0.0f, y = 0.0f, pressure = 0.0f;
1001 if (SDL_GetGamepadTouchpadFinger(_sdlGamepad, touchpad, finger, &down, &x, &y, &pressure)) {
1002 result[QStringLiteral(
"valid")] =
true;
1003 result[QStringLiteral(
"down")] = down;
1004 result[QStringLiteral(
"x")] = x;
1005 result[QStringLiteral(
"y")] = y;
1006 result[QStringLiteral(
"pressure")] = pressure;
1010 result[QStringLiteral(
"valid")] =
false;
1021 return SDL_GetNumJoystickBalls(_sdlJoystick);
1031 if (SDL_GetJoystickBall(_sdlJoystick, ball, &dx, &dy)) {
1032 result[QStringLiteral(
"valid")] =
true;
1033 result[QStringLiteral(
"dx")] = dx;
1034 result[QStringLiteral(
"dy")] = dy;
1038 result[QStringLiteral(
"valid")] =
false;
1048 if (_sdlGamepad && button >= 0 && button < SDL_GAMEPAD_BUTTON_COUNT) {
1049 return SDL_GamepadHasButton(_sdlGamepad,
static_cast<SDL_GamepadButton
>(button));
1051 if (_sdlJoystick && button >= 0 && button < SDL_GetNumJoystickButtons(_sdlJoystick)) {
1059 if (_sdlGamepad && axis >= 0 && axis < _gamepadAxes.length()) {
1060 return SDL_GamepadHasAxis(_sdlGamepad,
static_cast<SDL_GamepadAxis
>(_gamepadAxes[axis]));
1063 const int totalAxes = _gamepadAxes.length() + _nonGamepadAxes.length()
1064#ifdef TEST_WITH_VIRTUAL_AXES
1068 return axis >= 0 && axis < totalAxes;
1087 if (_sdlGamepad && button >= 0 && button < SDL_GAMEPAD_BUTTON_COUNT) {
1088 SDL_GamepadType type = SDL_GetGamepadType(_sdlGamepad);
1089 SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabelForType(type,
static_cast<SDL_GamepadButton
>(button));
1091 case SDL_GAMEPAD_BUTTON_LABEL_A:
1092 return QStringLiteral(
"A");
1093 case SDL_GAMEPAD_BUTTON_LABEL_B:
1094 return QStringLiteral(
"B");
1095 case SDL_GAMEPAD_BUTTON_LABEL_X:
1096 return QStringLiteral(
"X");
1097 case SDL_GAMEPAD_BUTTON_LABEL_Y:
1098 return QStringLiteral(
"Y");
1099 case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
1100 return QStringLiteral(
"Cross");
1101 case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
1102 return QStringLiteral(
"Circle");
1103 case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
1104 return QStringLiteral(
"Square");
1105 case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
1106 return QStringLiteral(
"Triangle");
1121 return SDL_IsJoystickHaptic(_sdlJoystick);
1129 return SDL_GetMaxHapticEffects(_sdlHaptic);
1137 const Uint32 features = SDL_GetHapticFeatures(_sdlHaptic);
1138 return (features & SDL_HAPTIC_LEFTRIGHT) || (features & SDL_HAPTIC_SINE);
1149 if (!_sdlJoystick || !SDL_IsJoystickHaptic(_sdlJoystick)) {
1153 _sdlHaptic = SDL_OpenHapticFromJoystick(_sdlJoystick);
1155 qCWarning(JoystickSDLLog) <<
"Failed to open haptic device:" << SDL_GetError();
1159 if (!SDL_InitHapticRumble(_sdlHaptic)) {
1160 qCWarning(JoystickSDLLog) <<
"Failed to init haptic rumble:" << SDL_GetError();
1161 SDL_CloseHaptic(_sdlHaptic);
1162 _sdlHaptic =
nullptr;
1166 qCDebug(JoystickSDLLog) <<
"Haptic rumble initialized";
1178 if (!SDL_PlayHapticRumble(_sdlHaptic, strength, durationMs)) {
1179 qCWarning(JoystickSDLLog) <<
"Failed to play haptic rumble:" << SDL_GetError();
1188 SDL_StopHapticRumble(_sdlHaptic);
1198 if (
guid.isEmpty()) {
1202 SDL_GUID sdlGuid = SDL_StringToGUID(qPrintable(
guid));
1203 char *mapping = SDL_GetGamepadMappingForGUID(sdlGuid);
1205 QString result = QString::fromUtf8(mapping);
1218 if (!_checkVirtualJoystick(
"setVirtualAxis")) {
1222 if (!SDL_SetJoystickVirtualAxis(_sdlJoystick, axis,
static_cast<Sint16
>(value))) {
1223 qCDebug(JoystickSDLLog) <<
"Failed to set virtual axis" << axis <<
":" << SDL_GetError();
1231 if (!_checkVirtualJoystick(
"setVirtualButton")) {
1235 if (!SDL_SetJoystickVirtualButton(_sdlJoystick, button, down)) {
1236 qCDebug(JoystickSDLLog) <<
"Failed to set virtual button" << button <<
":" << SDL_GetError();
1244 if (!_checkVirtualJoystick(
"setVirtualHat")) {
1248 if (!SDL_SetJoystickVirtualHat(_sdlJoystick, hat, value)) {
1249 qCDebug(JoystickSDLLog) <<
"Failed to set virtual hat" << hat <<
":" << SDL_GetError();
1261 return _hasGamepadCapability(SDL_PROP_GAMEPAD_CAP_MONO_LED_BOOLEAN);
1266 return _hasGamepadCapability(SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN);
1271 return _hasGamepadCapability(SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN);
1290 result[QStringLiteral(
"valid")] =
false;
1291 result[QStringLiteral(
"value")] = 0;
1293 if (!_sdlJoystick || axis < 0) {
1297 Sint16 initialValue = 0;
1298 if (SDL_GetJoystickAxisInitialState(_sdlJoystick, axis, &initialValue)) {
1299 result[QStringLiteral(
"valid")] =
true;
1300 result[QStringLiteral(
"value")] =
static_cast<int>(initialValue);
1311 if (!_sdlGamepad || mapping.isEmpty()) {
1315 if (!SDL_SetGamepadMapping(SDL_GetJoystickID(_sdlJoystick), qPrintable(mapping))) {
1316 qCDebug(JoystickSDLLog) <<
"Failed to set gamepad mapping:" << SDL_GetError();
1328 if (!_checkVirtualJoystick(
"setVirtualBall")) {
1332 if (!SDL_SetJoystickVirtualBall(_sdlJoystick, ball,
static_cast<Sint16
>(dx),
static_cast<Sint16
>(dy))) {
1333 qCDebug(JoystickSDLLog) <<
"Failed to set virtual ball" << ball <<
":" << SDL_GetError();
1341 if (!_checkVirtualJoystick(
"setVirtualTouchpad")) {
1346 const float clampedX = qBound(0.0f, x, 1.0f);
1347 const float clampedY = qBound(0.0f, y, 1.0f);
1348 const float clampedPressure = qBound(0.0f, pressure, 1.0f);
1350 if (x != clampedX || y != clampedY || pressure != clampedPressure) {
1351 qCDebug(JoystickSDLLog) <<
"Virtual touchpad coordinates clamped: x" << x <<
"->" << clampedX
1352 <<
"y" << y <<
"->" << clampedY
1353 <<
"pressure" << pressure <<
"->" << clampedPressure;
1356 if (!SDL_SetJoystickVirtualTouchpad(_sdlJoystick, touchpad, finger, down, clampedX, clampedY, clampedPressure)) {
1357 qCDebug(JoystickSDLLog) <<
"Failed to set virtual touchpad" << touchpad <<
"finger" << finger <<
":" << SDL_GetError();
1365 if (!_checkVirtualJoystick(
"sendVirtualSensorData")) {
1369 const float data[3] = {x, y, z};
1370 if (!SDL_SendJoystickVirtualSensorData(_sdlJoystick,
static_cast<SDL_SensorType
>(sensorType), SDL_GetTicksNS(), data, 3)) {
1371 qCDebug(JoystickSDLLog) <<
"Failed to send virtual sensor data:" << SDL_GetError();
1383 _cachedGyroData = data;
1384 _gyroDataCached =
true;
1390 _cachedAccelData = data;
1391 _accelDataCached =
true;
1398 if (currentState != _lastConnectionState && !currentState.isEmpty()) {
1399 qCDebug(JoystickSDLLog) <<
"Connection state changed:" << _lastConnectionState <<
"->" << currentState;
1400 _lastConnectionState = currentState;
1407 QVariantList driftingAxes;
1409 const int totalAxes = _gamepadAxes.length() + _nonGamepadAxes.length();
1410 for (
int i = 0; i < totalAxes; ++i) {
1411 const int currentValue = _getAxisValue(i);
1415 if (qAbs(currentValue) > threshold) {
1416 QVariantMap axisInfo;
1417 axisInfo[QStringLiteral(
"axis")] = i;
1418 axisInfo[QStringLiteral(
"label")] =
axisLabel(i);
1419 axisInfo[QStringLiteral(
"value")] = currentValue;
1420 axisInfo[QStringLiteral(
"percentage")] = qRound(qAbs(currentValue) * 100.0 / 32767.0);
1421 driftingAxes.append(axisInfo);
1425 return driftingAxes;
static QMap< QString, Joystick * > s_discoveryCache
Discovery cache - main thread only, cleared in shutdown()
static bool sdlEventWatcher(void *userdata, SDL_Event *event)
SDL event watcher - uses Qt::QueuedConnection for thread safety.
struct SDL_Joystick SDL_Joystick
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QString getMapping() const override
QString powerState() const override
bool isVirtual() const override
int batteryPercent() const override
quint16 vendorId() const override
QVariantMap getTouchpadFinger(int touchpad, int finger) const override
bool addMapping(const QString &mapping) override
QVariantMap getButtonBinding(int button) const override
void rumbleTriggers(quint16 left, quint16 right, quint32 durationMs) override
bool setVirtualButton(int button, bool down) override
QString serial() const override
bool setMapping(const QString &mapping) override
void updateCachedGyroData(const QVector3D &data)
bool setVirtualTouchpad(int touchpad, int finger, bool down, float x, float y, float pressure) override
QVector3D gyroscopeData() const override
void checkConnectionStateChanged()
bool hasAxis(int axis) const override
bool hasPlayerLED() const override
JoystickSDL(const QString &name, const QList< int > &gamepadAxes, const QList< int > &nonGamepadAxes, int buttonCount, int hatCount, int instanceId, QObject *parent=nullptr)
int playerIndex() const override
bool setVirtualHat(int hat, quint8 value) override
static void shutdown(bool deleteDiscoveryCache=true)
QString deviceType() const override
QString realGamepadType() const override
QVariantMap getBall(int ball) const override
void setPlayerIndex(int index) override
QVariantMap getAxisInitialState(int axis) const override
bool hasGyroscope() const override
bool hasMonoLED() const override
quint16 productId() const override
bool setAccelerometerEnabled(bool enabled) override
QString gamepadType() const override
void rumble(quint16 lowFreq, quint16 highFreq, quint32 durationMs) override
bool setVirtualBall(int ball, int dx, int dy) override
QString axisLabel(int axis) const override
QString getMappingForGUID(const QString &guid) const override
bool setVirtualAxis(int axis, int value) override
static QMap< QString, Joystick * > discover()
int ballCount() const override
void hapticRumbleStop() override
QVariantList detectAxisDrift(int threshold=8000) const
bool hasAccelerometer() const override
bool hasRumble() const override
void updateCachedAccelData(const QVector3D &data)
bool hapticRumblePlay(float strength, quint32 durationMs) override
QString buttonLabel(int button) const override
QVector3D accelerometerData() const override
QString connectionState() const override
QString buttonLabelForType(int button) const override
QVariantMap getAxisBinding(int axis) const override
QString connectionType() const override
bool hasButton(int button) const override
QString path() const override
void setInstanceId(int instanceId)
QString guid() const override
bool isGamepad() const override
bool hasRumbleTriggers() const override
bool sendVirtualSensorData(int sensorType, float x, float y, float z) override
void setLED(quint8 red, quint8 green, quint8 blue) override
float gyroscopeDataRate() const override
float accelerometerDataRate() const override
bool hasRGBLED() const override
bool hasLED() const override
bool hasHaptic() const override
bool hapticRumbleSupported() const override
bool sendEffect(const QByteArray &data) override
quint16 firmwareVersion() const override
int hapticEffectsCount() const override
int touchpadCount() const override
int touchpadFingerCount(int touchpad) const override
bool hapticRumbleInit() override
bool setGyroscopeEnabled(bool enabled) override
void playerIndexChanged()
QString name READ name int int int hatCount
void connectionStateChanged(const QString &newState)
QString name READ name int axisCount
QString name READ name int int buttonCount
void accelerometerDataUpdated(const QVector3D &data)
void gyroscopeDataUpdated(const QVector3D &data)
RAII lock guard for joysticks.
QString connectionStateToString(int state)
Connection state to string.
void shutdown()
Shutdown SDL joystick/gamepad subsystems.
QString gamepadTypeDisplayName(int type)
bool init()
Initialize SDL joystick/gamepad subsystems with QGC-specific hints.
QString getNameForInstanceId(int instanceId)
Get device name for instance ID.
void pumpEvents()
Pump SDL events (call periodically)
QVariantMap findBinding(SDL_Gamepad *gamepad, MatchFunc matchFunc)