Add all the files

This commit is contained in:
Exzap 2022-08-22 22:21:23 +02:00
parent e3db07a16a
commit d60742f52b
1445 changed files with 430238 additions and 0 deletions

View file

@ -0,0 +1,246 @@
#include "input/api/Controller.h"
#include "gui/guiWrapper.h"
ControllerBase::ControllerBase(std::string_view uuid, std::string_view display_name)
: m_uuid{uuid}, m_display_name{display_name}
{
}
const ControllerState& ControllerBase::update_state()
{
if (!m_is_calibrated)
calibrate();
ControllerState result = raw_state();
// ignore default buttons
result.buttons &= ~m_default_state.buttons;
// apply deadzone and range and ignore default axis values
apply_axis_setting(result.axis, m_default_state.axis, m_settings.axis);
apply_axis_setting(result.rotation, m_default_state.rotation, m_settings.rotation);
apply_axis_setting(result.trigger, m_default_state.trigger, m_settings.trigger);
#define APPLY_AXIS_BUTTON(_axis_, _flag_) \
if (result._axis_.x < -ControllerState::kAxisThreshold) \
result.buttons.set((_flag_) + (kAxisXN - kAxisXP)); \
else if (result._axis_.x > ControllerState::kAxisThreshold) \
result.buttons.set((_flag_)); \
if (result._axis_.y < -ControllerState::kAxisThreshold) \
result.buttons.set((_flag_) + 1 + (kAxisXN - kAxisXP)); \
else if (result._axis_.y > ControllerState::kAxisThreshold) \
result.buttons.set((_flag_) + 1);
if (result.axis.x < -ControllerState::kAxisThreshold)
result.buttons.set((kAxisXP) + (kAxisXN - kAxisXP));
else if (result.axis.x > ControllerState::kAxisThreshold)
result.buttons.set((kAxisXP));
if (result.axis.y < -ControllerState::kAxisThreshold)
result.buttons.set((kAxisXP) + 1 + (kAxisXN - kAxisXP));
else if (result.axis.y > ControllerState::kAxisThreshold)
result.buttons.set((kAxisXP) + 1);;
APPLY_AXIS_BUTTON(rotation, kRotationXP);
APPLY_AXIS_BUTTON(trigger, kTriggerXP);
/*
// positive values
kAxisXP,
kAxisYP,
kRotationXP,
kRotationYP,
kTriggerXP,
kTriggerYP,
// negative values
kAxisXN,
kAxisYN,
kRotationXN,
kRotationYN,
kTriggerXN,
kTriggerYN,
*/
#undef APPLY_AXIS_BUTTON
m_last_state = result;
return m_last_state;
}
void ControllerBase::apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value,
const AxisSetting& setting) const
{
constexpr float kMaxValue = 1.0f + ControllerState::kMinAxisValue;
if (setting.deadzone < 1.0f)
{
if (axis.x < default_value.x)
axis.x = (axis.x - default_value.x) / (kMaxValue + default_value.x);
else
axis.x = (axis.x - default_value.x) / (kMaxValue - default_value.x);
if (axis.y < default_value.y)
axis.y = (axis.y - default_value.y) / (kMaxValue + default_value.y);
else
axis.y = (axis.y - default_value.y) / (kMaxValue - default_value.y);
auto len = length(axis);
if (len >= setting.deadzone)
{
axis *= setting.range;
len = length(axis);
// Scaled Radial Dead Zone: stickInput = stickInput.normalized * ((stickInput.magnitude - deadzone) / (1 - deadzone));
if (len > 0)
{
axis = normalize(axis);
axis *= ((len - setting.deadzone) / (kMaxValue - setting.deadzone));
if (length(axis) > 1.0f)
axis = normalize(axis);
}
if (axis.x != 0 || axis.y != 0)
{
if (std::abs(axis.x) < ControllerState::kMinAxisValue)
axis.x = ControllerState::kMinAxisValue;
if (std::abs(axis.y) < ControllerState::kMinAxisValue)
axis.y = ControllerState::kMinAxisValue;
}
return;
}
}
axis = {0, 0};
}
bool ControllerBase::operator==(const ControllerBase& c) const
{
return api() == c.api() && uuid() == c.uuid();
}
float ControllerBase::get_axis_value(uint64 button) const
{
if (m_last_state.buttons.test(button))
{
if (button <= kButtonNoneAxisMAX || !has_axis())
return 1.0f;
switch (button)
{
case kAxisXP:
case kAxisXN:
return std::abs(m_last_state.axis.x);
case kAxisYP:
case kAxisYN:
return std::abs(m_last_state.axis.y);
case kRotationXP:
case kRotationXN:
return std::abs(m_last_state.rotation.x);
case kRotationYP:
case kRotationYN:
return std::abs(m_last_state.rotation.y);
case kTriggerXP:
case kTriggerXN:
return std::abs(m_last_state.trigger.x);
case kTriggerYP:
case kTriggerYN:
return std::abs(m_last_state.trigger.y);
}
}
return 0;
}
const ControllerState& ControllerBase::calibrate()
{
m_default_state = raw_state();
m_is_calibrated = is_connected();
return m_default_state;
}
std::string ControllerBase::get_button_name(uint64 button) const
{
switch (button)
{
case kButtonZL: return "ZL";
case kButtonZR: return "ZR";
case kButtonUp: return "DPAD-Up";
case kButtonDown: return "DPAD-Down";
case kButtonLeft: return "DPAD-Left";
case kButtonRight: return "DPAD-Right";
case kAxisXP: return "X-Axis+";
case kAxisYP: return "Y-Axis+";
case kAxisXN: return "X-Axis-";
case kAxisYN: return "Y-Axis-";
case kRotationXP: return "X-Rotation+";
case kRotationYP: return "Y-Rotation+";
case kRotationXN: return "X-Rotation-";
case kRotationYN: return "Y-Rotation-";
case kTriggerXP: return "X-Trigger+";
case kTriggerYP: return "Y-Trigger+";
case kTriggerXN: return "X-Trigger-";
case kTriggerYN: return "y-Trigger-";
}
return fmt::format("Button {}", (uint64)button);
}
ControllerBase::Settings ControllerBase::get_settings() const
{
std::scoped_lock lock(m_settings_mutex);
return m_settings;
}
void ControllerBase::set_settings(const Settings& settings)
{
std::scoped_lock lock(m_settings_mutex);
m_settings = settings;
}
void ControllerBase::set_axis_settings(const AxisSetting& settings)
{
std::scoped_lock lock(m_settings_mutex);
m_settings.axis = settings;
}
void ControllerBase::set_rotation_settings(const AxisSetting& settings)
{
std::scoped_lock lock(m_settings_mutex);
m_settings.rotation = settings;
}
void ControllerBase::set_trigger_settings(const AxisSetting& settings)
{
std::scoped_lock lock(m_settings_mutex);
m_settings.trigger = settings;
}
void ControllerBase::set_rumble(float rumble)
{
std::scoped_lock lock(m_settings_mutex);
m_settings.rumble = rumble;
}
void ControllerBase::set_use_motion(bool state)
{
std::scoped_lock lock(m_settings_mutex);
m_settings.motion = state;
}

201
src/input/api/Controller.h Normal file
View file

@ -0,0 +1,201 @@
#pragma once
#include "input/InputManager.h"
#include "input/motion/MotionSample.h"
namespace pugi
{
class xml_node;
}
enum Buttons2 : uint64
{
// General
kButton0,
kButton1,
kButton2,
kButton3,
kButton4,
kButton5,
kButton6,
kButton7,
kButton8,
kButton9,
kButton10,
kButton11,
kButton12,
kButton13,
kButton14,
kButton15,
kButton16,
kButton17,
kButton18,
kButton19,
kButton20,
kButton21,
kButton22,
kButton23,
kButton24,
kButton25,
kButton26,
kButton27,
kButton28,
kButton29,
kButton30,
kButton31,
// Trigger
kButtonZL,
kButtonZR,
// DPAD
kButtonUp,
kButtonDown,
kButtonLeft,
kButtonRight,
// positive values
kAxisXP,
kAxisYP,
kRotationXP,
kRotationYP,
kTriggerXP,
kTriggerYP,
// negative values
kAxisXN,
kAxisYN,
kRotationXN,
kRotationYN,
kTriggerXN,
kTriggerYN,
kButtonMAX,
kButtonNoneAxisMAX = kButtonRight,
kButtonAxisStart = kAxisXP,
};
class ControllerBase
{
public:
ControllerBase(std::string_view uuid, std::string_view display_name);
virtual ~ControllerBase() = default;
const std::string& uuid() const { return m_uuid; }
const std::string& display_name() const { return m_display_name; }
virtual std::string_view api_name() const = 0;
virtual InputAPI::Type api() const = 0;
virtual void update() {}
virtual bool connect() { return is_connected(); }
virtual bool is_connected() = 0;
virtual bool has_battery() { return false; }
virtual bool has_low_battery() { return false; }
const ControllerState& calibrate();
const ControllerState& update_state();
const ControllerState& get_state() const { return m_last_state; }
const ControllerState& get_default_state() { return is_calibrated() ? m_default_state : calibrate(); }
virtual ControllerState raw_state() = 0;
bool is_calibrated() const { return m_is_calibrated; }
float get_axis_value(uint64 button) const;
virtual bool has_axis() const { return true; }
bool use_motion() { return has_motion() && m_settings.motion; }
virtual bool has_motion() { return false; }
virtual MotionSample get_motion_sample() { return {}; }
virtual bool has_position() { return false; }
virtual glm::vec2 get_position() { return {}; }
virtual glm::vec2 get_prev_position() { return {}; }
virtual bool has_rumble() { return false; }
virtual void start_rumble() {}
virtual void stop_rumble() {}
virtual std::string get_button_name(uint64 button) const;
virtual void save(pugi::xml_node& node){}
virtual void load(const pugi::xml_node& node){}
struct AxisSetting
{
AxisSetting(float deadzone = 0.25f) : deadzone(deadzone) {}
float deadzone;
float range = 1.0f;
};
struct Settings
{
AxisSetting axis{}, rotation{}, trigger{};
float rumble = 0;
bool motion = false; // only valid when has_motion is true
};
Settings get_settings() const;
void set_settings(const Settings& settings);
void set_axis_settings(const AxisSetting& settings);
void set_rotation_settings(const AxisSetting& settings);
void set_trigger_settings(const AxisSetting& settings);
void set_rumble(float rumble);
void set_use_motion(bool state);
void apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value, const AxisSetting& setting) const;
bool operator==(const ControllerBase& c) const;
bool operator!=(const ControllerBase& c) const { return !(*this == c); }
protected:
std::string m_uuid;
std::string m_display_name;
ControllerState m_last_state{};
bool m_is_calibrated = false;
ControllerState m_default_state{};
mutable std::mutex m_settings_mutex;
Settings m_settings{};
};
template<class TProvider>
class Controller : public ControllerBase
{
public:
Controller(std::string_view uuid, std::string_view display_name)
: ControllerBase(uuid, display_name)
{
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
m_provider = std::dynamic_pointer_cast<TProvider>(InputManager::instance().get_api_provider(TProvider::kAPIType));
cemu_assert_debug(m_provider != nullptr);
}
Controller(std::string_view uuid, std::string_view display_name, const ControllerProviderSettings& settings)
: ControllerBase(uuid, display_name)
{
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
m_provider = std::dynamic_pointer_cast<TProvider>(InputManager::instance().get_api_provider(TProvider::kAPIType, settings));
cemu_assert_debug(m_provider != nullptr);
}
// update provider if settings are different from default provider
void update_provider(std::shared_ptr<TProvider> provider)
{
m_provider = provider;
}
protected:
using base_type = Controller<TProvider>;
std::shared_ptr<TProvider> m_provider;
};
using ControllerPtr = std::shared_ptr<ControllerBase>;

View file

@ -0,0 +1,88 @@
#pragma once
#include "input/api/InputAPI.h"
class ControllerBase;
struct ControllerProviderSettings
{
virtual ~ControllerProviderSettings() = default;
virtual bool operator==(const ControllerProviderSettings&) const = 0;
};
class ControllerProviderBase
{
public:
ControllerProviderBase() = default;
virtual ~ControllerProviderBase() = default;
virtual InputAPI::Type api() const = 0;
std::string_view api_name() const { return to_string(api()); }
virtual std::vector<std::shared_ptr<ControllerBase>> get_controllers() = 0;
virtual bool has_settings() const
{
return false;
}
virtual bool operator==(const ControllerProviderBase& p) const
{
return api() == p.api();
}
virtual bool operator==(const ControllerProviderSettings& p) const
{
return false;
}
};
using ControllerProviderPtr = std::shared_ptr<ControllerProviderBase>;
template <typename TSettings>
class ControllerProvider : public ControllerProviderBase
{
using base_type = ControllerProviderBase;
public:
ControllerProvider() = default;
ControllerProvider(const TSettings& settings)
: m_settings(settings)
{}
bool has_settings() const override
{
return true;
}
const TSettings& get_settings() const
{
return m_settings;
}
bool operator==(const ControllerProviderBase& p) const override
{
if (!base_type::operator==(p))
return false;
if (!p.has_settings())
return false;
auto* ptr = dynamic_cast<const ControllerProvider<TSettings>*>(&p);
if (!ptr)
return false;
return base_type::operator==(p) && m_settings == ptr->m_settings;
}
bool operator==(const ControllerProviderSettings& p) const override
{
auto* ptr = dynamic_cast<const TSettings*>(&p);
if (!ptr)
return false;
return m_settings == *ptr;
}
protected:
TSettings m_settings{};
};

View file

@ -0,0 +1,13 @@
#include "input/api/ControllerState.h"
bool ControllerState::operator==(const ControllerState& other) const
{
return buttons == other.buttons;
/*&& (std::signbit(axis.x) == std::signbit(other.axis.x) && std::abs(axis.x - other.axis.x) <= kAxisThreshold)
&& (std::signbit(axis.y) == std::signbit(other.axis.y) && std::abs(axis.y - other.axis.y) <= kAxisThreshold)
&& (std::signbit(rotation.x) == std::signbit(other.rotation.x) && std::abs(rotation.x - other.rotation.x) <= kAxisThreshold)
&& (std::signbit(rotation.y) == std::signbit(other.rotation.y) && std::abs(rotation.y - other.rotation.y) <= kAxisThreshold)
&& (std::signbit(trigger.x) == std::signbit(other.trigger.x) && std::abs(trigger.x - other.trigger.x) <= kAxisThreshold)
&& (std::signbit(trigger.y) == std::signbit(other.trigger.y) && std::abs(trigger.y - other.trigger.y) <= kAxisThreshold);*/
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <bitset>
#include <glm/vec2.hpp>
struct ControllerState
{
// when does a axis counts as pressed
constexpr static float kAxisThreshold = 0.1f;
// on the real console the stick x or y values never really reach 0.0 if one of the axis is moved
// some games rely on this due to incorrectly checking if the stick is tilted via if (vstick.x != 0 && vstick.y != 0)
// here we simulate a slight bias if the axis is almost perfectly centered
constexpr static float kMinAxisValue = 0.0000001f;
// [-1; 1]
glm::vec2 axis{ };
glm::vec2 rotation{ };
glm::vec2 trigger{ };
std::bitset<256> buttons{};
uint64 last_state = 0;
bool operator==(const ControllerState& other) const;
bool operator!=(const ControllerState& other) const
{
return !(*this == other);
}
};

View file

@ -0,0 +1,168 @@
#include "input/api/DSU/DSUController.h"
#include <boost/program_options/value_semantic.hpp>
DSUController::DSUController(uint32 index)
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index)
{
if (index >= DSUControllerProvider::kMaxClients)
throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}",
DSUControllerProvider::kMaxClients, index));
}
DSUController::DSUController(uint32 index, const DSUProviderSettings& settings)
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1), settings), m_index(index)
{
if (index >= DSUControllerProvider::kMaxClients)
throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}",
DSUControllerProvider::kMaxClients, index));
}
void DSUController::save(pugi::xml_node& node)
{
base_type::save(node);
node.append_child("ip").append_child(pugi::node_pcdata).set_value(
fmt::format("{}", m_provider->get_settings().ip).c_str());
node.append_child("port").append_child(pugi::node_pcdata).set_value(
fmt::format("{}", m_provider->get_settings().port).c_str());
}
void DSUController::load(const pugi::xml_node& node)
{
base_type::load(node);
DSUProviderSettings settings;
if (const auto value = node.child("ip"))
settings.ip = value.child_value();
if (const auto value = node.child("port"))
settings.port = ConvertString<uint16>(value.child_value());
const auto provider = InputManager::instance().get_api_provider(api(), settings);
update_provider(std::dynamic_pointer_cast<DSUControllerProvider>(provider));
connect();
}
bool DSUController::connect()
{
if (is_connected())
return true;
m_provider->request_pad_data(m_index);
return is_connected();
}
bool DSUController::is_connected()
{
return m_provider->is_connected(m_index);
}
MotionSample DSUController::get_motion_sample()
{
return m_provider->get_motion_sample(m_index);
}
bool DSUController::has_position()
{
const auto state = m_provider->get_state(m_index);
return state.data.tpad1.active || state.data.tpad2.active;
}
glm::vec2 DSUController::get_position()
{
// touchpad resolution is 1920x942
const auto state = m_provider->get_state(m_index);
if (state.data.tpad1.active)
return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f};
if (state.data.tpad2.active)
return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f};
return {};
}
glm::vec2 DSUController::get_prev_position()
{
const auto state = m_provider->get_prev_state(m_index);
if (state.data.tpad1.active)
return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f};
if (state.data.tpad2.active)
return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f};
return {};
}
std::string DSUController::get_button_name(uint64 button) const
{
switch (button)
{
case kButton0: return "Share";
case kButton1: return "Stick L";
case kButton2: return "Stick R";
case kButton3: return "Options";
case kButton4: return "Up";
case kButton5: return "Right";
case kButton6: return "Down";
case kButton7: return "Left";
case kButton8: return "ZL";
case kButton9: return "ZR";
case kButton10: return "L";
case kButton11: return "R";
case kButton12: return "Triangle";
case kButton13: return "Circle";
case kButton14: return "Cross";
case kButton15: return "Square";
case kButton16: return "Touch";
}
return base_type::get_button_name(button);
}
ControllerState DSUController::raw_state()
{
ControllerState result{};
if (!is_connected())
return result;
const auto state = m_provider->get_state(m_index);
// didn't read any data from the controller yet
if (state.info.state != DsState::Connected)
return result;
int bitindex = 0;
for (int i = 0; i < 8; ++i, ++bitindex)
{
if (HAS_BIT(state.data.state1, i))
{
result.buttons.set(bitindex);
}
}
for (int i = 0; i < 8; ++i, ++bitindex)
{
if (HAS_BIT(state.data.state2, i))
{
result.buttons.set(bitindex);
}
}
if (state.data.touch)
result.buttons.set(kButton16);
result.axis.x = (float)state.data.lx / std::numeric_limits<uint8>::max();
result.axis.x = (result.axis.x * 2.0f) - 1.0f;
result.axis.y = (float)state.data.ly / std::numeric_limits<uint8>::max();
result.axis.y = (result.axis.y * 2.0f) - 1.0f;
result.rotation.x = (float)state.data.rx / std::numeric_limits<uint8>::max();
result.rotation.x = (result.rotation.x * 2.0f) - 1.0f;
result.rotation.y = (float)state.data.ry / std::numeric_limits<uint8>::max();
result.rotation.y = (result.rotation.y * 2.0f) - 1.0f;
return result;
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "input/api/Controller.h"
#include "input/api/DSU/DSUControllerProvider.h"
#include "Cafe/HW/AI/AI.h"
#include "Cafe/HW/AI/AI.h"
#include "Cafe/HW/AI/AI.h"
#include "Cafe/HW/AI/AI.h"
class DSUController : public Controller<DSUControllerProvider>
{
public:
DSUController(uint32 index);
DSUController(uint32 index, const DSUProviderSettings& settings);
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::DSUClient) == "DSUController");
return to_string(InputAPI::DSUClient);
}
InputAPI::Type api() const override { return InputAPI::DSUClient; }
void save(pugi::xml_node& node) override;
void load(const pugi::xml_node& node) override;
bool connect() override;
bool is_connected() override;
bool has_motion() override { return true; }
MotionSample get_motion_sample() override;
bool has_position() override;
glm::vec2 get_position() override;
glm::vec2 get_prev_position() override;
std::string get_button_name(uint64 button) const override;
protected:
ControllerState raw_state() override;
private:
uint32 m_index;
};

View file

@ -0,0 +1,448 @@
#include "input/api/DSU/DSUControllerProvider.h"
#include "input/api/DSU/DSUController.h"
DSUControllerProvider::DSUControllerProvider()
: base_type(), m_uid(rand()), m_socket(m_io_service)
{
if (!connect())
{
throw std::runtime_error("dsu client can't open the udp connection");
}
m_running = true;
m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this);
m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this);
request_version();
}
DSUControllerProvider::DSUControllerProvider(const DSUProviderSettings& settings)
: base_type(settings), m_uid(rand()), m_socket(m_io_service)
{
if (!connect())
{
throw std::runtime_error("dsu client can't open the udp connection");
}
m_running = true;
m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this);
m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this);
request_version();
}
DSUControllerProvider::~DSUControllerProvider()
{
if (m_running)
{
m_running = false;
m_writer_thread.join();
m_reader_thread.join();
}
}
std::vector<std::shared_ptr<ControllerBase>> DSUControllerProvider::get_controllers()
{
std::vector<ControllerPtr> result;
std::array<uint8_t, kMaxClients> indices;
for (auto i = 0; i < kMaxClients; ++i)
indices[i] = get_packet_index(i);
request_pad_info();
const auto controller_result = wait_update(indices, 3000);
for (auto i = 0; i < kMaxClients; ++i)
{
if (controller_result[i] && is_connected(i))
result.emplace_back(std::make_shared<DSUController>(i, m_settings));
}
return result;
}
bool DSUControllerProvider::connect()
{
// already connected?
if (m_receiver_endpoint.address().to_string() == get_settings().ip && m_receiver_endpoint.port() == get_settings().port)
return true;
try
{
using namespace boost::asio;
ip::udp::resolver resolver(m_io_service);
const ip::udp::resolver::query query(ip::udp::v4(), get_settings().ip, fmt::format("{}", get_settings().port));
m_receiver_endpoint = *resolver.resolve(query);
if (m_socket.is_open())
m_socket.close();
m_socket.open(ip::udp::v4());
// set timeout for our threads to give a chance to exit
m_socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{200});
// reset data
m_state = {};
m_prev_state = {};
// restart threads
return true;
}
catch (const std::exception& ex)
{
forceLog_printf("dsu client connect error: %s", ex.what());
return false;
}
}
bool DSUControllerProvider::is_connected(uint8_t index) const
{
if (index >= kMaxClients)
return false;
std::scoped_lock lock(m_mutex[index]);
return m_state[index].info.state == DsState::Connected;
}
DSUControllerProvider::ControllerState DSUControllerProvider::get_state(uint8_t index) const
{
if (index >= kMaxClients)
return {};
std::scoped_lock lock(m_mutex[index]);
return m_state[index];
}
DSUControllerProvider::ControllerState DSUControllerProvider::get_prev_state(uint8_t index) const
{
if (index >= kMaxClients)
return {};
std::scoped_lock lock(m_mutex[index]);
return m_prev_state[index];
}
std::array<bool, DSUControllerProvider::kMaxClients> DSUControllerProvider::wait_update(
const std::array<uint8_t, kMaxClients>& indices, size_t timeout) const
{
std::array<bool, kMaxClients> result{false, false, false, false};
const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout);
do
{
for (int i = 0; i < kMaxClients; ++i)
{
if (result[i])
continue;
std::unique_lock lock(m_mutex[i]);
result[i] = indices[i] < m_state[i].packet_index;
}
if (std::all_of(result.cbegin(), result.cend(), [](const bool& v) { return v == true; }))
break;
//std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::this_thread::yield();
}
while (std::chrono::steady_clock::now() < end);
return result;
}
bool DSUControllerProvider::wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const
{
if (index >= kMaxClients)
return false;
std::unique_lock lock(m_mutex[index]);
if (packet_index < m_state[index].packet_index)
return true;
const auto result = m_wait_cond[index].wait_for(lock, std::chrono::milliseconds(timeout),
[this, index, packet_index]()
{
return packet_index < m_state[index].packet_index;
});
return result;
}
uint32_t DSUControllerProvider::get_packet_index(uint8_t index) const
{
std::scoped_lock lock(m_mutex[index]);
return m_state[index].packet_index;
}
void DSUControllerProvider::request_version()
{
auto msg = std::make_unique<VersionRequest>(m_uid);
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
}
void DSUControllerProvider::request_pad_info()
{
auto msg = std::make_unique<ListPorts>(m_uid, 4, std::array<uint8_t, 4>{0, 1, 2, 3});
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
}
void DSUControllerProvider::request_pad_info(uint8_t index)
{
if (index >= kMaxClients)
return;
auto msg = std::make_unique<ListPorts>(m_uid, 1, std::array<uint8_t, 4>{index});
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
}
void DSUControllerProvider::request_pad_data()
{
auto msg = std::make_unique<DataRequest>(m_uid);
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
}
void DSUControllerProvider::request_pad_data(uint8_t index)
{
if (index >= kMaxClients)
return;
auto msg = std::make_unique<DataRequest>(m_uid, index);
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
}
MotionSample DSUControllerProvider::get_motion_sample(uint8_t index) const
{
if (index >= kMaxClients)
return MotionSample();
std::scoped_lock lock(m_mutex[index]);
return m_state[index].motion_sample;
}
void DSUControllerProvider::reader_thread()
{
SetThreadName("DSUControllerProvider::reader_thread");
bool first_read = true;
while (m_running.load(std::memory_order_relaxed))
{
ServerMessage* msg;
//try
//{
std::array<char, 100> recv_buf; // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init)
boost::asio::ip::udp::endpoint sender_endpoint;
boost::system::error_code ec{};
const size_t len = m_socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint, 0, ec);
if (ec)
{
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: exception %s\n", ex.what());
#endif
// there's probably no server listening on the given address:port
if (first_read) // workaroud: first read always fails?
first_read = false;
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(250));
std::this_thread::yield();
}
continue;
}
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: received message with len: 0x%llx\n", len);
#endif
if (len < sizeof(ServerMessage)) // cant be a valid message
continue;
msg = (ServerMessage*)recv_buf.data();
// }
// catch (const std::exception&)
// {
//#ifdef DEBUG_DSU_CLIENT
// printf(" DSUControllerProvider::ReaderThread: exception %s\n", ex.what());
//#endif
//
// // there's probably no server listening on the given address:port
// if (first_read) // workaroud: first read always fails?
// first_read = false;
// else
// {
// std::this_thread::sleep_for(std::chrono::milliseconds(250));
// std::this_thread::yield();
// }
// continue;
// }
uint8_t index = 0xFF;
switch (msg->GetMessageType())
{
case MessageType::Version:
{
const auto rsp = (VersionResponse*)msg;
if (!rsp->IsValid())
{
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: VersionResponse is invalid!\n");
#endif
continue;
}
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: server version is: 0x%x\n", rsp->GetVersion());
#endif
m_server_version = rsp->GetVersion();
// wdc
break;
}
case MessageType::Information:
{
const auto info = (PortInfo*)msg;
if (!info->IsValid())
{
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: PortInfo is invalid!\n");
#endif
continue;
}
index = info->GetIndex();
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: received PortInfo for index %d\n", index);
#endif
auto& mutex = m_mutex[index];
std::scoped_lock lock(mutex);
m_prev_state[index] = m_state[index];
m_state[index] = *info;
m_wait_cond[index].notify_all();
break;
}
case MessageType::Data:
{
const auto rsp = (DataResponse*)msg;
if (!rsp->IsValid())
{
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: DataResponse is invalid!\n");
#endif
continue;
}
index = rsp->GetIndex();
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: received DataResponse for index %d\n", index);
#endif
auto& mutex = m_mutex[index];
std::scoped_lock lock(mutex);
m_prev_state[index] = m_state[index];
m_state[index] = *rsp;
m_wait_cond[index].notify_all();
// update motion info immediately, guaranteeing that we dont drop packets
integrate_motion(index, *rsp);
break;
}
}
if (index != 0xFF)
request_pad_data(index);
}
}
void DSUControllerProvider::writer_thread()
{
SetThreadName("DSUControllerProvider::writer_thread");
while (m_running.load(std::memory_order_relaxed))
{
std::unique_lock lock(m_writer_mutex);
while (m_writer_jobs.empty())
{
if (m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout)
{
if (!m_running.load(std::memory_order_relaxed))
return;
}
}
const auto msg = std::move(m_writer_jobs.front());
m_writer_jobs.pop();
lock.unlock();
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::WriterThread: sending message: 0x%x (len: 0x%x)\n", (int)msg->GetMessageType(), msg->GetSize());
#endif
try
{
m_socket.send_to(boost::asio::buffer(msg.get(), msg->GetSize()), m_receiver_endpoint);
}
catch (const std::exception&)
{
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::WriterThread: exception %s\n", ex.what());
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}
}
}
void DSUControllerProvider::integrate_motion(uint8_t index, const DataResponse& data_response)
{
const uint64 ts = data_response.GetMotionTimestamp();
if (ts <= m_last_motion_timestamp[index])
{
const uint64 dif = m_last_motion_timestamp[index] - ts;
if (dif >= 10000000) // timestamp more than 10 seconds in the past, a controller reset probably happened
m_last_motion_timestamp[index] = 0;
return;
}
const uint64 elapsedTime = ts - m_last_motion_timestamp[index];
m_last_motion_timestamp[index] = ts;
const double elapsedTimeD = (double)elapsedTime / 1000000.0;
const auto& acc = data_response.GetAcceleration();
const auto& gyro = data_response.GetGyro();
m_motion_handler[index].processMotionSample((float)elapsedTimeD,
gyro.x * 0.0174533f,
gyro.y * 0.0174533f,
gyro.z * 0.0174533f,
acc.x,
-acc.y,
-acc.z);
m_state[index].motion_sample = m_motion_handler[index].getMotionSample();
}
DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=(const PortInfo& port_info)
{
info = port_info.GetInfo();
last_update = std::chrono::steady_clock::now();
packet_index++; // increase packet index for every packet we assign/recv
return *this;
}
DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=(
const DataResponse& data_response)
{
this->operator=(static_cast<const PortInfo&>(data_response));
data = data_response.GetData();
return *this;
}

View file

@ -0,0 +1,118 @@
#pragma once
#include "input/motion/MotionHandler.h"
#include "input/api/DSU/DSUMessages.h"
#include "input/api/ControllerProvider.h"
#include <boost/asio.hpp>
#ifndef HAS_DSU
#define HAS_DSU 1
#endif
// #define DEBUG_DSU_CLIENT
struct DSUProviderSettings : public ControllerProviderSettings
{
std::string ip;
uint16 port;
DSUProviderSettings() : ip("127.0.0.1"), port(26760) {}
DSUProviderSettings(std::string ip, uint16 port)
: ip(std::move(ip)), port(port)
{
}
bool operator==(const DSUProviderSettings& s) const
{
return port == s.port && ip == s.ip;
}
bool operator==(const ControllerProviderSettings& s) const override
{
const auto* ptr = dynamic_cast<const DSUProviderSettings*>(&s);
return ptr && *this == *ptr;
}
};
class DSUControllerProvider : public ControllerProvider<DSUProviderSettings>
{
friend class DSUController;
using base_type = ControllerProvider<DSUProviderSettings>;
public:
constexpr static int kMaxClients = 8;
struct ControllerState
{
// when was info updated last time
std::chrono::steady_clock::time_point last_update{};
uint64_t packet_index = 0; // our packet index count
PortInfoData info{};
DataResponseData data{};
MotionSample motion_sample{};
ControllerState& operator=(const PortInfo& port_info);
ControllerState& operator=(const DataResponse& data_response);
};
DSUControllerProvider();
DSUControllerProvider(const DSUProviderSettings& settings);
~DSUControllerProvider();
inline static InputAPI::Type kAPIType = InputAPI::DSUClient;
InputAPI::Type api() const override { return kAPIType; }
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
bool connect();
bool is_connected(uint8_t index) const;
ControllerState get_state(uint8_t index) const;
ControllerState get_prev_state(uint8_t index) const;
MotionSample get_motion_sample(uint8_t index) const;
std::array<bool, kMaxClients> wait_update(const std::array<uint8_t, kMaxClients>& indices, size_t timeout) const;
bool wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const;
uint32_t get_packet_index(uint8_t index) const;
// refresh pad info for all pads
void request_pad_info();
// refresh pad info for pad with given index
void request_pad_info(uint8_t index);
void request_version();
void request_pad_data();
void request_pad_data(uint8_t index);
private:
uint16 m_server_version = 0;
std::atomic_bool m_running = false;
std::thread m_reader_thread, m_writer_thread;
void reader_thread();
void writer_thread();
void integrate_motion(uint8_t index, const DataResponse& data_response);
std::mutex m_writer_mutex;
std::condition_variable m_writer_cond;
uint32 m_uid;
boost::asio::io_service m_io_service;
boost::asio::ip::udp::endpoint m_receiver_endpoint;
boost::asio::ip::udp::socket m_socket;
std::array<ControllerState, kMaxClients> m_state{};
std::array<ControllerState, kMaxClients> m_prev_state{};
mutable std::array<std::mutex, kMaxClients> m_mutex;
mutable std::array<std::condition_variable, kMaxClients> m_wait_cond;
std::queue<std::unique_ptr<ClientMessage>> m_writer_jobs;
std::array<WiiUMotionHandler, kMaxClients> m_motion_handler;
std::array<uint64, kMaxClients> m_last_motion_timestamp{};
};

View file

@ -0,0 +1,97 @@
#include "input/api/DSU/DSUMessages.h"
#include <boost/crc.hpp>
constexpr uint32_t kMagicClient = 'CUSD';
constexpr uint32_t kMagicServer = 'SUSD';
constexpr uint16_t kProtocolVersion = 1001;
MessageHeader::MessageHeader(uint32_t magic, uint32_t uid)
: m_magic(magic), m_protocol_version(kProtocolVersion), m_uid(uid) { }
void MessageHeader::Finalize(size_t size)
{
m_packet_size = (uint16_t)(size - sizeof(MessageHeader));
m_crc32 = CRC32(size);
}
uint32_t MessageHeader::CRC32(size_t size) const
{
const auto tmp = m_crc32;
m_crc32 = 0;
boost::crc_32_type crc;
crc.process_bytes(this, size);
const auto result = crc.checksum();
m_crc32 = tmp;
return result;
}
bool MessageHeader::IsClientMessage() const { return m_magic == kMagicClient; }
bool MessageHeader::IsServerMessage() const { return m_magic == kMagicServer; }
Message::Message(uint32_t magic, uint32_t uid, MessageType type)
: MessageHeader(magic, uid), m_message_type(type)
{
}
ClientMessage::ClientMessage(uint32_t uid, MessageType message_type)
: Message(kMagicClient, uid, message_type) { }
VersionRequest::VersionRequest(uint32_t uid)
: ClientMessage(uid, MessageType::Version)
{
Finalize(sizeof(VersionRequest));
}
ListPorts::ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array<uint8_t, 4>& request_indices)
: ClientMessage(uid, MessageType::Information), m_count(num_pads_requests), m_indices(request_indices)
{
Finalize(sizeof(ListPorts));
}
DataRequest::DataRequest(uint32_t uid)
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::AllPads), m_index(0), m_mac_address({})
{
Finalize(sizeof(DataRequest));
}
DataRequest::DataRequest(uint32_t uid, uint8_t index)
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index), m_index(index), m_mac_address({})
{
Finalize(sizeof(DataRequest));
}
DataRequest::DataRequest(uint32_t uid, const MACAddress_t& mac_address)
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::MACAddress), m_index(0), m_mac_address(mac_address)
{
Finalize(sizeof(DataRequest));
}
DataRequest::DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address)
: ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index | RegisterFlag::MACAddress), m_index(index), m_mac_address(mac_address)
{
Finalize(sizeof(DataRequest));
}
bool ServerMessage::ValidateCRC32(size_t size) const
{
return GetCRC32() == CRC32(size);
}
bool VersionResponse::IsValid() const
{
return ValidateCRC32(sizeof(VersionResponse));
}
bool PortInfo::IsValid() const
{
return ValidateCRC32(sizeof(PortInfo));
}
bool DataResponse::IsValid() const
{
return ValidateCRC32(sizeof(DataResponse));
}

View file

@ -0,0 +1,275 @@
#pragma once
// https://v1993.github.io/cemuhook-protocol/
#include "Common/enumFlags.h"
#include "util/math/vector3.h"
#include <array>
#include <cstdint>
enum class DsState : uint8_t
{
Disconnected = 0x00,
Reserved = 0x01,
Connected = 0x02
};
enum class DsConnection : uint8_t
{
None = 0x00,
Usb = 0x01,
Bluetooth = 0x02
};
enum class DsModel : uint8_t
{
None = 0,
DS3 = 1,
DS4 = 2,
Generic = 3
};
enum class DsBattery : uint8_t
{
None = 0x00,
Dying = 0x01,
Low = 0x02,
Medium = 0x03,
High = 0x04,
Full = 0x05,
Charging = 0xEE,
Charged = 0xEF
};
enum class RegisterFlag : uint8_t
{
AllPads = 0x00,
Index = 0x01,
MACAddress = 0x02
};
ENABLE_BITMASK_OPERATORS(RegisterFlag);
enum class MessageType : uint32_t
{
Version = 0x100000,
Information = 0x100001,
Data = 0x100002,
Rumble = 0x100003, // TODO
};
using MACAddress_t = std::array<uint8_t, 6>;
#pragma pack(push,1)
class MessageHeader
{
public:
MessageHeader(uint32_t magic, uint32_t uid);
[[nodiscard]] uint16_t GetSize() const { return sizeof(MessageHeader) + m_packet_size; }
[[nodiscard]] bool IsClientMessage() const;
[[nodiscard]] bool IsServerMessage() const;
[[nodiscard]] uint32_t GetCRC32() const { return m_crc32; }
protected:
void Finalize(size_t size);
[[nodiscard]] uint32_t CRC32(size_t size) const;
private:
uint32_t m_magic;
uint16_t m_protocol_version;
uint16_t m_packet_size = 0;
mutable uint32_t m_crc32 = 0;
uint32_t m_uid;
};
static_assert(sizeof(MessageHeader) == 0x10);
class Message : public MessageHeader
{
public:
Message(uint32_t magic, uint32_t uid, MessageType type);
[[nodiscard]] MessageType GetMessageType() const { return m_message_type; }
private:
MessageType m_message_type;
};
static_assert(sizeof(Message) == 0x14);
// client messages
class ClientMessage : public Message
{
public:
ClientMessage(uint32_t uid, MessageType message_type);
};
static_assert(sizeof(ClientMessage) == sizeof(Message));
class VersionRequest : public ClientMessage
{
public:
VersionRequest(uint32_t uid);
};
static_assert(sizeof(VersionRequest) == sizeof(ClientMessage));
class ListPorts : public ClientMessage
{
public:
ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array<uint8_t, 4>& request_indices);
private:
uint32_t m_count;
std::array<uint8_t, 4> m_indices;
};
class DataRequest : public ClientMessage
{
public:
DataRequest(uint32_t uid);
DataRequest(uint32_t uid, uint8_t index);
DataRequest(uint32_t uid, const MACAddress_t& mac_address);
DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address);
private:
RegisterFlag m_reg_flags;
uint8_t m_index;
MACAddress_t m_mac_address;
};
// server messages
class ServerMessage : public Message
{
public:
ServerMessage() = delete;
protected:
[[nodiscard]] bool ValidateCRC32(size_t size) const;
};
class VersionResponse : public ServerMessage
{
public:
[[nodiscard]] bool IsValid() const;
[[nodiscard]] uint16_t GetVersion() const { return m_version; }
private:
uint16_t m_version;
uint8_t padding[2];
};
static_assert(sizeof(VersionResponse) == 0x18);
struct PortInfoData
{
uint8_t index;
DsState state;
DsModel model;
DsConnection connection;
MACAddress_t mac_address;
DsBattery battery;
uint8_t is_active;
};
class PortInfo : public ServerMessage
{
public:
[[nodiscard]] bool IsValid() const;
[[nodiscard]] const PortInfoData& GetInfo() const { return m_info; }
[[nodiscard]] uint8_t GetIndex() const { return m_info.index; }
[[nodiscard]] DsState GetState() const { return m_info.state; }
[[nodiscard]] DsModel GetModel() const { return m_info.model; }
[[nodiscard]] DsConnection GetConnection() const { return m_info.connection; }
[[nodiscard]] MACAddress_t GetMacAddress() const { return m_info.mac_address; }
[[nodiscard]] DsBattery GetBattery() const { return m_info.battery; }
[[nodiscard]] bool IsActive() const { return m_info.is_active != 0; }
protected:
PortInfoData m_info;
};
static_assert(sizeof(PortInfo) == 0x20);
struct TouchPoint
{
uint8_t active;
uint8_t index;
int16_t x, y;
};
static_assert(sizeof(TouchPoint) == 0x6);
struct DataResponseData
{
uint32_t m_packet_index;
uint8_t state1;
uint8_t state2;
uint8_t ps;
uint8_t touch;
// y values are inverted by convention
uint8_t lx, ly;
uint8_t rx, ry;
uint8_t dpad_left;
uint8_t dpad_down;
uint8_t dpad_right;
uint8_t dpad_up;
uint8_t square;
uint8_t cross;
uint8_t circle;
uint8_t triangle;
uint8_t r1;
uint8_t l1;
uint8_t r2;
uint8_t l2;
TouchPoint tpad1, tpad2;
uint64_t motion_timestamp;
Vector3f accel;
Vector3f gyro;
};
class DataResponse : public PortInfo
{
public:
[[nodiscard]] bool IsValid() const;
[[nodiscard]] const DataResponseData& GetData() const { return m_data; }
uint32_t GetPacketIndex() const { return m_data.m_packet_index; }
uint8_t GetState1() const { return m_data.state1; }
uint8_t GetState2() const { return m_data.state2; }
uint8_t GetPs() const { return m_data.ps; }
uint8_t GetTouch() const { return m_data.touch; }
uint8_t GetLx() const { return m_data.lx; }
uint8_t GetLy() const { return m_data.ly; }
uint8_t GetRx() const { return m_data.rx; }
uint8_t GetRy() const { return m_data.ry; }
uint8_t GetDpadLeft() const { return m_data.dpad_left; }
uint8_t GetDpadDown() const { return m_data.dpad_down; }
uint8_t GetDpadRight() const { return m_data.dpad_right; }
uint8_t GetDpadUp() const { return m_data.dpad_up; }
uint8_t GetSquare() const { return m_data.square; }
uint8_t GetCross() const { return m_data.cross; }
uint8_t GetCircle() const { return m_data.circle; }
uint8_t GetTriangle() const { return m_data.triangle; }
uint8_t GetR1() const { return m_data.r1; }
uint8_t GetL1() const { return m_data.l1; }
uint8_t GetR2() const { return m_data.r2; }
uint8_t GetL2() const { return m_data.l2; }
const TouchPoint& GetTpad1() const { return m_data.tpad1; }
const TouchPoint& GetTpad2() const { return m_data.tpad2; }
uint64_t GetMotionTimestamp() const { return m_data.motion_timestamp; }
const Vector3f& GetAcceleration() const { return m_data.accel; }
const Vector3f& GetGyro() const { return m_data.gyro; }
private:
DataResponseData m_data;
};
//static_assert(sizeof(DataResponse) == 0x20);
#pragma pack(pop)

View file

@ -0,0 +1,337 @@
#include "input/api/DirectInput/DirectInputController.h"
#include "gui/guiWrapper.h"
DirectInputController::DirectInputController(const GUID& guid)
: base_type(StringFromGUID(guid), fmt::format("[{}]", StringFromGUID(guid))),
m_guid{ guid }
{
}
DirectInputController::DirectInputController(const GUID& guid, std::string_view display_name)
: base_type(StringFromGUID(guid), display_name), m_guid(guid)
{
}
DirectInputController::~DirectInputController()
{
if (m_effect)
m_effect->Release();
if (m_device)
{
m_device->Unacquire();
// TODO: test if really needed
// workaround for gamecube controllers crash on release?
bool should_release_device = true;
if (m_product_guid == GUID{}) {
DIDEVICEINSTANCE info{};
info.dwSize = sizeof(DIDEVICEINSTANCE);
if (SUCCEEDED(m_device->GetDeviceInfo(&info)))
{
m_product_guid = info.guidProduct;
}
}
// info.guidProduct = {18440079-0000-0000-0000-504944564944}
constexpr GUID kGameCubeController = { 0x18440079, 0, 0, {0,0,0x50,0x49,0x44,0x56,0x49,0x44} };
if (kGameCubeController == m_product_guid)
should_release_device = false;
if (should_release_device)
m_device->Release();
}
}
void DirectInputController::save(pugi::xml_node& node)
{
base_type::save(node);
node.append_child("product_guid").append_child(pugi::node_pcdata).set_value(
fmt::format("{}", StringFromGUID(m_product_guid)).c_str());
}
void DirectInputController::load(const pugi::xml_node& node)
{
base_type::load(node);
if (const auto value = node.child("product_guid")) {
if (GUIDFromString(value.child_value(), m_product_guid) && m_product_guid != GUID{} && !is_connected())
{
// test if another controller with the same product guid is connectable and replace
for(const auto& c : m_provider->get_controllers())
{
if(const auto ptr = std::dynamic_pointer_cast<DirectInputController>(c))
{
if (ptr->is_connected() && ptr->get_product_guid() == m_product_guid)
{
const auto tmp_guid = m_guid;
m_guid = ptr->get_guid();
if (connect())
break;
// couldn't connect
m_guid = tmp_guid;
}
}
}
}
}
}
bool DirectInputController::connect()
{
if (is_connected())
return true;
m_effect = nullptr;
std::scoped_lock lock(m_mutex);
HRESULT hr = m_provider->get_dinput()->CreateDevice(m_guid, &m_device, nullptr);
if (FAILED(hr) || m_device == nullptr)
return false;
DIDEVICEINSTANCE idi{};
idi.dwSize = sizeof(DIDEVICEINSTANCE);
if (SUCCEEDED(m_device->GetDeviceInfo(&idi)))
{
// overwrite guid name with "real" display name
m_display_name = boost::nowide::narrow(idi.tszProductName);
}
// set data format
if (FAILED(m_device->SetDataFormat(m_provider->get_data_format())))
{
SAFE_RELEASE(m_device);
return false;
}
HWND hwndMainWindow = gui_getWindowInfo().window_main.hwnd;
// set access
if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_EXCLUSIVE)))
{
if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE)))
{
SAFE_RELEASE(m_device);
return false;
}
// rumble can only be used with exclusive access
}
else
{
GUID guid_effect = GUID_NULL;
// check if constant force is supported
HRESULT result = m_device->EnumEffects([](LPCDIEFFECTINFOW eff, LPVOID guid) -> BOOL
{
*(GUID*)guid = eff->guid;
return DIENUM_STOP;
}, &guid_effect, DIEFT_CONSTANTFORCE);
if (SUCCEEDED(result) && guid_effect != GUID_NULL)
{
DWORD dwAxes[2] = { DIJOFS_X, DIJOFS_Y };
LONG lDirection[2] = { 1, 0 };
DICONSTANTFORCE constant_force = { DI_FFNOMINALMAX }; // DI_FFNOMINALMAX -> should be max normally?!
DIEFFECT effect{};
effect.dwSize = sizeof(DIEFFECT);
effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
effect.dwDuration = INFINITE; // DI_SECONDS;
effect.dwGain = DI_FFNOMINALMAX; // No scaling
effect.dwTriggerButton = DIEB_NOTRIGGER; // Not a button response DIEB_NOTRIGGER DIJOFS_BUTTON0
effect.cAxes = 2;
effect.rgdwAxes = dwAxes;
effect.rglDirection = lDirection;
effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
effect.lpvTypeSpecificParams = &constant_force;
m_device->CreateEffect(guid_effect, &effect, &m_effect, nullptr);
}
}
DIDEVICEINSTANCE info{};
info.dwSize = sizeof(DIDEVICEINSTANCE);
if (SUCCEEDED(m_device->GetDeviceInfo(&info)))
{
m_product_guid = info.guidProduct;
}
std::fill(m_min_axis.begin(), m_min_axis.end(), 0);
std::fill(m_max_axis.begin(), m_max_axis.end(), std::numeric_limits<uint16>::max());
m_device->EnumObjects(
[](LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) -> BOOL
{
auto* thisptr = (DirectInputController*)pvRef;
const auto instance = DIDFT_GETINSTANCE(lpddoi->dwType);
// some tools may use state.rglSlider properties, so they have 8 instead of 6 axis
if(instance >= thisptr->m_min_axis.size())
{
return DIENUM_CONTINUE;
}
DIPROPRANGE range{};
range.diph.dwSize = sizeof(range);
range.diph.dwHeaderSize = sizeof(range.diph);
range.diph.dwHow = DIPH_BYID;
range.diph.dwObj = lpddoi->dwType;
if (thisptr->m_device->GetProperty(DIPROP_RANGE, &range.diph) == DI_OK)
{
thisptr->m_min_axis[instance] = range.lMin;
thisptr->m_max_axis[instance] = range.lMax;
}
return DIENUM_CONTINUE;
}, this, DIDFT_AXIS);
m_device->Acquire();
return true;
}
bool DirectInputController::is_connected()
{
std::shared_lock lock(m_mutex);
return m_device != nullptr;
}
bool DirectInputController::has_rumble()
{
return m_effect != nullptr;
}
void DirectInputController::start_rumble()
{
if (!has_rumble())
return;
}
void DirectInputController::stop_rumble()
{
if (!has_rumble())
return;
}
std::string DirectInputController::get_button_name(uint64 button) const
{
switch(button)
{
case kAxisXP: return "X+";
case kAxisYP: return "Y+";
case kAxisXN: return "X-";
case kAxisYN: return "Y-";
case kRotationXP: return "RX+";
case kRotationYP: return "RY+";
case kRotationXN: return "RX-";
case kRotationYN: return "RY-";
case kTriggerXP: return "Z+";
case kTriggerYP: return "RZ+";
case kTriggerXN: return "Z-";
case kTriggerYN: return "RZ-";
}
return base_type::get_button_name(button);
}
ControllerState DirectInputController::raw_state()
{
ControllerState result{};
if (!is_connected())
return result;
HRESULT hr = m_device->Poll();
if (FAILED(hr))
{
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
{
result.last_state = hr;
m_device->Acquire();
}
return result;
}
DIJOYSTATE state{};
hr = m_device->GetDeviceState(sizeof(state), &state);
if (FAILED(hr))
{
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
{
result.last_state = hr;
m_device->Acquire();
}
return result;
}
result.last_state = hr;
// buttons
for (size_t i = 0; i < std::size(state.rgbButtons); ++i)
{
if (HAS_BIT(state.rgbButtons[i], 7))
{
result.buttons.set(i);
}
}
// axis
constexpr float kThreshold = 0.001f;
float v = (float(state.lX - m_min_axis[0]) / float(m_max_axis[0] - m_min_axis[0])) * 2.0f - 1.0f;
if (std::abs(v) >= kThreshold)
result.axis.x = v;
v = (float(state.lY - m_min_axis[1]) / float(m_max_axis[1] - m_min_axis[1])) * 2.0f - 1.0f;
if (std::abs(v) >= kThreshold)
result.axis.y = -v;
// Right Stick
v = (float(state.lRx - m_min_axis[3]) / float(m_max_axis[3] - m_min_axis[3])) * 2.0f - 1.0f;
if (std::abs(v) >= kThreshold)
result.rotation.x = v;
v = (float(state.lRy - m_min_axis[4]) / float(m_max_axis[4] - m_min_axis[4])) * 2.0f - 1.0f;
if (std::abs(v) >= kThreshold)
result.rotation.y = -v;
// Trigger
v = (float(state.lZ - m_min_axis[2]) / float(m_max_axis[2] - m_min_axis[2])) * 2.0f - 1.0f;
if (std::abs(v) >= kThreshold)
result.trigger.x = v;
v = (float(state.lRz - m_min_axis[5]) / float(m_max_axis[5] - m_min_axis[5])) * 2.0f - 1.0f;
if (std::abs(v) >= kThreshold)
result.trigger.y = -v;
// dpad
const auto pov = state.rgdwPOV[0];
if (pov != static_cast<DWORD>(-1))
{
switch (pov)
{
case 0: result.buttons.set(kButtonUp);
break;
case 4500: result.buttons.set(kButtonUp); // up + right
case 9000: result.buttons.set(kButtonRight);
break;
case 13500: result.buttons.set(kButtonRight); // right + down
case 18000: result.buttons.set(kButtonDown);
break;
case 22500: result.buttons.set(kButtonDown); // down + left
case 27000: result.buttons.set(kButtonLeft);
break;
case 31500: result.buttons.set(kButtonLeft);; // left + up
result.buttons.set(kButtonUp); // left + up
break;
}
}
return result;
}

View file

@ -0,0 +1,49 @@
#pragma once
#include "input/api/DirectInput/DirectInputControllerProvider.h"
#include "input/api/Controller.h"
class DirectInputController : public Controller<DirectInputControllerProvider>
{
public:
DirectInputController(const GUID& guid);
DirectInputController(const GUID& guid, std::string_view display_name);
~DirectInputController() override;
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::DirectInput) == "DirectInput");
return to_string(InputAPI::DirectInput);
}
InputAPI::Type api() const override { return InputAPI::DirectInput; }
void save(pugi::xml_node& node) override;
void load(const pugi::xml_node& node) override;
bool connect() override;
bool is_connected() override;
bool has_rumble() override;
void start_rumble() override;
void stop_rumble() override;
std::string get_button_name(uint64 button) const override;
const GUID& get_guid() const { return m_guid; }
const GUID& get_product_guid() const { return m_product_guid; }
protected:
ControllerState raw_state() override;
private:
GUID m_guid;
GUID m_product_guid{};
std::shared_mutex m_mutex;
LPDIRECTINPUTDEVICE8 m_device = nullptr;
LPDIRECTINPUTEFFECT m_effect = nullptr;
std::array<LONG, 6> m_min_axis{};
std::array<LONG, 6> m_max_axis{};
};

View file

@ -0,0 +1,65 @@
#include "input/api/DirectInput/DirectInputControllerProvider.h"
#include "input/api/DirectInput/DirectInputController.h"
DirectInputControllerProvider::DirectInputControllerProvider()
{
/*m_module = LoadLibraryA("dinput8.dll");
if (!m_module)
throw std::runtime_error("can't load any xinput dll");
m_DirectInput8Create = (decltype(&DirectInput8Create))GetProcAddress(m_module, "DirectInput8Create");
m_GetdfDIJoystick = (decltype(&GetdfDIJoystick))GetProcAddress(m_module, "GetdfDIJoystick");
if (!m_DirectInput8Create)
{
FreeLibrary(m_module);
throw std::runtime_error("can't find the DirectInput8Create export");
}*/
const auto r = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_dinput8, nullptr);
if (FAILED(r) || !m_dinput8)
{
const auto error = GetLastError();
//FreeLibrary(m_module);
throw std::runtime_error(fmt::format("can't create direct input object (error: {:#x})", error));
}
}
DirectInputControllerProvider::~DirectInputControllerProvider()
{
if (m_dinput8)
m_dinput8->Release();
/*if (m_module)
FreeLibrary(m_module);
*/
}
std::vector<std::shared_ptr<ControllerBase>> DirectInputControllerProvider::get_controllers()
{
std::vector<std::shared_ptr<ControllerBase>> result;
m_dinput8->EnumDevices(DI8DEVCLASS_GAMECTRL,
[](LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) -> BOOL
{
auto* controllers = (decltype(&result))pvRef;
std::string display_name = boost::nowide::narrow(lpddi->tszProductName);
controllers->emplace_back(std::make_shared<DirectInputController>(lpddi->guidInstance, display_name));
return DIENUM_CONTINUE;
}, &result, DIEDFL_ALLDEVICES);
return result;
}
LPCDIDATAFORMAT DirectInputControllerProvider::get_data_format() const
{
/*if (m_GetdfDIJoystick)
return m_GetdfDIJoystick();*/
return GetdfDIJoystick();
}

View file

@ -0,0 +1,37 @@
#pragma once
#if BOOST_OS_WINDOWS
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include "input/api/ControllerProvider.h"
#ifndef HAS_DIRECTINPUT
#define HAS_DIRECTINPUT 1
#endif
class DirectInputControllerProvider : public ControllerProviderBase
{
public:
DirectInputControllerProvider();
~DirectInputControllerProvider() override;
inline static InputAPI::Type kAPIType = InputAPI::DirectInput;
InputAPI::Type api() const override { return kAPIType; }
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
IDirectInput8* get_dinput() const { return m_dinput8; }
LPCDIDATAFORMAT get_data_format() const;
private:
HMODULE m_module = nullptr;
decltype(&DirectInput8Create) m_DirectInput8Create;
decltype(&GetdfDIJoystick) m_GetdfDIJoystick = nullptr;
IDirectInput8* m_dinput8 = nullptr;
};
#endif

View file

@ -0,0 +1,103 @@
#include "input/api/GameCube/GameCubeController.h"
#ifdef HAS_GAMECUBE
GameCubeController::GameCubeController(uint32 adapter, uint32 index)
: base_type(fmt::format("{}_{}", adapter, index), fmt::format("Controller {}", index + 1)), m_adapter(adapter),
m_index(index)
{
// update names if multiple adapters are connected
if (adapter > 0)
m_display_name = fmt::format("Controller {} ({})", index + 1, adapter);
m_settings.axis.range = 1.20f;
m_settings.rotation.range = 1.25f;
m_settings.trigger.range = 1.07f;
}
bool GameCubeController::is_connected()
{
return m_provider->is_connected(m_adapter);
}
bool GameCubeController::has_rumble()
{
return m_provider->has_rumble_connected(m_adapter);
}
void GameCubeController::start_rumble()
{
if (m_settings.rumble <= 0)
return;
m_provider->set_rumble_state(m_adapter, m_index, true);
}
void GameCubeController::stop_rumble()
{
m_provider->set_rumble_state(m_adapter, m_index, false);
}
std::string GameCubeController::get_button_name(uint64 button) const
{
switch (button)
{
case kButton0: return "A";
case kButton1: return "B";
case kButton2: return "X";
case kButton3: return "Y";
case kButton4: return "Left";
case kButton5: return "Right";
case kButton6: return "Down";
case kButton7: return "Up";
case kButton8: return "Start";
case kButton9: return "Z";
case kButton10: return "Trigger R";
case kButton11: return "Trigger L";
}
return base_type::get_button_name(button);
}
ControllerState GameCubeController::raw_state()
{
ControllerState result{};
if (!is_connected())
return result;
const auto state = m_provider->get_state(m_adapter, m_index);
if (state.valid)
{
for (auto i = 0; i <= kButton11; ++i)
{
if (HAS_BIT(state.button, i))
{
result.buttons.set(i);
}
}
// printf("(%d, %d) - (%d, %d) - (%d, %d)\n", state.lstick_x, state.lstick_y, state.rstick_x, state.rstick_y, state.lstick, state.rstick);
result.axis.x = (float)state.lstick_x / std::numeric_limits<uint8>::max();
result.axis.x = (result.axis.x * 2.0f) - 1.0f;
result.axis.y = (float)state.lstick_y / std::numeric_limits<uint8>::max();
result.axis.y = (result.axis.y * 2.0f) - 1.0f;
result.rotation.x = (float)state.rstick_x / std::numeric_limits<uint8>::max();
result.rotation.x = (result.rotation.x * 2.0f) - 1.0f;
result.rotation.y = (float)state.rstick_y / std::numeric_limits<uint8>::max();
result.rotation.y = (result.rotation.y * 2.0f) - 1.0f;
result.trigger.x = (float)state.lstick / std::numeric_limits<uint8>::max();
result.trigger.y = (float)state.rstick / std::numeric_limits<uint8>::max();
}
return result;
}
#endif

View file

@ -0,0 +1,34 @@
#pragma once
#include "input/api/Controller.h"
#include "input/api/GameCube/GameCubeControllerProvider.h"
#ifdef HAS_GAMECUBE
class GameCubeController : public Controller<GameCubeControllerProvider>
{
public:
GameCubeController(uint32 adapter, uint32 index);
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::GameCube) == "GameCube");
return to_string(InputAPI::GameCube);
}
InputAPI::Type api() const override { return InputAPI::GameCube; }
bool is_connected() override;
bool has_rumble() override;
void start_rumble() override;
void stop_rumble() override;
std::string get_button_name(uint64 button) const override;
protected:
ControllerState raw_state() override;
uint32 m_adapter;
uint32 m_index;
};
#endif

View file

@ -0,0 +1,388 @@
#include "input/api/GameCube/GameCubeControllerProvider.h"
#include "input/api/GameCube/GameCubeController.h"
#include "util/libusbWrapper/libusbWrapper.h"
#if HAS_GAMECUBE
constexpr uint16_t kVendorId = 0x57e, kProductId = 0x337;
GameCubeControllerProvider::GameCubeControllerProvider()
{
m_libusb = libusbWrapper::getInstance();
m_libusb->init();
if(!m_libusb->isAvailable())
throw std::runtime_error("libusbWrapper not available");
m_libusb->p_libusb_init(&m_context);
for(auto i = 0; i < kMaxAdapters; ++i)
{
auto device = fetch_usb_device(i);
if(std::get<0>(device))
{
m_adapters[i].m_device_handle = std::get<0>(device);
m_adapters[i].m_endpoint_reader = std::get<1>(device);
m_adapters[i].m_endpoint_writer = std::get<2>(device);
}
}
if (m_libusb->p_libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
{
m_libusb->p_libusb_hotplug_register_callback(m_context, static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
LIBUSB_HOTPLUG_NO_FLAGS, kVendorId, kProductId, LIBUSB_HOTPLUG_MATCH_ANY, &GameCubeControllerProvider::hotplug_event, this, &m_callback_handle);
}
m_running = true;
m_reader_thread = std::thread(&GameCubeControllerProvider::reader_thread, this);
m_writer_thread = std::thread(&GameCubeControllerProvider::writer_thread, this);
}
GameCubeControllerProvider::~GameCubeControllerProvider()
{
if (m_running)
{
m_running = false;
m_writer_thread.join();
m_reader_thread.join();
}
if (m_callback_handle)
{
m_libusb->p_libusb_hotplug_deregister_callback(m_context, m_callback_handle);
m_callback_handle = 0;
}
for (auto& a : m_adapters)
{
m_libusb->p_libusb_close(a.m_device_handle);
}
if (m_context)
{
m_libusb->p_libusb_exit(m_context);
m_context = nullptr;
}
}
std::vector<ControllerPtr> GameCubeControllerProvider::get_controllers()
{
std::vector<ControllerPtr> result;
const auto adapter_count = get_adapter_count();
for (uint32 adapter_index = 0; adapter_index < adapter_count && adapter_index < kMaxAdapters; ++adapter_index)
{
// adapter doesn't tell us which one is actually connected, so we return all of them
for (int index = 0; index < 4; ++index)
result.emplace_back(std::make_shared<GameCubeController>(adapter_index, index));
}
return result;
}
uint32 GameCubeControllerProvider::get_adapter_count() const
{
uint32 adapter_count = 0;
libusb_device** devices;
const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices);
if (count < 0)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_list: {}", static_cast<int>(count), m_libusb->p_libusb_error_name(static_cast<int>(count))).c_str());
return adapter_count;
}
for (ssize_t i = 0; i < count; ++i)
{
if (!devices[i])
continue;
libusb_device_descriptor desc;
int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc);
if (ret != 0)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
continue;
}
if (desc.idVendor != kVendorId || desc.idProduct != kProductId)
continue;
++adapter_count;
}
m_libusb->p_libusb_free_device_list(devices, 1);
return adapter_count;
}
bool GameCubeControllerProvider::has_rumble_connected(uint32 adapter_index) const
{
if (adapter_index >= m_adapters.size())
return false;
std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex);
return m_adapters[adapter_index].m_rumble_connected;
}
bool GameCubeControllerProvider::is_connected(uint32 adapter_index) const
{
if (adapter_index >= m_adapters.size())
return false;
return m_adapters[adapter_index].m_device_handle != nullptr;
}
void GameCubeControllerProvider::set_rumble_state(uint32 adapter_index, uint32 index, bool state)
{
if (adapter_index >= m_adapters.size())
return;
if (index >= kMaxIndex)
return;
std::scoped_lock lock(m_writer_mutex);
m_adapters[adapter_index].rumble_states[index] = state;
m_rumble_changed = true;
m_writer_cond.notify_all();
}
GameCubeControllerProvider::GCState GameCubeControllerProvider::get_state(uint32 adapter_index, uint32 index)
{
if (adapter_index >= m_adapters.size())
return {};
if (index >= kMaxIndex)
return {};
std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex);
return m_adapters[adapter_index].m_states[index];
}
#ifdef interface
#undef interface
#endif
std::tuple<libusb_device_handle*, uint8, uint8> GameCubeControllerProvider::fetch_usb_device(uint32 adapter) const
{
std::tuple<libusb_device_handle*, uint8, uint8> result{nullptr, 0xFF, 0xFF};
libusb_device** devices;
const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices);
if (count < 0)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_list: {}", static_cast<int>(count), m_libusb->p_libusb_error_name(static_cast<int>(count))).c_str());
return result;
}
int adapter_index = 0;
for (ssize_t i = 0; i < count; ++i)
{
if (!devices[i])
continue;
libusb_device_descriptor desc;
int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc);
if (ret != 0)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
continue;
}
if (desc.idVendor != kVendorId || desc.idProduct != kProductId)
continue;
if (adapter != adapter_index++)
continue;
libusb_device_handle* device_handle;
ret = m_libusb->p_libusb_open(devices[i], &device_handle);
if (ret != 0)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_open: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
continue;
}
if (m_libusb->p_libusb_kernel_driver_active(device_handle, 0) == 1)
{
ret = m_libusb->p_libusb_detach_kernel_driver(device_handle, 0);
if (ret != 0)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_detach_kernel_driver: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
m_libusb->p_libusb_close(device_handle);
continue;
}
}
ret = m_libusb->p_libusb_claim_interface(device_handle, 0);
if (ret != 0)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_claim_interface: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str());
m_libusb->p_libusb_close(device_handle);
continue;
}
libusb_config_descriptor* config = nullptr;
m_libusb->p_libusb_get_config_descriptor(devices[i], 0, &config);
for (auto ic = 0; ic < config->bNumInterfaces; ic++)
{
const auto& interface = config->interface[ic];
for (auto j = 0; j < interface.num_altsetting; j++)
{
const auto& interface_desc = interface.altsetting[j];
for (auto k = 0; k < interface_desc.bNumEndpoints; k++)
{
const auto& endpoint = interface_desc.endpoint[k];
if (endpoint.bEndpointAddress & LIBUSB_ENDPOINT_IN)
std::get<1>(result) = endpoint.bEndpointAddress;
else
std::get<2>(result) = endpoint.bEndpointAddress;
}
}
}
m_libusb->p_libusb_free_config_descriptor(config);
// start polling
int size = 0;
uint8_t payload = 0x13;
m_libusb->p_libusb_interrupt_transfer(device_handle, std::get<2>(result), &payload, sizeof(payload), &size, 25);
std::get<0>(result) = device_handle;
break;
}
m_libusb->p_libusb_free_device_list(devices, 1);
return result;
}
void GameCubeControllerProvider::reader_thread()
{
SetThreadName("GCControllerAdapter::reader_thread");
while (m_running.load(std::memory_order_relaxed))
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::this_thread::yield();
for(auto& adapter : m_adapters)
{
if (!adapter.m_device_handle)
continue;
std::array<uint8_t, 37> data{};
int read;
const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_reader, data.data(), static_cast<int>(data.size()), &read, 25);
if (result == 0)
{
/*
byte 1
0x10 NORMAL STATE
0x20 WAVEBIRD STATE
0x04 RUMBLE POWER
*/
for (int i = 0; i < 4; ++i)
{
GCState state;
state.valid = true;
state.button = *(uint16*)&data[1 + (i * 9) + 1];
state.lstick_x = data[1 + (i * 9) + 3];
state.lstick_y = data[1 + (i * 9) + 4];
state.rstick_x = data[1 + (i * 9) + 5];
state.rstick_y = data[1 + (i * 9) + 6];
state.lstick = data[1 + (i * 9) + 7];
state.rstick = data[1 + (i * 9) + 8];
std::scoped_lock lock(adapter.m_state_mutex);
adapter.m_rumble_connected = HAS_FLAG(data[1], 4);
adapter.m_states[i] = state;
}
}
else if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_IO)
{
forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str());
if (const auto handle = adapter.m_device_handle.exchange(nullptr))
m_libusb->p_libusb_close(handle);
}
else { forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str()); }
}
}
}
void GameCubeControllerProvider::writer_thread()
{
SetThreadName("GCControllerAdapter::writer_thread");
std::array<std::array<bool, 4>, kMaxAdapters> rumble_states{};
while (m_running.load(std::memory_order_relaxed))
{
std::unique_lock lock(m_writer_mutex);
if (!m_rumble_changed && m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout)
{
if (!m_running)
return;
continue;
}
bool cmd_sent = false;
for (size_t i = 0; i < kMaxAdapters; ++i)
{
auto& adapter = m_adapters[i];
if (!adapter.m_device_handle)
continue;
if (adapter.rumble_states == rumble_states[i])
continue;
rumble_states[i] = adapter.rumble_states;
m_rumble_changed = false;
lock.unlock();
std::array<uint8, 5> rumble{ 0x11, rumble_states[i][0],rumble_states[i][1],rumble_states[i][2], rumble_states[i][3] };
int written;
const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_writer, rumble.data(), static_cast<int>(rumble.size()), &written, 25);
if (result != 0) { forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str()); }
cmd_sent = true;
lock.lock();
}
if(cmd_sent)
{
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
}
int GameCubeControllerProvider::hotplug_event(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data)
{
auto* thisptr = static_cast<GameCubeControllerProvider*>(user_data);
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event)
{
for (auto i = 0; i < kMaxAdapters; ++i)
{
if (thisptr->m_adapters[i].m_device_handle)
continue;
auto device = thisptr->fetch_usb_device(i);
if (std::get<0>(device))
{
thisptr->m_adapters[i].m_endpoint_reader = std::get<1>(device);
thisptr->m_adapters[i].m_endpoint_writer = std::get<2>(device);
thisptr->m_adapters[i].m_device_handle = std::get<0>(device);
}
}
}
/*else
{
const auto device_handle = thisptr->m_device_handle.exchange(nullptr);
if (device_handle)
thisptr->m_libusb->p_libusb_close(device_handle);
}*/
return 0;
}
#endif

View file

@ -0,0 +1,79 @@
#pragma once
#include "util/libusbWrapper/libusbWrapper.h"
#include "input/api/ControllerProvider.h"
#ifdef HAS_GAMECUBE
class GameCubeControllerProvider : public ControllerProviderBase
{
friend class DSUController;
public:
constexpr static size_t kMaxAdapters = 4;
constexpr static size_t kMaxIndex = 4;
GameCubeControllerProvider();
~GameCubeControllerProvider();
inline static InputAPI::Type kAPIType = InputAPI::GameCube;
InputAPI::Type api() const override { return kAPIType; }
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
uint32 get_adapter_count() const;
bool has_rumble_connected(uint32 adapter_index) const;
bool is_connected(uint32 adapter_index) const;
void set_rumble_state(uint32 adapter_index, uint32 index, bool state);
struct GCState
{
bool valid = false;
uint16 button = 0;
uint8 lstick_x = 0;
uint8 lstick_y = 0;
uint8 rstick_x = 0;
uint8 rstick_y = 0;
uint8 lstick = 0;
uint8 rstick = 0;
};
GCState get_state(uint32 adapter_index, uint32 index);
private:
std::shared_ptr<libusbWrapper> m_libusb;
libusb_context* m_context = nullptr;
std::atomic_bool m_running = false;
std::thread m_reader_thread, m_writer_thread;
void reader_thread();
void writer_thread();
// handle, endpoint_reader, endpoint_writer
std::tuple<libusb_device_handle*, uint8, uint8> fetch_usb_device(uint32 adapter) const;
std::mutex m_writer_mutex;
std::condition_variable m_writer_cond;
bool m_rumble_changed = false;
struct Adapter
{
std::atomic<libusb_device_handle*> m_device_handle{};
uint8 m_endpoint_reader = 0xFF, m_endpoint_writer = 0xFF;
mutable std::mutex m_state_mutex;
std::array<GCState, kMaxIndex> m_states{};
bool m_rumble_connected = false;
std::array<bool, kMaxIndex> rumble_states{};
};
std::array<Adapter, kMaxAdapters> m_adapters;
libusb_hotplug_callback_handle m_callback_handle = 0;
static int hotplug_event(struct libusb_context* ctx, struct libusb_device* dev, libusb_hotplug_event event, void* user_data);
};
#endif

79
src/input/api/InputAPI.h Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#include "util/helpers/helpers.h"
namespace InputAPI
{
enum Type
{
Keyboard,
SDLController,
XInput,
DirectInput,
DSUClient,
GameCube,
Wiimote,
WGIGamepad,
WGIRawController,
MAX
};
constexpr std::string_view to_string(Type type)
{
switch (type)
{
case Keyboard:
return "Keyboard";
case DirectInput:
return "DirectInput";
case XInput:
return "XInput";
case Wiimote:
return "Wiimote";
case GameCube:
return "GameCube";
case DSUClient:
return "DSUController";
case WGIGamepad:
return "WGIGamepad";
case WGIRawController:
return "WGIRawController";
case SDLController:
return "SDLController";
default:
break;
}
throw std::runtime_error(fmt::format("unknown input api: {}", to_underlying(type)));
}
constexpr Type from_string(std::string_view str)
{
if (str == to_string(Keyboard))
return Keyboard;
else if (str == to_string(DirectInput))
return DirectInput;
else if (str == to_string(XInput))
return XInput;
else if (str == to_string(Wiimote))
return Wiimote;
else if (str == to_string(GameCube))
return GameCube;
else if (str == to_string(DSUClient))
return DSUClient;
else if (str == to_string(SDLController))
return SDLController;
else if (str == "DSU") // legacy
return DSUClient;
//else if (str == "WGIGamepad")
// return WGIGamepad;
//
//else if (str == "WGIRawController")
// return WGIRawController;
throw std::runtime_error(fmt::format("unknown input api: {}", str));
}
}

View file

@ -0,0 +1,66 @@
#include "input/api/Keyboard/KeyboardController.h"
#include "gui/guiWrapper.h"
KeyboardController::KeyboardController()
: base_type("keyboard", "Keyboard")
{
}
std::string KeyboardController::get_button_name(uint64 button) const
{
#if BOOST_OS_WINDOWS
LONG scan_code = MapVirtualKeyA((UINT)button, MAPVK_VK_TO_VSC_EX);
if(HIBYTE(scan_code))
scan_code |= 0x100;
// because MapVirtualKey strips the extended bit for some keys
switch (button)
{
case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: // arrow keys
case VK_PRIOR: case VK_NEXT: // page up and page down
case VK_END: case VK_HOME:
case VK_INSERT: case VK_DELETE:
case VK_DIVIDE: // numpad slash
case VK_NUMLOCK:
{
scan_code |= 0x100; // set extended bit
break;
}
}
scan_code <<= 16;
char key_name[128];
if (GetKeyNameTextA(scan_code, key_name, std::size(key_name)) != 0)
return key_name;
#endif
return fmt::format("key_{}", button);
}
extern WindowInfo g_window_info;
ControllerState KeyboardController::raw_state()
{
ControllerState result{};
if (g_window_info.app_active)
{
static_assert(result.buttons.size() == std::size(g_window_info.keydown), "invalid size");
for (uint32 i = wxKeyCode::WXK_BACK; i < result.buttons.size(); ++i)
{
if(const bool v = g_window_info.keydown[i])
{
result.buttons.set(i, v);
}
}
// ignore generic key codes on Windows when there is also a left/right variant
#if BOOST_OS_WINDOWS
result.buttons.set(VK_SHIFT, false);
result.buttons.set(VK_CONTROL, false);
result.buttons.set(VK_MENU, false);
#endif
}
return result;
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "input/api/Keyboard/KeyboardControllerProvider.h"
#include "input/api/Controller.h"
class KeyboardController : public Controller<KeyboardControllerProvider>
{
public:
KeyboardController();
std::string_view api_name() const override // TODO: use constexpr virtual function with c++20
{
static_assert(to_string(InputAPI::Keyboard) == "Keyboard");
return to_string(InputAPI::Keyboard);
}
InputAPI::Type api() const override { return InputAPI::Keyboard; }
bool is_connected() override { return true; }
bool has_axis() const override { return false; }
std::string get_button_name(uint64 button) const override;
protected:
ControllerState raw_state() override;
};

View file

@ -0,0 +1,10 @@
#include "input/api/Keyboard/KeyboardControllerProvider.h"
#include "input/api/Keyboard/KeyboardController.h"
std::vector<std::shared_ptr<ControllerBase>> KeyboardControllerProvider::get_controllers()
{
std::vector<std::shared_ptr<ControllerBase>> result;
result.emplace_back(std::make_shared<KeyboardController>());
return result;
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "input/api/ControllerProvider.h"
#ifndef HAS_KEYBOARD
#define HAS_KEYBOARD 1
#endif
class KeyboardControllerProvider : public ControllerProviderBase
{
friend class KeyboardController;
public:
inline static InputAPI::Type kAPIType = InputAPI::Keyboard;
InputAPI::Type api() const override { return kAPIType; }
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
};

View file

@ -0,0 +1,173 @@
#include "input/api/SDL/SDLController.h"
#include "input/api/SDL/SDLControllerProvider.h"
SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index)
: base_type(fmt::format("{}_", guid_index), fmt::format("Controller {}", guid_index + 1)), m_guid_index(guid_index),
m_guid(guid)
{
char tmp[64];
SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp));
m_uuid += tmp;
}
SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name)
: base_type(fmt::format("{}_", guid_index), display_name), m_guid_index(guid_index), m_guid(guid)
{
char tmp[64];
SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp));
m_uuid += tmp;
}
SDLController::~SDLController()
{
if (m_controller)
SDL_GameControllerClose(m_controller);
}
bool SDLController::is_connected()
{
std::scoped_lock lock(m_controller_mutex);
if (!m_controller)
{
return false;
}
if (!SDL_GameControllerGetAttached(m_controller))
{
m_controller = nullptr;
return false;
}
return true;
}
bool SDLController::connect()
{
if (is_connected())
{
return true;
}
m_has_rumble = false;
const auto index = m_provider->get_index(m_guid_index, m_guid);
std::scoped_lock lock(m_controller_mutex);
m_diid = SDL_JoystickGetDeviceInstanceID(index);
if (m_diid == -1)
return false;
m_controller = SDL_GameControllerFromInstanceID(m_diid);
if (!m_controller)
{
m_controller = SDL_GameControllerOpen(index);
if (!m_controller)
return false;
}
if (const char* name = SDL_GameControllerName(m_controller))
m_display_name = name;
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i)
{
m_buttons[i] = SDL_GameControllerHasButton(m_controller, (SDL_GameControllerButton)i);
}
for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i)
{
m_axis[i] = SDL_GameControllerHasAxis(m_controller, (SDL_GameControllerAxis)i);
}
if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_ACCEL))
{
m_has_accel = true;
SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE);
}
if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_GYRO))
{
m_has_gyro = true;
SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE);
}
m_has_rumble = SDL_GameControllerRumble(m_controller, 0, 0, 0) == 0;
return true;
}
void SDLController::start_rumble()
{
std::scoped_lock lock(m_controller_mutex);
if (is_connected() && !m_has_rumble)
return;
if (m_settings.rumble <= 0)
return;
SDL_GameControllerRumble(m_controller, (Uint16)(m_settings.rumble * 0xFFFF), (Uint16)(m_settings.rumble * 0xFFFF),
5 * 1000);
}
void SDLController::stop_rumble()
{
std::scoped_lock lock(m_controller_mutex);
if (is_connected() && !m_has_rumble)
return;
SDL_GameControllerRumble(m_controller, 0, 0, 0);
}
MotionSample SDLController::get_motion_sample()
{
if (is_connected() && has_motion())
{
return m_provider->motion_sample(m_diid);
}
return {};
}
std::string SDLController::get_button_name(uint64 button) const
{
if (const char* name = SDL_GameControllerGetStringForButton((SDL_GameControllerButton)button))
return name;
return base_type::get_button_name(button);
}
ControllerState SDLController::raw_state()
{
ControllerState result{};
std::scoped_lock lock(m_controller_mutex);
if (!is_connected())
return result;
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i)
{
if (m_buttons[i] && SDL_GameControllerGetButton(m_controller, (SDL_GameControllerButton)i))
{
result.buttons.set(i);
}
}
if (m_axis[SDL_CONTROLLER_AXIS_LEFTX])
result.axis.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_LEFTY])
result.axis.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_RIGHTX])
result.rotation.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTX) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_RIGHTY])
result.rotation.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTY) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERLEFT])
result.trigger.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERRIGHT])
result.trigger.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32767.0f;
return result;
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "input/api/Controller.h"
#include "input/api/SDL/SDLControllerProvider.h"
#include <SDL2/SDL_gamecontroller.h>
class SDLController : public Controller<SDLControllerProvider>
{
public:
SDLController(const SDL_JoystickGUID& guid, size_t guid_index);
SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name);
~SDLController() override;
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::SDLController) == "SDLController");
return to_string(InputAPI::SDLController);
}
InputAPI::Type api() const override { return InputAPI::SDLController; }
bool is_connected() override;
bool connect() override;
bool has_motion() override { return m_has_gyro && m_has_accel; }
bool has_rumble() override { return m_has_rumble; }
void start_rumble() override;
void stop_rumble() override;
MotionSample get_motion_sample() override;
std::string get_button_name(uint64 button) const override;
const SDL_JoystickGUID& get_guid() const { return m_guid; }
constexpr static SDL_JoystickGUID kLeftJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00,0x68 ,0x00 };
constexpr static SDL_JoystickGUID kRightJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
constexpr static SDL_JoystickGUID kSwitchProController{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
protected:
ControllerState raw_state() override;
private:
inline static SDL_JoystickGUID kEmptyGUID{};
size_t m_guid_index;
SDL_JoystickGUID m_guid;
std::recursive_mutex m_controller_mutex;
SDL_GameController* m_controller = nullptr;
SDL_JoystickID m_diid = -1;
bool m_has_gyro = false;
bool m_has_accel = false;
bool m_has_rumble = false;
std::array<bool, SDL_CONTROLLER_BUTTON_MAX> m_buttons{};
std::array<bool, SDL_CONTROLLER_AXIS_MAX> m_axis{};
};

View file

@ -0,0 +1,244 @@
#include "input/api/SDL/SDLControllerProvider.h"
#include "input/api/SDL/SDLController.h"
#include "util/helpers/TempState.h"
#include <SDL2/SDL.h>
#include <boost/functional/hash.hpp>
struct SDL_JoystickGUIDHash
{
std::size_t operator()(const SDL_JoystickGUID& guid) const
{
return boost::hash_value(guid.data);
}
};
SDLControllerProvider::SDLControllerProvider()
{
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1");
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) < 0)
throw std::runtime_error(fmt::format("couldn't initialize SDL: %s", SDL_GetError()));
if (SDL_GameControllerEventState(SDL_ENABLE) < 0) {
forceLog_printf("Couldn't enable SDL gamecontroller event polling: %s", SDL_GetError());
}
m_running = true;
m_thread = std::thread(&SDLControllerProvider::event_thread, this);
}
SDLControllerProvider::~SDLControllerProvider()
{
if (m_running)
{
m_running = false;
// wake the thread with a quit event if it's currently waiting for events
SDL_Event evt;
evt.type = SDL_QUIT;
SDL_PushEvent(&evt);
// wait until thread exited
m_thread.join();
}
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS);
}
std::vector<std::shared_ptr<ControllerBase>> SDLControllerProvider::get_controllers()
{
std::vector<std::shared_ptr<ControllerBase>> result;
std::unordered_map<SDL_JoystickGUID, size_t, SDL_JoystickGUIDHash> guid_counter;
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
for (int i = 0; i < SDL_NumJoysticks(); ++i)
{
if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER)
{
const auto guid = SDL_JoystickGetDeviceGUID(i);
const auto it = guid_counter.try_emplace(guid, 0);
if (auto* controller = SDL_GameControllerOpen(i))
{
const char* name = SDL_GameControllerName(controller);
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second, name));
SDL_GameControllerClose(controller);
}
else
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second));
++it.first->second;
}
}
return result;
}
int SDLControllerProvider::get_index(size_t guid_index, const SDL_JoystickGUID& guid) const
{
size_t index = 0;
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
for (int i = 0; i < SDL_NumJoysticks(); ++i)
{
if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER)
{
if(guid == SDL_JoystickGetDeviceGUID(i))
{
if (index == guid_index)
{
return i;
}
++index;
}
}
}
return -1;
}
MotionSample SDLControllerProvider::motion_sample(int diid)
{
std::scoped_lock lock(m_motion_data_mtx[diid]);
return m_motion_data[diid];
}
void SDLControllerProvider::event_thread()
{
SetThreadName("SDLControllerProvider::event_thread");
while (m_running.load(std::memory_order_relaxed))
{
SDL_Event event{};
SDL_WaitEvent(&event);
switch (event.type)
{
case SDL_QUIT:
m_running = false;
return;
case SDL_CONTROLLERAXISMOTION: /**< Game controller axis motion */
{
break;
}
case SDL_CONTROLLERBUTTONDOWN: /**< Game controller button pressed */
{
break;
}
case SDL_CONTROLLERBUTTONUP: /**< Game controller button released */
{
break;
}
case SDL_CONTROLLERDEVICEADDED: /**< A new Game controller has been inserted into the system */
{
InputManager::instance().on_device_changed();
break;
}
case SDL_CONTROLLERDEVICEREMOVED: /**< An opened Game controller has been removed */
{
InputManager::instance().on_device_changed();
break;
}
case SDL_CONTROLLERDEVICEREMAPPED: /**< The controller mapping was updated */
{
break;
}
case SDL_CONTROLLERTOUCHPADDOWN: /**< Game controller touchpad was touched */
{
break;
}
case SDL_CONTROLLERTOUCHPADMOTION: /**< Game controller touchpad finger was moved */
{
break;
}
case SDL_CONTROLLERTOUCHPADUP: /**< Game controller touchpad finger was lifted */
{
break;
}
case SDL_CONTROLLERSENSORUPDATE: /**< Game controller sensor was updated */
{
const auto index = event.csensor.which;
const auto ts = event.csensor.timestamp;
auto& motionTracking = m_motion_tracking[index];
if (event.csensor.sensor == SDL_SENSOR_ACCEL)
{
const auto dif = ts - motionTracking.lastTimestampAccel;
if (dif <= 0)
break;
if (dif >= 10000)
{
motionTracking.hasAcc = false;
motionTracking.hasGyro = false;
motionTracking.lastTimestampAccel = ts;
break;
}
motionTracking.lastTimestampAccel = ts;
motionTracking.acc[0] = -event.csensor.data[0] / 9.81f;
motionTracking.acc[1] = -event.csensor.data[1] / 9.81f;
motionTracking.acc[2] = -event.csensor.data[2] / 9.81f;
motionTracking.hasAcc = true;
}
if (event.csensor.sensor == SDL_SENSOR_GYRO)
{
const auto dif = ts - motionTracking.lastTimestampGyro;
if (dif <= 0)
break;
if (dif >= 10000)
{
motionTracking.hasAcc = false;
motionTracking.hasGyro = false;
motionTracking.lastTimestampGyro = ts;
break;
}
motionTracking.lastTimestampGyro = ts;
motionTracking.gyro[0] = event.csensor.data[0];
motionTracking.gyro[1] = -event.csensor.data[1];
motionTracking.gyro[2] = -event.csensor.data[2];
motionTracking.hasGyro = true;
}
if (motionTracking.hasAcc && motionTracking.hasGyro)
{
auto ts = std::max(motionTracking.lastTimestampGyro, motionTracking.lastTimestampAccel);
if (ts > motionTracking.lastTimestampIntegrate)
{
const auto tsDif = ts - motionTracking.lastTimestampIntegrate;
motionTracking.lastTimestampIntegrate = ts;
float tsDifD = (float)tsDif / 1000.0f;
if (tsDifD >= 1.0f)
tsDifD = 1.0f;
m_motion_handler[index].processMotionSample(tsDifD, motionTracking.gyro.x, motionTracking.gyro.y, motionTracking.gyro.z, motionTracking.acc.x, -motionTracking.acc.y, -motionTracking.acc.z);
std::scoped_lock lock(m_motion_data_mtx[index]);
m_motion_data[index] = m_motion_handler[index].getMotionSample();
}
motionTracking.hasAcc = false;
motionTracking.hasGyro = false;
}
break;
}
}
}
}

View file

@ -0,0 +1,54 @@
#pragma once
#include <SDL2/SDL_joystick.h>
#include "input/motion/MotionHandler.h"
#include "input/api/ControllerProvider.h"
#ifndef HAS_SDL
#define HAS_SDL 1
#endif
static bool operator==(const SDL_JoystickGUID& g1, const SDL_JoystickGUID& g2)
{
return memcmp(&g1, &g2, sizeof(SDL_JoystickGUID)) == 0;
}
class SDLControllerProvider : public ControllerProviderBase
{
friend class SDLController;
public:
SDLControllerProvider();
~SDLControllerProvider();
inline static InputAPI::Type kAPIType = InputAPI::SDLController;
InputAPI::Type api() const override { return kAPIType; }
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
int get_index(size_t guid_index, const SDL_JoystickGUID& guid) const;
MotionSample motion_sample(int diid);
private:
void event_thread();
std::atomic_bool m_running = false;
std::thread m_thread;
std::array<WiiUMotionHandler, 8> m_motion_handler{};
std::array<MotionSample, 8> m_motion_data{};
std::array<std::mutex, 8> m_motion_data_mtx{};
struct MotionInfoTracking
{
uint64 lastTimestampGyro{};
uint64 lastTimestampAccel{};
uint64 lastTimestampIntegrate{};
bool hasGyro{};
bool hasAcc{};
glm::vec3 gyro{};
glm::vec3 acc{};
};
std::array<MotionInfoTracking, 8> m_motion_tracking{};
};

View file

@ -0,0 +1,233 @@
#include "input/api/Wiimote/NativeWiimoteController.h"
#include "input/api/Wiimote/WiimoteControllerProvider.h"
#include <pugixml.hpp>
NativeWiimoteController::NativeWiimoteController(size_t index)
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index)
{
m_settings.motion = true;
}
void NativeWiimoteController::save(pugi::xml_node& node)
{
base_type::save(node);
node.append_child("packet_delay").append_child(pugi::node_pcdata).set_value(
fmt::format("{}", m_packet_delay).c_str());
}
void NativeWiimoteController::load(const pugi::xml_node& node)
{
base_type::load(node);
if (const auto value = node.child("packet_delay"))
m_packet_delay = ConvertString<uint32>(value.child_value());
}
bool NativeWiimoteController::connect()
{
if (is_connected())
return true;
if (!m_provider->is_registered_device(m_index))
{
m_provider->get_controllers();
}
if (m_provider->is_connected(m_index))
{
m_provider->set_packet_delay(m_index, m_packet_delay);
m_provider->set_led(m_index, m_player_index);
return true;
}
return false;
}
bool NativeWiimoteController::is_connected()
{
if (m_provider->is_connected(m_index))
{
m_provider->set_packet_delay(m_index, m_packet_delay);
return true;
}
return false;
}
void NativeWiimoteController::set_player_index(size_t player_index)
{
m_player_index = player_index;
m_provider->set_led(m_index, m_player_index);
}
NativeWiimoteController::Extension NativeWiimoteController::get_extension() const
{
Extension result = None;
const auto ext = m_provider->get_state(m_index).m_extension;
if (std::holds_alternative<NunchuckData>(ext))
result = Nunchuck;
else if (std::holds_alternative<ClassicData>(ext))
result = Classic;
return result;
}
bool NativeWiimoteController::is_mpls_attached() const
{
return m_provider->get_state(m_index).m_motion_plus.has_value();
}
bool NativeWiimoteController::has_position()
{
const auto state = m_provider->get_state(m_index);
return std::any_of(state.ir_camera.dots.cbegin(), state.ir_camera.dots.cend(),
[](const IRDot& v) { return v.visible; });
}
glm::vec2 NativeWiimoteController::get_position()
{
const auto state = m_provider->get_state(m_index);
return state.ir_camera.position;
}
glm::vec2 NativeWiimoteController::get_prev_position()
{
const auto state = m_provider->get_state(m_index);
return state.ir_camera.m_prev_position;
}
bool NativeWiimoteController::has_low_battery()
{
const auto state = m_provider->get_state(m_index);
return HAS_FLAG(state.flags, kBatteryEmpty);
}
void NativeWiimoteController::start_rumble()
{
if (m_settings.rumble < 1.0f)
{
return;
}
m_provider->set_rumble(m_index, true);
}
void NativeWiimoteController::stop_rumble()
{
m_provider->set_rumble(m_index, false);
}
MotionSample NativeWiimoteController::get_motion_sample()
{
const auto state = m_provider->get_state(m_index);
return state.motion_sample;
}
MotionSample NativeWiimoteController::get_nunchuck_motion_sample() const
{
const auto state = m_provider->get_state(m_index);
if (std::holds_alternative<NunchuckData>(state.m_extension))
{
return std::get<NunchuckData>(state.m_extension).motion_sample;
}
return {};
}
std::string NativeWiimoteController::get_button_name(uint64 button) const
{
switch (button)
{
case kWiimoteButton_A: return "A";
case kWiimoteButton_B: return "B";
case kWiimoteButton_One: return "1";
case kWiimoteButton_Two: return "2";
case kWiimoteButton_Plus: return "+";
case kWiimoteButton_Minus: return "-";
case kWiimoteButton_Home: return "HOME";
case kWiimoteButton_Up: return "UP";
case kWiimoteButton_Down: return "DOWN";
case kWiimoteButton_Left: return "LEFT";
case kWiimoteButton_Right: return "RIGHT";
// nunchuck
case kWiimoteButton_C: return "C";
case kWiimoteButton_Z: return "Z";
// classic
case kHighestWiimote + kClassicButton_A: return "A";
case kHighestWiimote + kClassicButton_B: return "B";
case kHighestWiimote + kClassicButton_Y: return "Y";
case kHighestWiimote + kClassicButton_X: return "X";
case kHighestWiimote + kClassicButton_Plus: return "+";
case kHighestWiimote + kClassicButton_Minus: return "-";
case kHighestWiimote + kClassicButton_Home: return "HOME";
case kHighestWiimote + kClassicButton_Up: return "UP";
case kHighestWiimote + kClassicButton_Down: return "DOWN";
case kHighestWiimote + kClassicButton_Left: return "LEFT";
case kHighestWiimote + kClassicButton_Right: return "RIGHT";
case kHighestWiimote + kClassicButton_L: return "L";
case kHighestWiimote + kClassicButton_R: return "R";
case kHighestWiimote + kClassicButton_ZL: return "ZL";
case kHighestWiimote + kClassicButton_ZR: return "ZR";
}
return base_type::get_button_name(button);
}
uint32 NativeWiimoteController::get_packet_delay()
{
m_packet_delay = m_provider->get_packet_delay(m_index);
return m_packet_delay;
}
void NativeWiimoteController::set_packet_delay(uint32 delay)
{
m_packet_delay = delay;
m_provider->set_packet_delay(m_index, delay);
}
ControllerState NativeWiimoteController::raw_state()
{
ControllerState result{};
if (!is_connected())
return result;
const auto state = m_provider->get_state(m_index);
result.buttons = state.buttons;
if (std::holds_alternative<NunchuckData>(state.m_extension))
{
const auto nunchuck = std::get<NunchuckData>(state.m_extension);
if (nunchuck.c)
result.buttons.set(kWiimoteButton_C);
if (nunchuck.z)
result.buttons.set(kWiimoteButton_Z);
result.axis = nunchuck.axis;
}
else if (std::holds_alternative<ClassicData>(state.m_extension))
{
const auto classic = std::get<ClassicData>(state.m_extension);
result.buttons |= (uint64)classic.buttons << kHighestWiimote;
result.axis = classic.left_axis;
result.rotation = classic.right_axis;
result.trigger = classic.trigger;
}
return result;
}

View file

@ -0,0 +1,66 @@
#pragma once
#include "input/api/Controller.h"
#include "input/api/Wiimote/WiimoteControllerProvider.h"
// todo: find better name because of emulated nameclash
class NativeWiimoteController : public Controller<WiimoteControllerProvider>
{
public:
NativeWiimoteController(size_t index);
enum Extension
{
None,
Nunchuck,
Classic,
MotionPlus,
};
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::Wiimote) == "Wiimote");
return to_string(InputAPI::Wiimote);
}
InputAPI::Type api() const override { return InputAPI::Wiimote; }
void save(pugi::xml_node& node) override;
void load(const pugi::xml_node& node) override;
bool connect() override;
bool is_connected() override;
void set_player_index(size_t player_index);
Extension get_extension() const;
bool is_mpls_attached() const;
ControllerState raw_state() override;
bool has_position() override;
glm::vec2 get_position() override;
glm::vec2 get_prev_position() override;
bool has_motion() override { return true; }
bool has_rumble() override { return true; }
bool has_battery() override { return true; }
bool has_low_battery() override;
void start_rumble() override;
void stop_rumble() override;
MotionSample get_motion_sample() override;
MotionSample get_nunchuck_motion_sample() const;
std::string get_button_name(uint64 button) const override;
uint32 get_packet_delay();
void set_packet_delay(uint32 delay);
private:
size_t m_index;
size_t m_player_index = 0;
uint32 m_packet_delay = WiimoteControllerProvider::kDefaultPacketDelay;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,126 @@
#pragma once
#include "input/motion/MotionHandler.h"
#include "input/api/Wiimote/WiimoteDevice.h"
#include "input/api/Wiimote/WiimoteMessages.h"
#include "input/api/ControllerProvider.h"
#include <list>
#include <variant>
#include <boost/ptr_container/ptr_vector.hpp>
#ifndef HAS_WIIMOTE
#define HAS_WIIMOTE 1
#endif
#define WIIMOTE_DEBUG 1
class WiimoteControllerProvider : public ControllerProviderBase
{
friend class WiimoteController;
public:
constexpr static uint32 kDefaultPacketDelay = 25;
WiimoteControllerProvider();
~WiimoteControllerProvider();
inline static InputAPI::Type kAPIType = InputAPI::Wiimote;
InputAPI::Type api() const override { return kAPIType; }
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
bool is_connected(size_t index);
bool is_registered_device(size_t index);
void set_rumble(size_t index, bool state);
void request_status(size_t index);
void set_led(size_t index, size_t player_index);
uint32 get_packet_delay(size_t index);
void set_packet_delay(size_t index, uint32 delay);
struct WiimoteState
{
uint16 buttons = 0;
uint8 flags = 0;
uint8 battery_level = 0;
glm::vec3 m_acceleration{}, m_prev_acceleration{};
float m_roll = 0;
std::chrono::high_resolution_clock::time_point m_last_motion_timestamp{};
MotionSample motion_sample{};
WiiUMotionHandler motion_handler{};
bool m_calibrated = false;
Calibration m_calib_acceleration{};
struct IRCamera
{
IRMode mode = kIRDisabled;
std::array<IRDot, 4> dots{}, prev_dots{};
glm::vec2 position{}, m_prev_position{};
glm::vec2 middle {};
float distance = 0;
std::pair<sint32, sint32> indices{ 0,1 };
}ir_camera{};
std::optional<MotionPlusData> m_motion_plus;
std::variant<std::monostate, NunchuckData, ClassicData> m_extension{};
};
WiimoteState get_state(size_t index);
private:
std::atomic_bool m_running = false;
std::thread m_reader_thread, m_writer_thread;
std::shared_mutex m_device_mutex;
struct Wiimote
{
Wiimote(WiimoteDevicePtr device)
: device(std::move(device)) {}
WiimoteDevicePtr device;
std::atomic_bool connected = true;
std::atomic_bool rumble = false;
std::shared_mutex mutex;
WiimoteState state{};
std::atomic_uint32_t data_delay = kDefaultPacketDelay;
std::chrono::high_resolution_clock::time_point data_ts{};
};
boost::ptr_vector<Wiimote> m_wiimotes;
std::list<std::pair<size_t,std::vector<uint8>>> m_write_queue;
std::mutex m_writer_mutex;
std::condition_variable m_writer_cond;
void reader_thread();
void writer_thread();
void calibrate(size_t index);
IRMode set_ir_camera(size_t index, bool state);
void send_packet(size_t index, std::vector<uint8> data);
void send_read_packet(size_t index, MemoryType type, RegisterAddress address, uint16 size);
void send_write_packet(size_t index, MemoryType type, RegisterAddress address, const std::vector<uint8>& data);
void parse_acceleration(WiimoteState& wiimote_state, const uint8*& data);
void rotate_ir(WiimoteState& wiimote_state);
void calculate_ir_position(WiimoteState& wiimote_state);
int parse_ir(WiimoteState& wiimote_state, const uint8* data);
void request_extension(size_t index);
void detect_motion_plus(size_t index);
void set_motion_plus(size_t index, bool state);
void update_report_type(size_t index);
};

View file

@ -0,0 +1,16 @@
#pragma once
class WiimoteDevice
{
friend class WiimoteInfo;
public:
virtual ~WiimoteDevice() = default;
virtual bool write_data(const std::vector<uint8>& data) = 0;
virtual std::optional<std::vector<uint8_t>> read_data() = 0;
virtual bool operator==(WiimoteDevice& o) const = 0;
bool operator!=(WiimoteDevice& o) const { return *this == o; }
};
using WiimoteDevicePtr = std::shared_ptr<WiimoteDevice>;

View file

@ -0,0 +1,244 @@
#pragma once
// https://wiibrew.org/wiki/Wiimote
enum InputReportId : uint8
{
kNone = 0,
kStatus = 0x20,
kRead = 0x21,
kWrite = 0x22,
kDataCore = 0x30,
kDataCoreAcc = 0x31,
kDataCoreExt8 = 0x32,
kDataCoreAccIR = 0x33,
kDataCoreExt19 = 0x34,
kDataCoreAccExt = 0x35,
kDataCoreIRExt = 0x36,
kDataCoreAccIRExt = 0x37,
kDataExt = 0x3d,
};
enum RegisterAddress : uint32
{
kRegisterCalibration = 0x16,
kRegisterCalibration2 = 0x20, // backup calibration data
kRegisterIR = 0x4b00030,
kRegisterIRSensitivity1 = 0x4b00000,
kRegisterIRSensitivity2 = 0x4b0001a,
kRegisterIRMode = 0x4b00033,
kRegisterExtensionEncrypted = 0x4a40040,
kRegisterExtension1 = 0x4a400f0,
kRegisterExtension2 = 0x4a400fb,
kRegisterExtensionType = 0x4a400fa,
kRegisterExtensionCalibration = 0x4a40020,
kRegisterMotionPlusDetect = 0x4a600fa,
kRegisterMotionPlusInit = 0x4a600f0,
kRegisterMotionPlusEnable = 0x4a600fe,
};
enum ExtensionType : uint64
{
kExtensionNunchuck = 0x0000A4200000,
kExtensionClassic = 0x0000A4200101,
kExtensionClassicPro = 0x0100A4200101,
kExtensionDrawsome = 0xFF00A4200013,
kExtensionGuitar = 0x0000A4200103,
kExtensionDrums = 0x0100A4200103,
kExtensionBalanceBoard = 0x2A2C,
kExtensionMotionPlus = 0xa6200005,
kExtensionPartialyInserted = 0xffffffffffff,
};
enum MemoryType : uint8
{
kEEPROMMemory = 0,
kRegisterMemory = 0x4,
};
enum StatusBitmask : uint8
{
kBatteryEmpty = 0x1,
kExtensionConnected = 0x2,
kSpeakerEnabled = 0x4,
kIREnabled = 0x8,
kLed1 = 0x10,
kLed2 = 0x20,
kLed3 = 0x40,
kLed4 = 0x80
};
enum OutputReportId : uint8
{
kLED = 0x11,
kType = 0x12,
kIR = 0x13,
kSpeakerState = 0x14,
kStatusRequest = 0x15,
kWriteMemory = 0x16,
kReadMemory = 0x17,
kSpeakerData = 0x18,
kSpeakerMute = 0x19,
kIR2 = 0x1A,
};
enum IRMode : uint8
{
kIRDisabled,
kBasicIR = 1,
kExtendedIR = 3,
kFullIR = 5,
};
enum WiimoteButtons
{
kWiimoteButton_Left = 0,
kWiimoteButton_Right = 1,
kWiimoteButton_Down = 2,
kWiimoteButton_Up = 3,
kWiimoteButton_Plus = 4,
kWiimoteButton_Two = 8,
kWiimoteButton_One = 9,
kWiimoteButton_B = 10,
kWiimoteButton_A = 11,
kWiimoteButton_Minus = 12,
kWiimoteButton_Home = 15,
// self defined
kWiimoteButton_C = 16,
kWiimoteButton_Z = 17,
kHighestWiimote = 20,
};
enum ClassicButtons
{
kClassicButton_R = 1,
kClassicButton_Plus = 2,
kClassicButton_Home = 3,
kClassicButton_Minus = 4,
kClassicButton_L = 5,
kClassicButton_Down = 6,
kClassicButton_Right = 7,
kClassicButton_Up = 8,
kClassicButton_Left = 9,
kClassicButton_ZR = 10,
kClassicButton_X = 11,
kClassicButton_A = 12,
kClassicButton_Y = 13,
kClassicButton_B = 14,
kClassicButton_ZL = 15,
};
struct Calibration
{
glm::vec<3, uint16> zero{ 0x200, 0x200, 0x200 };
glm::vec<3, uint16> gravity{ 0x240, 0x240, 0x240 };
};
struct BasicIR
{
uint8 x1;
uint8 y1;
struct
{
uint8 x2 : 2;
uint8 y2 : 2;
uint8 x1 : 2;
uint8 y1 : 2;
} bits;
static_assert(sizeof(bits) == 1);
uint8 x2;
uint8 y2;
};
static_assert(sizeof(BasicIR) == 5);
struct ExtendedIR
{
uint8 x;
uint8 y;
struct
{
uint8 size : 4;
uint8 x : 2;
uint8 y : 2;
} bits;
static_assert(sizeof(bits) == 1);
};
static_assert(sizeof(ExtendedIR) == 3);
struct IRDot
{
bool visible = false;
glm::vec2 pos;
glm::vec<2, uint16> raw;
uint32 size;
};
struct IRCamera
{
IRMode mode;
std::array<IRDot, 4> dots{}, prev_dots{};
glm::vec2 position, m_prev_position;
glm::vec2 middle;
float distance;
std::pair<sint32, sint32> indices{ 0,1 };
};
struct NunchuchCalibration : Calibration
{
glm::vec<2, uint8> min{};
glm::vec<2, uint8> center{ 0x7f, 0x7f };
glm::vec<2, uint8> max{ 0xff, 0xff };
};
struct MotionPlusData
{
Calibration calibration{};
glm::vec3 orientation; // yaw, roll, pitch
bool slow_roll = false;
bool slow_pitch = false;
bool slow_yaw = false;
bool extension_connected = false;
};
struct NunchuckData
{
glm::vec3 acceleration{}, prev_acceleration{};
NunchuchCalibration calibration{};
bool c = false;
bool z = false;
glm::vec2 axis{};
glm::vec<2, uint8> raw_axis{};
MotionSample motion_sample{};
};
struct ClassicData
{
glm::vec2 left_axis{};
glm::vec<2, uint8> left_raw_axis{};
glm::vec2 right_axis{};
glm::vec<2, uint8> right_raw_axis{};
glm::vec2 trigger{};
glm::vec<2, uint8> raw_trigger{};
uint16 buttons = 0;
};

View file

@ -0,0 +1,127 @@
#include "input/api/Wiimote/windows/WinWiimoteDevice.h"
#include <hidsdi.h>
#include <SetupAPI.h>
WinWiimoteDevice::WinWiimoteDevice(HANDLE handle, std::vector<uint8_t> identifier)
: m_handle(handle), m_identifier(std::move(identifier))
{
m_overlapped.hEvent = CreateEvent(nullptr, TRUE, TRUE, nullptr);
}
WinWiimoteDevice::~WinWiimoteDevice()
{
CancelIo(m_handle);
ResetEvent(m_overlapped.hEvent);
CloseHandle(m_handle);
}
bool WinWiimoteDevice::write_data(const std::vector<uint8>& data)
{
return HidD_SetOutputReport(m_handle, (void*)data.data(), (ULONG)data.size());
}
std::optional<std::vector<uint8_t>> WinWiimoteDevice::read_data()
{
DWORD read = 0;
std::array<uint8_t, 32> buffer{};
if (!ReadFile(m_handle, buffer.data(), (DWORD)buffer.size(), &read, &m_overlapped))
{
const auto error = GetLastError();
if (error == ERROR_DEVICE_NOT_CONNECTED)
return {};
else if (error == ERROR_IO_PENDING)
{
const auto wait_result = WaitForSingleObject(m_overlapped.hEvent, 100);
if (wait_result == WAIT_TIMEOUT)
{
CancelIo(m_handle);
ResetEvent(m_overlapped.hEvent);
return {};
}
else if (wait_result == WAIT_FAILED)
return {};
if (GetOverlappedResult(m_handle, &m_overlapped, &read, FALSE) == FALSE)
return {};
}
else if (error == ERROR_INVALID_HANDLE)
{
ResetEvent(m_overlapped.hEvent);
return {};
}
else
{
cemu_assert_debug(false);
}
}
ResetEvent(m_overlapped.hEvent);
if (read == 0)
return {};
return {{buffer.cbegin(), buffer.cbegin() + read}};
}
std::vector<WiimoteDevicePtr> WinWiimoteDevice::get_devices()
{
std::vector<WiimoteDevicePtr> result;
GUID hid_guid;
HidD_GetHidGuid(&hid_guid);
const auto device_info = SetupDiGetClassDevs(&hid_guid, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
for (DWORD index = 0; ; ++index)
{
SP_DEVICE_INTERFACE_DATA device_data{};
device_data.cbSize = sizeof(device_data);
if (SetupDiEnumDeviceInterfaces(device_info, nullptr, &hid_guid, index, &device_data) == FALSE)
break;
DWORD device_data_len;
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, nullptr, 0, &device_data_len, nullptr) == FALSE
&& GetLastError() != ERROR_INSUFFICIENT_BUFFER)
continue;
std::vector<uint8_t> detail_data_buffer;
detail_data_buffer.resize(device_data_len);
const auto detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)detail_data_buffer.data();
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, device_data_len, nullptr, nullptr)
== FALSE)
continue;
HANDLE device_handle = CreateFile(detail_data->DevicePath, (GENERIC_READ | GENERIC_WRITE),
(FILE_SHARE_READ | FILE_SHARE_WRITE), nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, nullptr);
if (device_handle == INVALID_HANDLE_VALUE)
continue;
HIDD_ATTRIBUTES attributes{};
attributes.Size = sizeof(attributes);
if (HidD_GetAttributes(device_handle, &attributes) == FALSE)
{
CloseHandle(device_handle);
continue;
}
if (attributes.VendorID != 0x057e || (attributes.ProductID != 0x0306 && attributes.ProductID != 0x0330))
{
CloseHandle(device_handle);
continue;
}
result.emplace_back(std::make_shared<WinWiimoteDevice>(device_handle, detail_data_buffer));
}
return result;
}
bool WinWiimoteDevice::operator==(WiimoteDevice& o) const
{
return m_identifier == static_cast<WinWiimoteDevice&>(o).m_identifier;
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "input/api/Wiimote/WiimoteDevice.h"
class WinWiimoteDevice : public WiimoteDevice
{
public:
WinWiimoteDevice(HANDLE handle, std::vector<uint8_t> identifier);
~WinWiimoteDevice();
bool write_data(const std::vector<uint8>& data) override;
std::optional<std::vector<uint8_t>> read_data() override;
static std::vector<WiimoteDevicePtr> get_devices();
bool operator==(WiimoteDevice& o) const override;
private:
HANDLE m_handle;
OVERLAPPED m_overlapped{};
std::vector<uint8_t> m_identifier;
};
using WiimoteDevice_t = WinWiimoteDevice;

View file

@ -0,0 +1,151 @@
#include "input/api/XInput/XInputController.h"
XInputController::XInputController(uint32 index)
: base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1))
{
if (index >= XUSER_MAX_COUNT)
throw std::runtime_error(fmt::format("invalid xinput index {} (must be smaller than {})", index,
XUSER_MAX_COUNT));
m_index = index;
m_settings.axis.deadzone = m_settings.rotation.deadzone = m_settings.trigger.deadzone = 0.15f;
}
bool XInputController::connect()
{
if (m_connected)
return true;
m_has_battery = false;
XINPUT_CAPABILITIES caps{};
m_connected = m_provider->m_XInputGetCapabilities(m_index, XINPUT_FLAG_GAMEPAD, &caps) !=
ERROR_DEVICE_NOT_CONNECTED;
if (!m_connected) return false;
m_has_rumble = (caps.Vibration.wLeftMotorSpeed > 0) || (caps.Vibration.wRightMotorSpeed > 0);
if (m_provider->m_XInputGetBatteryInformation)
{
XINPUT_BATTERY_INFORMATION battery{};
if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS)
{
m_has_battery = (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType ==
BATTERY_TYPE_NIMH);
}
}
return m_connected;
}
bool XInputController::is_connected()
{
return m_connected;
}
void XInputController::start_rumble()
{
if (!has_rumble() || m_settings.rumble <= 0)
return;
XINPUT_VIBRATION vibration;
vibration.wLeftMotorSpeed = static_cast<WORD>(m_settings.rumble * std::numeric_limits<uint16>::max());
vibration.wRightMotorSpeed = static_cast<WORD>(m_settings.rumble * std::numeric_limits<uint16>::max());
m_provider->m_XInputSetState(m_index, &vibration);
}
void XInputController::stop_rumble()
{
if (!has_rumble())
return;
XINPUT_VIBRATION vibration{};
m_provider->m_XInputSetState(m_index, &vibration);
}
bool XInputController::has_low_battery()
{
if (!has_battery())
return false;
XINPUT_BATTERY_INFORMATION battery{};
if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS)
{
return (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType == BATTERY_TYPE_NIMH) && battery
.BatteryLevel <= BATTERY_LEVEL_LOW;
}
return false;
}
std::string XInputController::get_button_name(uint64 button) const
{
switch (1ULL << button)
{
case XINPUT_GAMEPAD_A: return "A";
case XINPUT_GAMEPAD_B: return "B";
case XINPUT_GAMEPAD_X: return "X";
case XINPUT_GAMEPAD_Y: return "Y";
case XINPUT_GAMEPAD_LEFT_SHOULDER: return "L";
case XINPUT_GAMEPAD_RIGHT_SHOULDER: return "R";
case XINPUT_GAMEPAD_START: return "Start";
case XINPUT_GAMEPAD_BACK: return "Select";
case XINPUT_GAMEPAD_LEFT_THUMB: return "L-Stick";
case XINPUT_GAMEPAD_RIGHT_THUMB: return "R-Stick";
case XINPUT_GAMEPAD_DPAD_UP: return "DPAD-Up";
case XINPUT_GAMEPAD_DPAD_DOWN: return "DPAD-Down";
case XINPUT_GAMEPAD_DPAD_LEFT: return "DPAD-Left";
case XINPUT_GAMEPAD_DPAD_RIGHT: return "DPAD-Right";
}
return Controller::get_button_name(button);
}
ControllerState XInputController::raw_state()
{
ControllerState result{};
if (!m_connected)
return result;
XINPUT_STATE state;
if (m_provider->m_XInputGetState(m_index, &state) != ERROR_SUCCESS)
{
m_connected = false;
return result;
}
// Buttons
result.buttons = state.Gamepad.wButtons;
if (state.Gamepad.sThumbLX > 0)
result.axis.x = (float)state.Gamepad.sThumbLX / std::numeric_limits<sint16>::max();
else if (state.Gamepad.sThumbLX < 0)
result.axis.x = (float)-state.Gamepad.sThumbLX / std::numeric_limits<sint16>::min();
if (state.Gamepad.sThumbLY > 0)
result.axis.y = (float)state.Gamepad.sThumbLY / std::numeric_limits<sint16>::max();
else if (state.Gamepad.sThumbLY < 0)
result.axis.y = (float)-state.Gamepad.sThumbLY / std::numeric_limits<sint16>::min();
// Right Stick
if (state.Gamepad.sThumbRX > 0)
result.rotation.x = (float)state.Gamepad.sThumbRX / std::numeric_limits<sint16>::max();
else if (state.Gamepad.sThumbRX < 0)
result.rotation.x = (float)-state.Gamepad.sThumbRX / std::numeric_limits<sint16>::min();
if (state.Gamepad.sThumbRY > 0)
result.rotation.y = (float)state.Gamepad.sThumbRY / std::numeric_limits<sint16>::max();
else if (state.Gamepad.sThumbRY < 0)
result.rotation.y = (float)-state.Gamepad.sThumbRY / std::numeric_limits<sint16>::min();
// Trigger
result.trigger.x = (float)state.Gamepad.bLeftTrigger / std::numeric_limits<uint8>::max();
result.trigger.y = (float)state.Gamepad.bRightTrigger / std::numeric_limits<uint8>::max();
return result;
}

View file

@ -0,0 +1,38 @@
#pragma once
#include "input/api/XInput/XInputControllerProvider.h"
#include "input/api/Controller.h"
class XInputController : public Controller<XInputControllerProvider>
{
public:
XInputController(uint32 index);
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::XInput) == "XInput");
return to_string(InputAPI::XInput);
}
InputAPI::Type api() const override { return InputAPI::XInput; }
bool connect() override;
bool is_connected() override;
bool has_rumble() override { return m_has_rumble; }
bool has_battery() override { return m_has_battery; }
bool has_low_battery() override;
void start_rumble() override;
void stop_rumble() override;
std::string get_button_name(uint64 button) const override;
protected:
ControllerState raw_state() override;
private:
uint32 m_index;
bool m_connected = false;
bool m_has_battery = false;
bool m_has_rumble = false;
};

View file

@ -0,0 +1,57 @@
#include <Windows.h>
#include "input/api/XInput/XInputControllerProvider.h"
#include "input/api/XInput/XInputController.h"
XInputControllerProvider::XInputControllerProvider()
{
// try to load newest to oldest
m_module = LoadLibraryA("XInput1_4.DLL");
if (!m_module)
{
m_module = LoadLibraryA("XInput1_3.DLL");
if (!m_module)
{
m_module = LoadLibraryA("XInput9_1_0.dll");
if (!m_module)
throw std::runtime_error("can't load any xinput dll");
}
}
#define GET_XINPUT_PROC(__FUNC__) m_##__FUNC__ = (decltype(m_##__FUNC__))GetProcAddress(m_module, #__FUNC__)
GET_XINPUT_PROC(XInputGetCapabilities);
GET_XINPUT_PROC(XInputGetState);
GET_XINPUT_PROC(XInputSetState);
if (!m_XInputGetCapabilities || !m_XInputGetState || !m_XInputSetState)
{
FreeLibrary(m_module);
throw std::runtime_error("can't find necessary xinput functions");
}
// only available in XInput1_4 and XInput1_3
GET_XINPUT_PROC(XInputGetBatteryInformation);
#undef GET_XINPUT_PROC
}
XInputControllerProvider::~XInputControllerProvider()
{
if (m_module)
FreeLibrary(m_module);
}
std::vector<std::shared_ptr<ControllerBase>> XInputControllerProvider::get_controllers()
{
std::vector<std::shared_ptr<ControllerBase>> result;
for(DWORD i = 0; i < XUSER_MAX_COUNT; ++i)
{
XINPUT_CAPABILITIES caps;
if (m_XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS)
{
result.emplace_back(std::make_shared<XInputController>(i));
}
}
return result;
}

View file

@ -0,0 +1,32 @@
#pragma once
#if BOOST_OS_WINDOWS
#include "input/api/ControllerProvider.h"
#include <Xinput.h>
#ifndef HAS_XINPUT
#define HAS_XINPUT 1
#endif
class XInputControllerProvider : public ControllerProviderBase
{
friend class XInputController;
public:
XInputControllerProvider();
~XInputControllerProvider() override;
inline static InputAPI::Type kAPIType = InputAPI::XInput;
InputAPI::Type api() const override { return kAPIType; }
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
private:
HMODULE m_module = nullptr;
decltype(&XInputGetBatteryInformation) m_XInputGetBatteryInformation;
decltype(&XInputGetCapabilities) m_XInputGetCapabilities;
decltype(&XInputSetState) m_XInputSetState;
decltype(&XInputGetState) m_XInputGetState;
};
#endif