]> git.tdb.fi Git - libs/gui.git/commitdiff
Implement game controllers on Windows through XInput
authorMikko Rasa <tdb@tdb.fi>
Tue, 20 Sep 2016 08:14:07 +0000 (11:14 +0300)
committerMikko Rasa <tdb@tdb.fi>
Tue, 20 Sep 2016 08:14:07 +0000 (11:14 +0300)
Build
source/input/windows/gamecontroller.cpp [new file with mode: 0644]
source/input/windows/gamecontroller_platform.h [new file with mode: 0644]

diff --git a/Build b/Build
index 69d9b6bfa1427c38a55694159578158481b684d0..d9d7fc73ac6c795094bcc672a74999b253d2d20c 100644 (file)
--- 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 (file)
index 0000000..43a947b
--- /dev/null
@@ -0,0 +1,188 @@
+#include <msp/core/application.h>
+#include <msp/strings/format.h>
+#include "gamecontroller.h"
+#include "gamecontroller_platform.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Input {
+
+vector<unsigned> 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; i<XUSER_MAX_COUNT; ++i)
+               if(XInputGetState(i, &state)==ERROR_SUCCESS)
+                       Private::detected_controllers.push_back(i);
+
+       detect_done = true;
+       n_detected_controllers = Private::detected_controllers.size();
+
+       return Private::detected_controllers.size();
+}
+
+void GameController::use_event_dispatcher(IO::EventDispatcher *ed)
+{
+       if(event_disp)
+               event_disp->remove(*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<void (GameController::*)()>(&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 (file)
index 0000000..46c2dd8
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef MSP_INPUT_GAMECONTROLLER_PLATFORM_H_
+#define MSP_INPUT_GAMECONTROLLER_PLATFORM_H_
+
+#include <windows.h>
+#include <xinput.h>
+#include <msp/core/thread.h>
+#include <msp/io/pipe.h>
+#include <msp/time/timer.h>
+
+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<unsigned> detected_controllers;
+
+       Private();
+
+       bool generate_event();
+
+       void update_state(GameController &, const XINPUT_STATE &, bool);
+
+};
+
+} // namespace Input
+} // namespace Msp
+
+#endif