6#include <QtCore/QTextStream>
7#include <QtCore/QStandardPaths>
23 const auto loadMappingsFromFile = [](
const QString &path,
const char *description) {
25 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
29 qCDebug(SDLJoystickLog) <<
"Loading gamepad mappings from" << description;
31 QTextStream stream(&file);
32 while (!stream.atEnd()) {
33 const QString line = stream.readLine();
34 if (line.startsWith(
'#') || line.isEmpty()) {
37 if (SDL_AddGamepadMapping(qPrintable(line)) == -1) {
38 qCWarning(SDLJoystickLog) <<
"Couldn't add gamepad mapping:" << SDL_GetError();
43 qCDebug(SDLJoystickLog) <<
"Loaded" << count <<
"mappings from" << description;
48 if (!loadMappingsFromFile(QStringLiteral(
":/gamecontrollerdb.txt"),
"bundled database")) {
49 qCWarning(SDLJoystickLog) <<
"Couldn't load bundled gamepad mapping database";
53 const QString userMappingsPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)
54 + QStringLiteral(
"/gamecontrollerdb.txt");
55 if (loadMappingsFromFile(userMappingsPath,
"user config")) {
56 qCInfo(SDLJoystickLog) <<
"Loaded user gamepad mappings from" << userMappingsPath;
66 qCDebug(SDLJoystickLog) <<
"Initializing SDL joystick subsystem";
69 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
"1");
73 SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE,
"");
77 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM,
"1");
78 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
"1");
81 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI,
"1");
82 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4,
"1");
83 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5,
"1");
84 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
"1");
85 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX,
"1");
86 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
"1");
87 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,
"1");
90 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS,
"1");
91 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH,
"1");
92 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED,
"1");
95 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII,
"1");
96 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED,
"1");
97 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
"1");
98 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_8BITDO,
"1");
99 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI,
"1");
100 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA,
"1");
101 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA,
"1");
104 SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION,
"1");
107 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT,
"1");
111 SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES,
"1");
112 SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS,
"1");
116 SDL_SetHint(SDL_HINT_JOYSTICK_THREAD,
"1");
119 SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
"1");
121 if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_JOYSTICK)) {
122 qCWarning(SDLJoystickLog) <<
"Failed to initialize SDL:" << SDL_GetError();
128 qCDebug(SDLJoystickLog) <<
"SDL joystick subsystem initialized successfully";
134 qCDebug(SDLJoystickLog) <<
"Shutting down SDL joystick subsystem";
136 SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_JOYSTICK);
141 return SDL_WasInit(SDL_INIT_JOYSTICK) != 0;
151 SDL_UpdateJoysticks();
156 SDL_SetJoystickEventsEnabled(enabled);
161 return SDL_JoystickEventsEnabled();
166 SDL_SetGamepadEventsEnabled(enabled);
171 return SDL_GamepadEventsEnabled();
176 SDL_UpdateJoysticks();
181 SDL_UpdateGamepads();
195 SDL_UnlockJoysticks();
204 const char *str = SDL_GetGamepadStringForType(
static_cast<SDL_GamepadType
>(type));
205 return str ? QString::fromUtf8(str) : QString();
211 return SDL_GAMEPAD_TYPE_UNKNOWN;
213 return SDL_GetGamepadTypeFromString(qPrintable(str));
218 switch (
static_cast<SDL_GamepadType
>(type)) {
219 case SDL_GAMEPAD_TYPE_STANDARD:
220 return QStringLiteral(
"Standard");
221 case SDL_GAMEPAD_TYPE_XBOX360:
222 return QStringLiteral(
"Xbox 360");
223 case SDL_GAMEPAD_TYPE_XBOXONE:
224 return QStringLiteral(
"Xbox One");
225 case SDL_GAMEPAD_TYPE_PS3:
226 return QStringLiteral(
"PlayStation 3");
227 case SDL_GAMEPAD_TYPE_PS4:
228 return QStringLiteral(
"PlayStation 4");
229 case SDL_GAMEPAD_TYPE_PS5:
230 return QStringLiteral(
"PlayStation 5");
231 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
232 return QStringLiteral(
"Nintendo Switch Pro");
233 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
234 return QStringLiteral(
"Nintendo Joy-Con (L)");
235 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
236 return QStringLiteral(
"Nintendo Joy-Con (R)");
237 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
238 return QStringLiteral(
"Nintendo Joy-Con Pair");
240 return QStringLiteral(
"Unknown");
246 const char *str = SDL_GetGamepadStringForAxis(
static_cast<SDL_GamepadAxis
>(axis));
247 return str ? QString::fromUtf8(str) : QString();
253 return SDL_GAMEPAD_AXIS_INVALID;
255 return SDL_GetGamepadAxisFromString(qPrintable(str));
260 const char *str = SDL_GetGamepadStringForButton(
static_cast<SDL_GamepadButton
>(button));
261 return str ? QString::fromUtf8(str) : QString();
267 return SDL_GAMEPAD_BUTTON_INVALID;
269 return SDL_GetGamepadButtonFromString(qPrintable(str));
274 switch (
static_cast<SDL_JoystickConnectionState
>(state)) {
275 case SDL_JOYSTICK_CONNECTION_INVALID:
276 return QStringLiteral(
"Invalid");
277 case SDL_JOYSTICK_CONNECTION_UNKNOWN:
278 return QStringLiteral(
"Unknown");
279 case SDL_JOYSTICK_CONNECTION_WIRED:
280 return QStringLiteral(
"Wired");
281 case SDL_JOYSTICK_CONNECTION_WIRELESS:
282 return QStringLiteral(
"Wireless");
295 result[QStringLiteral(
"valid")] =
false;
296 result[QStringLiteral(
"vendor")] = 0;
297 result[QStringLiteral(
"product")] = 0;
298 result[QStringLiteral(
"version")] = 0;
299 result[QStringLiteral(
"crc16")] = 0;
301 if (guid.isEmpty()) {
305 SDL_GUID sdlGuid = SDL_StringToGUID(qPrintable(guid));
306 Uint16 vendor = 0, product = 0, version = 0, crc16 = 0;
307 SDL_GetJoystickGUIDInfo(sdlGuid, &vendor, &product, &version, &crc16);
309 result[QStringLiteral(
"valid")] =
true;
310 result[QStringLiteral(
"vendor")] =
static_cast<int>(vendor);
311 result[QStringLiteral(
"product")] =
static_cast<int>(product);
312 result[QStringLiteral(
"version")] =
static_cast<int>(version);
313 result[QStringLiteral(
"crc16")] =
static_cast<int>(crc16);
320 if (guid.isEmpty()) {
324 SDL_GUID sdlGuid = SDL_StringToGUID(qPrintable(guid));
325 char *mapping = SDL_GetGamepadMappingForGUID(sdlGuid);
327 QString result = QString::fromUtf8(mapping);
340 if (filePath.isEmpty()) {
344 int count = SDL_AddGamepadMappingsFromFile(qPrintable(filePath));
346 qCDebug(SDLJoystickLog) <<
"Failed to load mappings from" << filePath <<
":" << SDL_GetError();
348 qCDebug(SDLJoystickLog) <<
"Loaded" << count <<
"mappings from" << filePath;
355 if (mapping.isEmpty()) {
359 int result = SDL_AddGamepadMapping(qPrintable(mapping));
361 qCWarning(SDLJoystickLog) <<
"Failed to add gamepad mapping:" << SDL_GetError();
365 qCDebug(SDLJoystickLog) <<
"Added gamepad mapping, result:" << result;
371 return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)
372 + QStringLiteral(
"/gamecontrollerdb.txt");
377 if (mapping.isEmpty()) {
387 const int commaPos = mapping.indexOf(
',');
389 qCWarning(SDLJoystickLog) <<
"Invalid mapping format, cannot extract GUID";
392 const QString guid = mapping.left(commaPos);
396 QFile file(filePath);
399 QStringList existingMappings;
400 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
401 QTextStream in(&file);
402 while (!in.atEnd()) {
403 const QString line = in.readLine();
404 if (line.isEmpty() || line.startsWith(
'#')) {
405 existingMappings.append(line);
409 if (!line.startsWith(guid +
',')) {
410 existingMappings.append(line);
417 if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
418 qCWarning(SDLJoystickLog) <<
"Failed to open user mappings file for writing:" << filePath;
422 QTextStream out(&file);
423 out <<
"# QGroundControl User Gamepad Mappings\n";
424 out <<
"# Generated automatically - edit with caution\n\n";
425 for (
const QString &line : existingMappings) {
426 if (!line.isEmpty()) {
430 out << mapping <<
"\n";
433 qCInfo(SDLJoystickLog) <<
"Persisted gamepad mapping for GUID:" << guid <<
"to" << filePath;
446 qCDebug(SDLJoystickLog) <<
"Reloaded gamepad mappings";
456 SDL_Gamepad *gamepad = SDL_GetGamepadFromPlayerIndex(playerIndex);
458 SDL_JoystickID
id = SDL_GetGamepadID(gamepad);
459 return static_cast<int>(id);
462 SDL_Joystick *joystick = SDL_GetJoystickFromPlayerIndex(playerIndex);
464 SDL_JoystickID
id = SDL_GetJoystickID(joystick);
465 return static_cast<int>(id);
477 const char *name = SDL_GetGamepadNameForID(
static_cast<SDL_JoystickID
>(instanceId));
479 return QString::fromUtf8(name);
481 name = SDL_GetJoystickNameForID(
static_cast<SDL_JoystickID
>(instanceId));
483 return QString::fromUtf8(name);
492 const char *path = SDL_GetGamepadPathForID(
static_cast<SDL_JoystickID
>(instanceId));
494 return QString::fromUtf8(path);
496 path = SDL_GetJoystickPathForID(
static_cast<SDL_JoystickID
>(instanceId));
497 return path ? QString::fromUtf8(path) : QString();
502 SDL_GUID guid = SDL_GetJoystickGUIDForID(
static_cast<SDL_JoystickID
>(instanceId));
504 SDL_GUIDToString(guid, guidStr,
sizeof(guidStr));
505 return QString::fromUtf8(guidStr);
510 Uint16 vendor = SDL_GetGamepadVendorForID(
static_cast<SDL_JoystickID
>(instanceId));
514 return SDL_GetJoystickVendorForID(
static_cast<SDL_JoystickID
>(instanceId));
519 Uint16 product = SDL_GetGamepadProductForID(
static_cast<SDL_JoystickID
>(instanceId));
523 return SDL_GetJoystickProductForID(
static_cast<SDL_JoystickID
>(instanceId));
528 Uint16 version = SDL_GetGamepadProductVersionForID(
static_cast<SDL_JoystickID
>(instanceId));
532 return SDL_GetJoystickProductVersionForID(
static_cast<SDL_JoystickID
>(instanceId));
537 SDL_GamepadType type = SDL_GetGamepadTypeForID(
static_cast<SDL_JoystickID
>(instanceId));
543 SDL_GamepadType type = SDL_GetRealGamepadTypeForID(
static_cast<SDL_JoystickID
>(instanceId));
549 int index = SDL_GetGamepadPlayerIndexForID(
static_cast<SDL_JoystickID
>(instanceId));
553 return SDL_GetJoystickPlayerIndexForID(
static_cast<SDL_JoystickID
>(instanceId));
559 SDL_JoystickConnectionState state = SDL_GetGamepadConnectionState(SDL_GetGamepadFromID(
static_cast<SDL_JoystickID
>(instanceId)));
560 if (state == SDL_JOYSTICK_CONNECTION_INVALID) {
561 state = SDL_GetJoystickConnectionState(SDL_GetJoystickFromID(
static_cast<SDL_JoystickID
>(instanceId)));
572 SDL_VirtualJoystickDesc desc;
573 SDL_INIT_INTERFACE(&desc);
574 desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
575 desc.naxes =
static_cast<Uint16
>(axisCount);
576 desc.nbuttons =
static_cast<Uint16
>(buttonCount);
577 desc.nhats =
static_cast<Uint16
>(hatCount);
578 const QByteArray utf8Name = name.toUtf8();
579 desc.name = utf8Name.constData();
581 SDL_JoystickID instanceId = SDL_AttachVirtualJoystick(&desc);
582 if (instanceId == 0) {
583 qCWarning(SDLJoystickLog) <<
"Failed to create virtual joystick:" << SDL_GetError();
589 qCDebug(SDLJoystickLog) <<
"Created virtual joystick" << name <<
"with instance ID" << instanceId;
590 return static_cast<int>(instanceId);
595 if (instanceId <= 0) {
599 if (!SDL_DetachVirtualJoystick(
static_cast<SDL_JoystickID
>(instanceId))) {
600 qCWarning(SDLJoystickLog) <<
"Failed to destroy virtual joystick" << instanceId <<
":" << SDL_GetError();
606 qCDebug(SDLJoystickLog) <<
"Destroyed virtual joystick" << instanceId;
612 return SDL_IsJoystickVirtual(
static_cast<SDL_JoystickID
>(instanceId));
621 result[QStringLiteral(
"valid")] =
true;
622 result[QStringLiteral(
"inputType")] =
static_cast<int>(binding->input_type);
623 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
624 result[QStringLiteral(
"inputAxis")] = binding->input.axis.axis;
625 result[QStringLiteral(
"inputAxisMin")] = binding->input.axis.axis_min;
626 result[QStringLiteral(
"inputAxisMax")] = binding->input.axis.axis_max;
627 }
else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON) {
628 result[QStringLiteral(
"inputButton")] = binding->input.button;
629 }
else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT) {
630 result[QStringLiteral(
"inputHat")] = binding->input.hat.hat;
631 result[QStringLiteral(
"inputHatMask")] = binding->input.hat.hat_mask;
struct SDL_Gamepad SDL_Gamepad
struct SDL_Joystick SDL_Joystick
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QVariantMap getGUIDInfo(const QString &guid)
Parse GUID info (vendor, product, version, crc16)
bool destroyVirtualJoystick(int instanceId)
Destroy a virtual joystick.
QString connectionStateToString(int state)
Connection state to string.
bool gamepadEventsEnabled()
QString getPathForInstanceId(int instanceId)
Get device path for instance ID.
QString userMappingsFilePath()
Get path to user's custom mappings file.
int getVendorForInstanceId(int instanceId)
Get vendor ID for instance ID.
bool reloadMappings()
Reload all gamepad mappings.
void shutdown()
Shutdown SDL joystick/gamepad subsystems.
QString gamepadTypeDisplayName(int type)
bool isVirtualJoystick(int instanceId)
Check if a joystick is virtual.
void updateJoysticks()
Update joystick/gamepad state.
QString getTypeForInstanceId(int instanceId)
Get gamepad type string for instance ID.
QString getConnectionStateForInstanceId(int instanceId)
int addMappingsFromFile(const QString &filePath)
Add gamepad mappings from a file.
bool addMapping(const QString &mapping)
Add a single mapping string.
bool init()
Initialize SDL joystick/gamepad subsystems with QGC-specific hints.
QString getRealTypeForInstanceId(int instanceId)
Get real (underlying) gamepad type string for instance ID.
static QHash< int, QString > s_virtualJoystickNames
int gamepadButtonFromString(const QString &str)
QString getGUIDForInstanceId(int instanceId)
Get GUID string for instance ID.
int getProductVersionForInstanceId(int instanceId)
Get product version for instance ID.
QString getMappingForGUID(const QString &guid)
Get mapping string for a GUID.
QString getNameForInstanceId(int instanceId)
Get device name for instance ID.
int getPlayerIndexForInstanceId(int instanceId)
Get player index for instance ID.
bool isInitialized()
Check if SDL joystick subsystem is initialized.
static void loadGamepadMappings()
void setJoystickEventsEnabled(bool enabled)
Enable/disable joystick event processing.
int createVirtualJoystick(const QString &name, int axisCount, int buttonCount, int hatCount)
Create a virtual joystick.
void setGamepadEventsEnabled(bool enabled)
Enable/disable gamepad event processing.
void populateBindingResult(QVariantMap &result, const SDL_GamepadBinding *binding)
Populate a QVariantMap with binding information from SDL_GamepadBinding.
bool addMappingPersistent(const QString &mapping)
Add a mapping and persist it to user's config file.
void pumpEvents()
Pump SDL events (call periodically)
bool joystickEventsEnabled()
int getProductForInstanceId(int instanceId)
Get product ID for instance ID.
int gamepadTypeFromString(const QString &str)
int getInstanceIdFromPlayerIndex(int playerIndex)
Get instance ID from player index.
QString gamepadButtonToString(int button)
Gamepad button conversions.
QString gamepadTypeToString(int type)
Gamepad type conversions.
int gamepadAxisFromString(const QString &str)
QString gamepadAxisToString(int axis)
Gamepad axis conversions.
void lockJoysticks()
Lock joystick access for thread-safe operations.