From 130d5570e08c32d9db9d3e5324c2c8e99d1cdb02 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Tue, 20 Sep 2016 11:14:07 +0300 Subject: [PATCH] Implement game controllers on Windows through XInput --- Build | 1 + source/input/windows/gamecontroller.cpp | 188 ++++++++++++++++++ .../input/windows/gamecontroller_platform.h | 52 +++++ 3 files changed, 241 insertions(+) create mode 100644 source/input/windows/gamecontroller.cpp create mode 100644 source/input/windows/gamecontroller_platform.h diff --git a/Build b/Build index 69d9b6b..d9d7fc7 100644 --- a/Build +++ b/Build @@ -14,6 +14,7 @@ package "mspgui" build_info { library "gdi32"; + library "xinput"; }; }; if_arch "darwin" diff --git a/source/input/windows/gamecontroller.cpp b/source/input/windows/gamecontroller.cpp new file mode 100644 index 0000000..43a947b --- /dev/null +++ b/source/input/windows/gamecontroller.cpp @@ -0,0 +1,188 @@ +#include +#include +#include "gamecontroller.h" +#include "gamecontroller_platform.h" + +using namespace std; + +namespace Msp { +namespace Input { + +vector GameController::Private::detected_controllers; + +GameController::GameController(unsigned index) +{ + if(!detect_done) + detect(); + if(index>=Private::detected_controllers.size()) + throw device_not_available(format("GameController(%d)", index)); + + unsigned user_index = Private::detected_controllers[index]; + + XINPUT_STATE state; + DWORD err = XInputGetState(user_index, &state); + if(err!=ERROR_SUCCESS) + throw device_not_available(format("GameController(%d)", index)); + + priv = new Private; + priv->index = user_index; + priv->update_state(*this, state, false); + + name = format("Controller %d", user_index); +} + +GameController::~GameController() +{ + use_event_dispatcher(0); + + delete priv; +} + +unsigned GameController::detect() +{ + Private::detected_controllers.clear(); + + XINPUT_STATE state; + for(unsigned i=0; iremove(*priv->event_pipe); + + event_disp = ed; + if(event_disp) + { + if(!priv->event_pipe) + { + priv->event_pipe = new IO::Pipe; + priv->event_pipe->signal_data_available.connect(sigc::mem_fun(this, static_cast(&GameController::tick))); + priv->timer_slot = &GameControllerTimerThread::add_slot(); + priv->timer_slot->signal_timeout.connect(sigc::mem_fun(priv, &Private::generate_event)); + } + + event_disp->add(*priv->event_pipe); + } + else if(priv->event_pipe) + { + GameControllerTimerThread::remove_slot(*priv->timer_slot); + priv->timer_slot = 0; + delete priv->event_pipe; + priv->event_pipe = 0; + } +} + +void GameController::tick() +{ + if(priv->event_pipe) + { + char buf[64]; + priv->event_pipe->read(buf, sizeof(buf)); + } + + XINPUT_STATE state; + DWORD err = XInputGetState(priv->index, &state); + if(err==ERROR_SUCCESS) + priv->update_state(*this, state, true); +} + +void GameController::tick(const Time::TimeDelta &) +{ + tick(); +} + + +GameController::Private::Private(): + index(0), + last_packet_number(0), + event_pipe(0), + timer_slot(0) +{ } + +bool GameController::Private::generate_event() +{ + event_pipe->put(1); + return true; +} + +void GameController::Private::update_state(GameController &ctrl, const XINPUT_STATE &state, bool event) +{ + if(state.dwPacketNumber==last_packet_number) + return; + last_packet_number = state.dwPacketNumber; + + ctrl.set_axis_value(0, state.Gamepad.sThumbLX/32768.0, event); + ctrl.set_axis_value(1, -state.Gamepad.sThumbLY/32768.0, event); + ctrl.set_axis_value(3, state.Gamepad.sThumbRX/32768.0, event); + ctrl.set_axis_value(4, -state.Gamepad.sThumbRY/32768.0, event); + WORD dpad_x = state.Gamepad.wButtons&(XINPUT_GAMEPAD_DPAD_LEFT|XINPUT_GAMEPAD_DPAD_RIGHT); + WORD dpad_y = state.Gamepad.wButtons&(XINPUT_GAMEPAD_DPAD_UP|XINPUT_GAMEPAD_DPAD_DOWN); + ctrl.set_axis_value(6, (dpad_x==XINPUT_GAMEPAD_DPAD_LEFT ? -1 : dpad_x==XINPUT_GAMEPAD_DPAD_RIGHT ? 1 : 0), event); + ctrl.set_axis_value(7, (dpad_y==XINPUT_GAMEPAD_DPAD_UP ? -1 : dpad_y==XINPUT_GAMEPAD_DPAD_DOWN ? 1 : 0), event); + ctrl.set_button_state(0, state.Gamepad.wButtons&XINPUT_GAMEPAD_A, event); + ctrl.set_button_state(1, state.Gamepad.wButtons&XINPUT_GAMEPAD_B, event); + ctrl.set_button_state(2, state.Gamepad.wButtons&XINPUT_GAMEPAD_X, event); + ctrl.set_button_state(3, state.Gamepad.wButtons&XINPUT_GAMEPAD_Y, event); + ctrl.set_button_state(4, state.Gamepad.wButtons&XINPUT_GAMEPAD_LEFT_SHOULDER, event); + ctrl.set_button_state(5, state.Gamepad.wButtons&XINPUT_GAMEPAD_RIGHT_SHOULDER, event); + ctrl.set_button_state(6, state.Gamepad.wButtons&XINPUT_GAMEPAD_BACK, event); + ctrl.set_button_state(7, state.Gamepad.wButtons&XINPUT_GAMEPAD_START, event); + ctrl.set_button_state(8, state.Gamepad.wButtons&XINPUT_GAMEPAD_LEFT_THUMB, event); + ctrl.set_button_state(9, state.Gamepad.wButtons&XINPUT_GAMEPAD_RIGHT_THUMB, event); +} + + +GameControllerTimerThread *GameControllerTimerThread::thread = 0; + +GameControllerTimerThread::GameControllerTimerThread(): + Thread("GameController"), + n_users(0) +{ + launch(); +} + +GameControllerTimerThread::~GameControllerTimerThread() +{ + timer.add(Time::zero); + join(); +} + +Time::Timer::Slot &GameControllerTimerThread::add_slot() +{ + if(!thread) + thread = new GameControllerTimerThread; + ++thread->n_users; + return thread->timer.add(100*Time::msec); +} + +void GameControllerTimerThread::remove_slot(Time::Timer::Slot &slot) +{ + thread->timer.cancel(slot); + if(!--thread->n_users) + { + thread->timer.add(Time::zero); + delete thread; + thread = 0; + } +} + +void GameControllerTimerThread::main() +{ + while(1) + { + timer.tick(); + if(!n_users) + break; + } +} + +} // namespace Input +} // namespace Msp diff --git a/source/input/windows/gamecontroller_platform.h b/source/input/windows/gamecontroller_platform.h new file mode 100644 index 0000000..46c2dd8 --- /dev/null +++ b/source/input/windows/gamecontroller_platform.h @@ -0,0 +1,52 @@ +#ifndef MSP_INPUT_GAMECONTROLLER_PLATFORM_H_ +#define MSP_INPUT_GAMECONTROLLER_PLATFORM_H_ + +#include +#include +#include +#include +#include + +namespace Msp { +namespace Input { + +class GameControllerTimerThread: public Msp::Thread +{ +private: + Time::Timer timer; + unsigned n_users; + + static GameControllerTimerThread *thread; + + GameControllerTimerThread(); + ~GameControllerTimerThread(); +public: + static Time::Timer::Slot &add_slot(); + static void remove_slot(Time::Timer::Slot &); + +private: + virtual void main(); +}; + + +struct GameController::Private +{ + unsigned index; + unsigned last_packet_number; + IO::Pipe *event_pipe; + Time::Timer::Slot *timer_slot; + + static std::vector detected_controllers; + + Private(); + + bool generate_event(); + + void update_state(GameController &, const XINPUT_STATE &, bool); + +}; + +} // namespace Input +} // namespace Msp + +#endif -- 2.45.2