123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- /**
- * Copyright (c) 2006-2014 LOVE Development Team
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- **/
- #include "common/config.h"
- #include "JoystickModule.h"
- #include "Joystick.h"
- // SDL
- #include <SDL.h>
- // C++
- #include <sstream>
- #include <algorithm>
- // C
- #include <cstdlib>
- namespace love
- {
- namespace joystick
- {
- namespace sdl
- {
- JoystickModule::JoystickModule()
- {
- if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0)
- throw love::Exception("%s", SDL_GetError());
- // Initialize any joysticks which are already connected.
- for (int i = 0; i < SDL_NumJoysticks(); i++)
- addJoystick(i);
- // Start joystick event watching. Joysticks are automatically added and
- // removed via love.event.
- SDL_JoystickEventState(SDL_ENABLE);
- SDL_GameControllerEventState(SDL_ENABLE);
- }
- JoystickModule::~JoystickModule()
- {
- // Close any open Joysticks.
- for (auto stick : joysticks)
- {
- stick->close();
- stick->release();
- }
- if (SDL_WasInit(SDL_INIT_HAPTIC) != 0)
- SDL_QuitSubSystem(SDL_INIT_HAPTIC);
- SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
- }
- const char *JoystickModule::getName() const
- {
- return "love.joystick.sdl";
- }
- love::joystick::Joystick *JoystickModule::getJoystick(int joyindex)
- {
- if (joyindex < 0 || (size_t) joyindex >= activeSticks.size())
- return nullptr;
- return activeSticks[joyindex];
- }
- int JoystickModule::getIndex(const love::joystick::Joystick *joystick)
- {
- for (size_t i = 0; i < activeSticks.size(); i++)
- {
- if (activeSticks[i] == joystick)
- return i;
- }
- // Joystick is not connected.
- return -1;
- }
- int JoystickModule::getJoystickCount() const
- {
- return (int) activeSticks.size();
- }
- love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid)
- {
- for (auto stick : activeSticks)
- {
- if (stick->getInstanceID() == instanceid)
- return stick;
- }
- return nullptr;
- }
- love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
- {
- if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
- return nullptr;
- std::string guidstr = getDeviceGUID(deviceindex);
- joystick::Joystick *joystick = 0;
- bool reused = false;
- for (auto stick : joysticks)
- {
- // Try to re-use a disconnected Joystick with the same GUID.
- if (!stick->isConnected() && stick->getGUID() == guidstr)
- {
- joystick = stick;
- reused = true;
- break;
- }
- }
- if (!joystick)
- {
- joystick = new Joystick(joysticks.size());
- joysticks.push_back(joystick);
- }
- // Make sure the Joystick object isn't in the active list already.
- removeJoystick(joystick);
- if (!joystick->open(deviceindex))
- return nullptr;
- // Make sure multiple instances of the same physical joystick aren't added
- // to the active list.
- for (auto activestick : activeSticks)
- {
- if (joystick->getHandle() == activestick->getHandle())
- {
- joystick->close();
- // If we just created the stick, remove it since it's a duplicate.
- if (!reused)
- {
- joysticks.remove(joystick);
- joystick->release();
- }
- return activestick;
- }
- }
- activeSticks.push_back(joystick);
- return joystick;
- }
- void JoystickModule::removeJoystick(love::joystick::Joystick *joystick)
- {
- if (!joystick)
- return;
- // Close the Joystick and remove it from the active joystick list.
- auto it = std::find(activeSticks.begin(), activeSticks.end(), joystick);
- if (it != activeSticks.end())
- {
- (*it)->close();
- activeSticks.erase(it);
- }
- }
- bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput, Joystick::JoystickInput joyinput)
- {
- // All SDL joystick GUID strings are 32 characters.
- if (guid.length() != 32)
- throw love::Exception("Invalid joystick GUID: %s", guid.c_str());
- SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
- std::string mapstr;
- char *sdlmapstr = SDL_GameControllerMappingForGUID(sdlguid);
- if (sdlmapstr)
- {
- mapstr = sdlmapstr;
- SDL_free(sdlmapstr);
- }
- else
- {
- // Use a generic name if we have to create a new mapping string.
- mapstr = guid + ",Controller,";
- }
- std::stringstream joyinputstream;
- Uint8 sdlhat;
- // We can't have negative int values in the bind string.
- switch (joyinput.type)
- {
- case Joystick::INPUT_TYPE_AXIS:
- if (joyinput.axis >= 0)
- joyinputstream << "a" << joyinput.axis;
- break;
- case Joystick::INPUT_TYPE_BUTTON:
- if (joyinput.button >= 0)
- joyinputstream << "b" << joyinput.button;
- break;
- case Joystick::INPUT_TYPE_HAT:
- if (joyinput.hat.value >= 0 && Joystick::getConstant(joyinput.hat.value, sdlhat))
- joyinputstream << "h" << joyinput.hat.value << "." << int(sdlhat);
- break;
- default:
- break;
- }
- std::string joyinputstr = joyinputstream.str();
- if (joyinputstr.length() == 0)
- throw love::Exception("Invalid joystick input value.");
- // SDL's name for the gamepad input value, e.g. "guide".
- std::string gpinputname = stringFromGamepadInput(gpinput);
- // We should remove any existing joystick bind for this gamepad buttton/axis
- // so SDL's parser doesn't get mixed up.
- removeBindFromMapString(mapstr, joyinputstr);
- // The string we'll be adding to the mapping string, e.g. "guide:b10,"
- std::string insertstr = gpinputname + ":" + joyinputstr + ",";
- // We should replace any existing gamepad bind.
- size_t findpos = mapstr.find(gpinputname + ":");
- if (findpos != std::string::npos)
- {
- // The bind string ends at the next comma, or the end of the string.
- size_t endpos = mapstr.find_first_of(',', findpos);
- if (endpos == std::string::npos)
- endpos = mapstr.length() - 1;
- mapstr.replace(findpos, endpos - findpos + 1, insertstr);
- }
- else
- {
- // Just append to the end if we don't need to replace anything.
- mapstr += insertstr;
- }
- // 1 == added, 0 == updated, -1 == error.
- int status = SDL_GameControllerAddMapping(mapstr.c_str());
- // FIXME: massive hack until missing APIs are added to SDL 2:
- // https://bugzilla.libsdl.org/show_bug.cgi?id=1975
- if (status == 1)
- checkGamepads(guid);
- return status >= 0;
- }
- Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput)
- {
- // All SDL joystick GUID strings are 32 characters.
- if (guid.length() != 32)
- throw love::Exception("Invalid joystick GUID: %s", guid.c_str());
- Joystick::JoystickInput jinput;
- jinput.type = Joystick::INPUT_TYPE_MAX_ENUM;
- SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
- std::string mapstr;
- char *sdlmapstr = SDL_GameControllerMappingForGUID(sdlguid);
- if (!sdlmapstr)
- return jinput;
- mapstr = sdlmapstr;
- SDL_free(sdlmapstr);
- std::string gpbindname = stringFromGamepadInput(gpinput);
- size_t findpos = mapstr.find(std::string(",") + gpbindname + ":");
- if (findpos == std::string::npos)
- return jinput;
- size_t endpos = mapstr.find_first_of(',', findpos);
- if (endpos == std::string::npos)
- {
- // Assume end-of-string if we can't find the next comma.
- endpos = mapstr.length() - 1;
- }
- if (endpos >= mapstr.length())
- return jinput; // Something went wrong.
- // Strip out the trailing comma from our search position, if it exists.
- if (mapstr[endpos] == ',')
- endpos--;
- // New start position: comma + gamepadinputlength + ":".
- findpos += 1 + gpbindname.length() + 1;
- std::string jbindstr = mapstr.substr(findpos, endpos - findpos + 1);
- jinput = JoystickInputFromString(jbindstr);
- return jinput;
- }
- std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinput) const
- {
- SDL_GameControllerAxis sdlaxis;
- SDL_GameControllerButton sdlbutton;
- const char *gpinputname = nullptr;
- switch (gpinput.type)
- {
- case Joystick::INPUT_TYPE_AXIS:
- if (Joystick::getConstant(gpinput.axis, sdlaxis))
- gpinputname = SDL_GameControllerGetStringForAxis(sdlaxis);
- break;
- case Joystick::INPUT_TYPE_BUTTON:
- if (Joystick::getConstant(gpinput.button, sdlbutton))
- gpinputname = SDL_GameControllerGetStringForButton(sdlbutton);
- break;
- default:
- break;
- }
- if (!gpinputname)
- throw love::Exception("Invalid gamepad axis/button.");
- return std::string(gpinputname);
- }
- Joystick::JoystickInput JoystickModule::JoystickInputFromString(const std::string &str) const
- {
- Joystick::JoystickInput jinput;
- jinput.type = Joystick::INPUT_TYPE_MAX_ENUM;
- // Return an invalid value rather than throwing an exception.
- if (str.length() < 2)
- return jinput;
- // The input type will always be the first character in the string.
- char inputtype = str[0];
- std::string bindvalues = str.substr(1);
- Uint8 sdlhat;
- switch (inputtype)
- {
- case 'a':
- jinput.type = Joystick::INPUT_TYPE_AXIS;
- jinput.axis = atoi(bindvalues.c_str());
- break;
- case 'b':
- jinput.type = Joystick::INPUT_TYPE_BUTTON;
- jinput.button = atoi(bindvalues.c_str());
- break;
- case 'h':
- // Hat string syntax is "index.value".
- if (bindvalues.length() < 3)
- break;
- jinput.type = Joystick::INPUT_TYPE_HAT;
- jinput.hat.index = atoi(bindvalues.substr(0, 1).c_str());
- sdlhat = (Uint8) atoi(bindvalues.substr(2, 1).c_str());
- if (!Joystick::getConstant(sdlhat, jinput.hat.value))
- {
- // Return an invalid value if we can't find the hat constant.
- jinput.type = Joystick::INPUT_TYPE_MAX_ENUM;
- return jinput;
- }
- break;
- default:
- break;
- }
- return jinput;
- }
- void JoystickModule::removeBindFromMapString(std::string &mapstr, const std::string &joybindstr) const
- {
- // Find the joystick part of the bind in the string.
- size_t joybindpos = mapstr.find(joybindstr + ",");
- if (joybindpos == std::string::npos)
- {
- joybindpos = mapstr.rfind(joybindstr);
- if (joybindpos != mapstr.length() - joybindstr.length())
- return;
- }
- if (joybindpos == std::string::npos)
- return;
- // Find the start of the entire bind.
- size_t bindstart = mapstr.rfind(',', joybindpos);
- if (bindstart != std::string::npos && bindstart < mapstr.length() - 1)
- {
- size_t bindend = mapstr.find(',', bindstart + 1);
- if (bindend == std::string::npos)
- bindend = mapstr.length() - 1;
- // Replace it with an empty string (remove it.)
- mapstr.replace(bindstart, bindend - bindstart + 1, "");
- }
- }
- void JoystickModule::checkGamepads(const std::string &guid) const
- {
- // FIXME: massive hack until missing APIs are added to SDL 2:
- // https://bugzilla.libsdl.org/show_bug.cgi?id=1975
- // Make sure all connected joysticks of a certain guid that are
- // gamepad-capable are opened as such.
- for (int d_index = 0; d_index < SDL_NumJoysticks(); d_index++)
- {
- if (!SDL_IsGameController(d_index))
- continue;
- if (guid.compare(getDeviceGUID(d_index)) != 0)
- continue;
- for (auto stick : activeSticks)
- {
- if (stick->isGamepad() || guid.compare(stick->getGUID()) != 0)
- continue;
- // Big hack time: open the index as a game controller and compare
- // the underlying joystick handle to the active stick's.
- SDL_GameController *controller = SDL_GameControllerOpen(d_index);
- if (controller == nullptr)
- continue;
- SDL_Joystick *sdlstick = SDL_GameControllerGetJoystick(controller);
- if (sdlstick == (SDL_Joystick *) stick->getHandle())
- stick->openGamepad(d_index);
- // GameController objects are reference-counted in SDL.
- SDL_GameControllerClose(controller);
- }
- }
- }
- std::string JoystickModule::getDeviceGUID(int deviceindex) const
- {
- if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
- return std::string("");
- // SDL_JoystickGetGUIDString uses 32 bytes plus the null terminator.
- char guidstr[33] = {'\0'};
- // SDL2's GUIDs identify *classes* of devices, instead of unique devices.
- SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(deviceindex);
- SDL_JoystickGetGUIDString(guid, guidstr, sizeof(guidstr));
- return std::string(guidstr);
- }
- void JoystickModule::loadGamepadMappings(const std::string &mappings)
- {
- // TODO: We should use SDL_GameControllerAddMappingsFromRW. We're
- // duplicating its functionality for now because it was added after
- // SDL 2.0.0's release, and we want runtime compat with 2.0.0 on Linux...
- std::stringstream ss(mappings);
- std::string mapping;
- bool success = false;
- // The mappings string contains newline-separated mappings.
- while (std::getline(ss, mapping))
- {
- if (mapping.empty())
- continue;
- // Strip out and compare any "platform:XYZ," in the mapping.
- size_t pstartpos = mapping.find("platform:");
- if (pstartpos != std::string::npos)
- {
- pstartpos += strlen("platform:");
- size_t pendpos = mapping.find_first_of(',', pstartpos);
- std::string platform = mapping.substr(pstartpos, pendpos - pstartpos);
- if (platform.compare(SDL_GetPlatform()) != 0)
- continue;
- pstartpos -= strlen("platform:");
- mapping.erase(pstartpos, pendpos - pstartpos + 1);
- }
- success = success || (SDL_GameControllerAddMapping(mapping.c_str()) != -1);
- }
- if (!success)
- throw love::Exception("Invalid gamepad mappings.");
- }
- std::string JoystickModule::saveGamepadMapping(const std::string &pguid)
- {
- SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(pguid.c_str());
- std::string mapping;
- char *sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
- if (sdlmapping == nullptr)
- throw love::Exception("The specified Joystick GUID string has no gamepad mapping.");
- mapping = sdlmapping;
- SDL_free(sdlmapping);
- if (mapping.find_last_of(',') != mapping.size() - 1)
- mapping += ",";
- // Matches SDL_GameControllerAddMappingsFromRW.
- mapping += "platform:" + std::string(SDL_GetPlatform()) + ",";
- return mapping;
- }
- } // sdl
- } // joystick
- } // love
|