mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-12-15 10:37:01 +00:00
Add all the files
This commit is contained in:
parent
e3db07a16a
commit
d60742f52b
1445 changed files with 430238 additions and 0 deletions
246
src/input/api/Controller.cpp
Normal file
246
src/input/api/Controller.cpp
Normal 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
201
src/input/api/Controller.h
Normal 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>;
|
||||
|
||||
88
src/input/api/ControllerProvider.h
Normal file
88
src/input/api/ControllerProvider.h
Normal 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{};
|
||||
};
|
||||
13
src/input/api/ControllerState.cpp
Normal file
13
src/input/api/ControllerState.cpp
Normal 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);*/
|
||||
|
||||
}
|
||||
30
src/input/api/ControllerState.h
Normal file
30
src/input/api/ControllerState.h
Normal 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);
|
||||
}
|
||||
};
|
||||
168
src/input/api/DSU/DSUController.cpp
Normal file
168
src/input/api/DSU/DSUController.cpp
Normal 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;
|
||||
}
|
||||
44
src/input/api/DSU/DSUController.h
Normal file
44
src/input/api/DSU/DSUController.h
Normal 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;
|
||||
};
|
||||
|
||||
448
src/input/api/DSU/DSUControllerProvider.cpp
Normal file
448
src/input/api/DSU/DSUControllerProvider.cpp
Normal 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;
|
||||
}
|
||||
118
src/input/api/DSU/DSUControllerProvider.h
Normal file
118
src/input/api/DSU/DSUControllerProvider.h
Normal 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{};
|
||||
};
|
||||
97
src/input/api/DSU/DSUMessages.cpp
Normal file
97
src/input/api/DSU/DSUMessages.cpp
Normal 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));
|
||||
}
|
||||
275
src/input/api/DSU/DSUMessages.h
Normal file
275
src/input/api/DSU/DSUMessages.h
Normal 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)
|
||||
337
src/input/api/DirectInput/DirectInputController.cpp
Normal file
337
src/input/api/DirectInput/DirectInputController.cpp
Normal 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;
|
||||
}
|
||||
49
src/input/api/DirectInput/DirectInputController.h
Normal file
49
src/input/api/DirectInput/DirectInputController.h
Normal 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{};
|
||||
};
|
||||
65
src/input/api/DirectInput/DirectInputControllerProvider.cpp
Normal file
65
src/input/api/DirectInput/DirectInputControllerProvider.cpp
Normal 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();
|
||||
}
|
||||
37
src/input/api/DirectInput/DirectInputControllerProvider.h
Normal file
37
src/input/api/DirectInput/DirectInputControllerProvider.h
Normal 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
|
||||
103
src/input/api/GameCube/GameCubeController.cpp
Normal file
103
src/input/api/GameCube/GameCubeController.cpp
Normal 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
|
||||
34
src/input/api/GameCube/GameCubeController.h
Normal file
34
src/input/api/GameCube/GameCubeController.h
Normal 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
|
||||
388
src/input/api/GameCube/GameCubeControllerProvider.cpp
Normal file
388
src/input/api/GameCube/GameCubeControllerProvider.cpp
Normal 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
|
||||
79
src/input/api/GameCube/GameCubeControllerProvider.h
Normal file
79
src/input/api/GameCube/GameCubeControllerProvider.h
Normal 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
79
src/input/api/InputAPI.h
Normal 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));
|
||||
}
|
||||
}
|
||||
66
src/input/api/Keyboard/KeyboardController.cpp
Normal file
66
src/input/api/Keyboard/KeyboardController.cpp
Normal 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;
|
||||
}
|
||||
27
src/input/api/Keyboard/KeyboardController.h
Normal file
27
src/input/api/Keyboard/KeyboardController.h
Normal 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;
|
||||
|
||||
};
|
||||
10
src/input/api/Keyboard/KeyboardControllerProvider.cpp
Normal file
10
src/input/api/Keyboard/KeyboardControllerProvider.cpp
Normal 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;
|
||||
}
|
||||
17
src/input/api/Keyboard/KeyboardControllerProvider.h
Normal file
17
src/input/api/Keyboard/KeyboardControllerProvider.h
Normal 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;
|
||||
};
|
||||
173
src/input/api/SDL/SDLController.cpp
Normal file
173
src/input/api/SDL/SDLController.cpp
Normal 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;
|
||||
}
|
||||
60
src/input/api/SDL/SDLController.h
Normal file
60
src/input/api/SDL/SDLController.h
Normal 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{};
|
||||
};
|
||||
|
||||
244
src/input/api/SDL/SDLControllerProvider.cpp
Normal file
244
src/input/api/SDL/SDLControllerProvider.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/input/api/SDL/SDLControllerProvider.h
Normal file
54
src/input/api/SDL/SDLControllerProvider.h
Normal 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{};
|
||||
|
||||
};
|
||||
233
src/input/api/Wiimote/NativeWiimoteController.cpp
Normal file
233
src/input/api/Wiimote/NativeWiimoteController.cpp
Normal 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;
|
||||
}
|
||||
66
src/input/api/Wiimote/NativeWiimoteController.h
Normal file
66
src/input/api/Wiimote/NativeWiimoteController.h
Normal 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;
|
||||
};
|
||||
|
||||
1056
src/input/api/Wiimote/WiimoteControllerProvider.cpp
Normal file
1056
src/input/api/Wiimote/WiimoteControllerProvider.cpp
Normal file
File diff suppressed because it is too large
Load diff
126
src/input/api/Wiimote/WiimoteControllerProvider.h
Normal file
126
src/input/api/Wiimote/WiimoteControllerProvider.h
Normal 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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
16
src/input/api/Wiimote/WiimoteDevice.h
Normal file
16
src/input/api/Wiimote/WiimoteDevice.h
Normal 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>;
|
||||
244
src/input/api/Wiimote/WiimoteMessages.h
Normal file
244
src/input/api/Wiimote/WiimoteMessages.h
Normal 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;
|
||||
};
|
||||
127
src/input/api/Wiimote/windows/WinWiimoteDevice.cpp
Normal file
127
src/input/api/Wiimote/windows/WinWiimoteDevice.cpp
Normal 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;
|
||||
}
|
||||
24
src/input/api/Wiimote/windows/WinWiimoteDevice.h
Normal file
24
src/input/api/Wiimote/windows/WinWiimoteDevice.h
Normal 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;
|
||||
151
src/input/api/XInput/XInputController.cpp
Normal file
151
src/input/api/XInput/XInputController.cpp
Normal 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;
|
||||
}
|
||||
38
src/input/api/XInput/XInputController.h
Normal file
38
src/input/api/XInput/XInputController.h
Normal 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;
|
||||
};
|
||||
57
src/input/api/XInput/XInputControllerProvider.cpp
Normal file
57
src/input/api/XInput/XInputControllerProvider.cpp
Normal 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;
|
||||
}
|
||||
32
src/input/api/XInput/XInputControllerProvider.h
Normal file
32
src/input/api/XInput/XInputControllerProvider.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue