From 762952e7eaadcce2298a8d998966cd02c5290dfc Mon Sep 17 00:00:00 2001 From: Bim Overbohm Date: Fri, 24 May 2013 13:44:40 +0200 Subject: [PATCH] Poll joystick / HID devices without SDL on Windows / Linux Get a list of joysticks / HID devices from the system (scan "/dev/input/js*" on Linux / use GetRawInputDeviceInfo() on Windows) and poll again every 5s via a SDL timer. If the list changes SDL can be re-inited. Atm only a log message is written. --- src/InputManager.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++- src/InputManager.h | 62 +++++++++++++------ src/main.cpp | 5 +- 3 files changed, 187 insertions(+), 21 deletions(-) diff --git a/src/InputManager.cpp b/src/InputManager.cpp index f64d07cc5..4f8f15394 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -6,11 +6,29 @@ #include #include "platform.h" +#if defined(WIN32) || defined(_WIN32) + #include +#endif + namespace fs = boost::filesystem; +//----- InputDevice ---------------------------------------------------------------------------- + +InputDevice::InputDevice(const std::string & deviceName, unsigned long vendorId, unsigned long productId) + : name(deviceName), vendor(vendorId), product(productId) +{ +} + +bool InputDevice::operator==(const InputDevice & b) const +{ + return (name == b.name && vendor == b.vendor && product == b.product); +} + +//----- InputManager --------------------------------------------------------------------------- + InputManager::InputManager(Window* window) : mWindow(window), mJoysticks(NULL), mInputConfigs(NULL), mKeyboardInputConfig(NULL), mPrevAxisValues(NULL), - mNumJoysticks(0), mNumPlayers(0) + mNumJoysticks(0), mNumPlayers(0), devicePollingTimer(nullptr) { } @@ -19,12 +37,112 @@ InputManager::~InputManager() deinit(); } +std::vector InputManager::getInputDevices() const +{ + std::vector currentDevices; + + //retrieve all input devices from system +#if defined (__APPLE__) + #error TODO: Not implemented for MacOS yet!!! +#elif defined(__linux__) + //open linux input devices file system + const std::string inputPath("/dev/input"); + fs::directory_iterator dirIt(inputPath); + while (dirIt != fs::directory_iterator()) { + //get directory entry + std::string deviceName = (*dirIt).path().string(); + //remove parent path + deviceName.erase(0, inputPath.length() + 1); + //check if it start with "js" + if (deviceName.length() >= 3 && deviceName.find("js") == 0) { + //looks like a joystick. add to devices. + currentDevices.push_back(InputDevice(deviceName, 0, 0)); + } + dirIt++; + } + //or dump /proc/bus/input/devices anbd search for a Handler=..."js"... entry +#elif defined(WIN32) || defined(_WIN32) + RAWINPUTDEVICELIST * deviceList = nullptr; + UINT nrOfDevices = 0; + //get number of input devices + if (GetRawInputDeviceList(deviceList, &nrOfDevices, sizeof(RAWINPUTDEVICELIST)) != -1 && nrOfDevices > 0) + { + //get list of input devices + deviceList = new RAWINPUTDEVICELIST[nrOfDevices]; + if (GetRawInputDeviceList(deviceList, &nrOfDevices, sizeof(RAWINPUTDEVICELIST)) != -1) + { + //loop through input devices + for (int i = 0; i < nrOfDevices; i++) + { + //get device name + char * rawName = new char[2048]; + UINT rawNameSize = 2047; + GetRawInputDeviceInfo(deviceList[i].hDevice, RIDI_DEVICENAME, (void *)rawName, &rawNameSize); + //null-terminate string + rawName[rawNameSize] = '\0'; + //convert to string + std::string deviceName = rawName; + delete [] rawName; + //get deviceType + RID_DEVICE_INFO deviceInfo; + UINT deviceInfoSize = sizeof(RID_DEVICE_INFO); + GetRawInputDeviceInfo(deviceList[i].hDevice, RIDI_DEVICEINFO, (void *)&deviceInfo, &deviceInfoSize); + //check if it is a HID. we ignore keyboards and mice... + if (deviceInfo.dwType == RIM_TYPEHID) + { + //check if the vendor/product already exists in list. yes. could be more elegant... + std::vector::const_iterator cdIt = currentDevices.cbegin(); + while (cdIt != currentDevices.cend()) + { + if (cdIt->name == deviceName && cdIt->product == deviceInfo.hid.dwProductId && cdIt->vendor == deviceInfo.hid.dwVendorId) + { + //device already there + break; + } + ++cdIt; + } + //was the device found? + if (cdIt == currentDevices.cend()) + { + //no. add it. + currentDevices.push_back(InputDevice(deviceName, deviceInfo.hid.dwProductId, deviceInfo.hid.dwVendorId)); + } + } + } + } + delete [] deviceList; + } +#endif + + return currentDevices; +} + +Uint32 InputManager::devicePollingCallback(Uint32 interval, void* param) +{ + //this thing my be running in a different thread, so we're not allowed to call + //any functions or change/allocate/delete stuff, but can send a user event + SDL_Event event; + SDL_UserEvent userevent; + userevent.type = SDL_USEREVENT_POLLDEVICES; + userevent.code = 0; + userevent.data1 = nullptr; + userevent.data2 = nullptr; + event.type = SDL_USEREVENT; + event.user = userevent; + SDL_PushEvent(&event); + + return interval; +} + void InputManager::init() { if(mJoysticks != NULL) deinit(); - SDL_InitSubSystem(SDL_INIT_JOYSTICK); + //get current input devices from system + inputDevices = getInputDevices(); + + SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_TIMER); mNumJoysticks = SDL_NumJoysticks(); mJoysticks = new SDL_Joystick*[mNumJoysticks]; @@ -46,11 +164,16 @@ void InputManager::init() SDL_JoystickEventState(SDL_ENABLE); + //start timer for input device polling + devicePollingTimer = SDL_AddTimer(POLLING_INTERVAL, devicePollingCallback, (void *)this); + loadConfig(); } void InputManager::deinit() { + SDL_RemoveTimer(devicePollingTimer); + SDL_JoystickEventState(SDL_DISABLE); if(!SDL_WasInit(SDL_INIT_JOYSTICK)) @@ -77,7 +200,9 @@ void InputManager::deinit() mPrevAxisValues = NULL; } - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_TIMER); + + inputDevices.clear(); } int InputManager::getNumJoysticks() { return mNumJoysticks; } @@ -164,6 +289,16 @@ bool InputManager::parseEvent(const SDL_Event& ev) case SDL_KEYUP: mWindow->input(getInputConfigByDevice(DEVICE_KEYBOARD), Input(DEVICE_KEYBOARD, TYPE_KEY, ev.key.keysym.sym, 0, false)); return true; + + case SDL_USEREVENT_POLLDEVICES: + //poll joystick / HID again + std::vector currentDevices = getInputDevices(); + //compare device lists to see if devices were added/deleted + if (currentDevices != inputDevices) { + LOG(LogInfo) << "Device configuration changed!"; + inputDevices = currentDevices; + } + return true; } return false; diff --git a/src/InputManager.h b/src/InputManager.h index 711249d13..921a29fa5 100644 --- a/src/InputManager.h +++ b/src/InputManager.h @@ -9,10 +9,55 @@ class InputConfig; class Window; +struct InputDevice +{ + std::string name; + unsigned long vendor; + unsigned long product; + + InputDevice(const std::string & deviceName, unsigned long vendorId, unsigned long productId); + bool operator==(const InputDevice & b) const; +}; + //you should only ever instantiate one of these, by the way class InputManager { + static const int DEADZONE = 23000; + + Window* mWindow; + + //non-InputManager classes shouldn't use this, as you can easily miss the keyboard + InputConfig* getInputConfigByDevice(int device); + + void loadDefaultConfig(); + + int mNumJoysticks; + int mNumPlayers; + SDL_Joystick** mJoysticks; + InputConfig** mInputConfigs; + InputConfig* mKeyboardInputConfig; + std::map* mPrevAxisValues; + + std::vector inputDevices; + + /*! + Retrieve joysticks/ HID devices from system. + \return Returns a list of InputDevices that can be compared to the current /inputDevices to check if the configuration has changed. + \note This currently reads the content of the /dev/input on linux, searches for "js**" names and stores those. On Windows it uses GetRawInputDeviceInfo to find devices of type RIM_TYPEHID and stores those. + */ + std::vector getInputDevices() const; + + static const int POLLING_INTERVAL = 5000; + SDL_TimerID devicePollingTimer; + + /*! + Called when devicePollingTimer runs out. Sends a SDL_UserEvent with type SDL_USEREVENT_POLLDEVICES to the event queue. + */ + static Uint32 devicePollingCallback(Uint32 interval, void * param); + public: + static const int SDL_USEREVENT_POLLDEVICES = SDL_USEREVENT + 100; //This event is issued when the input devices should be rescanned. + InputManager(Window* window); ~InputManager(); @@ -32,23 +77,6 @@ public: bool parseEvent(const SDL_Event& ev); InputConfig* getInputConfigByPlayer(int player); - -private: - static const int DEADZONE = 23000; - - Window* mWindow; - - //non-InputManager classes shouldn't use this, as you can easily miss the keyboard - InputConfig* getInputConfigByDevice(int device); - - void loadDefaultConfig(); - - int mNumJoysticks; - int mNumPlayers; - SDL_Joystick** mJoysticks; - InputConfig** mInputConfigs; - InputConfig* mKeyboardInputConfig; - std::map* mPrevAxisValues; }; #endif diff --git a/src/main.cpp b/src/main.cpp index d0f347903..bb1084cd3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -172,7 +172,10 @@ int main(int argc, char* argv[]) timeSinceLastEvent = 0; } break; - + case InputManager::SDL_USEREVENT_POLLDEVICES: + //try to poll input devices, but do not necessarily wake up... + window.getInputManager()->parseEvent(event); + break; case SDL_QUIT: running = false; break;