mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 23:55:40 +00:00
Add evdev controller interface
This commit is contained in:
parent
ee3aa0dc4d
commit
53fb55ff15
|
@ -75,6 +75,17 @@ if(SDL2_FOUND)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
find_package(LIBEVDEV REQUIRED)
|
||||||
|
target_compile_definitions(frontend-common PUBLIC "-DWITH_EVDEV=1")
|
||||||
|
target_include_directories(frontend-common PRIVATE ${LIBEVDEV_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(frontend-common PRIVATE ${LIBEVDEV_LIBRARIES})
|
||||||
|
target_sources(frontend-common PRIVATE
|
||||||
|
evdev_controller_interface.cpp
|
||||||
|
evdev_controller_interface.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(ENABLE_DISCORD_PRESENCE)
|
if(ENABLE_DISCORD_PRESENCE)
|
||||||
target_compile_definitions(frontend-common PUBLIC -DWITH_DISCORD_PRESENCE=1)
|
target_compile_definitions(frontend-common PUBLIC -DWITH_DISCORD_PRESENCE=1)
|
||||||
target_link_libraries(frontend-common PRIVATE discord-rpc)
|
target_link_libraries(frontend-common PRIVATE discord-rpc)
|
||||||
|
|
|
@ -95,6 +95,9 @@ static constexpr std::array<const char*, static_cast<u32>(ControllerInterface::B
|
||||||
// Deliberately not translated as it's not exposed to users.
|
// Deliberately not translated as it's not exposed to users.
|
||||||
"Android",
|
"Android",
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
TRANSLATABLE("ControllerInterface", "Evdev"),
|
||||||
|
#endif
|
||||||
}};
|
}};
|
||||||
|
|
||||||
std::optional<ControllerInterface::Backend> ControllerInterface::ParseBackendName(const char* name)
|
std::optional<ControllerInterface::Backend> ControllerInterface::ParseBackendName(const char* name)
|
||||||
|
@ -132,6 +135,9 @@ ControllerInterface::Backend ControllerInterface::GetDefaultBackend()
|
||||||
#include "dinput_controller_interface.h"
|
#include "dinput_controller_interface.h"
|
||||||
#include "xinput_controller_interface.h"
|
#include "xinput_controller_interface.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
#include "evdev_controller_interface.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
std::unique_ptr<ControllerInterface> ControllerInterface::Create(Backend type)
|
std::unique_ptr<ControllerInterface> ControllerInterface::Create(Backend type)
|
||||||
{
|
{
|
||||||
|
@ -145,6 +151,10 @@ std::unique_ptr<ControllerInterface> ControllerInterface::Create(Backend type)
|
||||||
if (type == Backend::DInput)
|
if (type == Backend::DInput)
|
||||||
return std::make_unique<DInputControllerInterface>();
|
return std::make_unique<DInputControllerInterface>();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
if (type == Backend::Evdev)
|
||||||
|
return std::make_unique<EvdevControllerInterface>();
|
||||||
|
#endif
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,9 @@ public:
|
||||||
#endif
|
#endif
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
Android,
|
Android,
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
Evdev,
|
||||||
#endif
|
#endif
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
382
src/frontend-common/evdev_controller_interface.cpp
Normal file
382
src/frontend-common/evdev_controller_interface.cpp
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
#include "evdev_controller_interface.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/file_system.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "core/controller.h"
|
||||||
|
#include "core/host_interface.h"
|
||||||
|
#include "core/system.h"
|
||||||
|
#include <alloca.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
Log_SetChannel(EvdevControllerInterface);
|
||||||
|
|
||||||
|
EvdevControllerInterface::EvdevControllerInterface() = default;
|
||||||
|
|
||||||
|
EvdevControllerInterface::~EvdevControllerInterface() = default;
|
||||||
|
|
||||||
|
ControllerInterface::Backend EvdevControllerInterface::GetBackend() const
|
||||||
|
{
|
||||||
|
return ControllerInterface::Backend::Evdev;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::Initialize(CommonHostInterface* host_interface)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < 1000; index++)
|
||||||
|
{
|
||||||
|
TinyString path;
|
||||||
|
path.Format("/dev/input/event%d", index);
|
||||||
|
|
||||||
|
int fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||||
|
if (fd < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
struct libevdev* obj;
|
||||||
|
if (libevdev_new_from_fd(fd, &obj) != 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("libevdev_new_from_fd(%s) failed", path.GetCharArray());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerData data(fd, obj);
|
||||||
|
if (InitializeController(index, &data))
|
||||||
|
m_controllers.push_back(std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ControllerInterface::Initialize(host_interface))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevControllerInterface::Shutdown()
|
||||||
|
{
|
||||||
|
ControllerInterface::Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevControllerInterface::ControllerData::ControllerData(int fd_, struct libevdev* obj_) : obj(obj_), fd(fd_) {}
|
||||||
|
|
||||||
|
EvdevControllerInterface::ControllerData::ControllerData(ControllerData&& move)
|
||||||
|
: obj(move.obj), fd(move.fd), controller_id(move.controller_id), num_motors(move.num_motors), deadzone(move.deadzone),
|
||||||
|
axises(std::move(move.axises)), buttons(std::move(move.buttons))
|
||||||
|
{
|
||||||
|
move.obj = nullptr;
|
||||||
|
move.fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevControllerInterface::ControllerData::~ControllerData()
|
||||||
|
{
|
||||||
|
if (obj)
|
||||||
|
libevdev_free(obj);
|
||||||
|
if (fd >= 0)
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevControllerInterface::ControllerData&
|
||||||
|
EvdevControllerInterface::ControllerData::operator=(EvdevControllerInterface::ControllerData&& move)
|
||||||
|
{
|
||||||
|
if (obj)
|
||||||
|
libevdev_free(obj);
|
||||||
|
obj = move.obj;
|
||||||
|
move.obj = nullptr;
|
||||||
|
if (fd >= 0)
|
||||||
|
close(fd);
|
||||||
|
fd = move.fd;
|
||||||
|
move.fd = -1;
|
||||||
|
controller_id = move.controller_id;
|
||||||
|
num_motors = move.num_motors;
|
||||||
|
deadzone = move.deadzone;
|
||||||
|
axises = std::move(move.axises);
|
||||||
|
buttons = std::move(move.buttons);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevControllerInterface::ControllerData* EvdevControllerInterface::GetControllerById(int id)
|
||||||
|
{
|
||||||
|
for (ControllerData& cd : m_controllers)
|
||||||
|
{
|
||||||
|
if (cd.controller_id == id)
|
||||||
|
return &cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::InitializeController(int index, ControllerData* cd)
|
||||||
|
{
|
||||||
|
const char* name = libevdev_get_name(cd->obj);
|
||||||
|
Log_DevPrintf("Input %d device name: \"%s\"", index, name);
|
||||||
|
Log_DevPrintf("Input %d device ID: bus %#x vendor %#x product %#x", index, libevdev_get_id_bustype(cd->obj),
|
||||||
|
libevdev_get_id_vendor(cd->obj), libevdev_get_id_product(cd->obj));
|
||||||
|
|
||||||
|
for (u32 key = 0; key < KEY_CNT; key++)
|
||||||
|
{
|
||||||
|
if (!libevdev_has_event_code(cd->obj, EV_KEY, key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const char* button_name = libevdev_event_code_get_name(EV_KEY, key);
|
||||||
|
Log_DevPrintf("Key %d: %s -> Button %zu", key, button_name ? button_name : "null", cd->buttons.size());
|
||||||
|
|
||||||
|
ControllerData::Button button;
|
||||||
|
button.id = key;
|
||||||
|
cd->buttons.push_back(std::move(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heuristic borrowed from Dolphin's evdev controller interface - ignore bogus devices
|
||||||
|
// which do have less than 2 axises and less than 8 buttons.
|
||||||
|
if (cd->axises.size() < 2 && cd->buttons.size() < 8)
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Ignoring device %s due to heuristic", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevControllerInterface::HandleControllerEvents(ControllerData* cd)
|
||||||
|
{
|
||||||
|
struct input_event ev;
|
||||||
|
while (libevdev_next_event(cd->obj, LIBEVDEV_READ_FLAG_NORMAL, &ev) == 0)
|
||||||
|
{
|
||||||
|
switch (ev.type)
|
||||||
|
{
|
||||||
|
case EV_KEY:
|
||||||
|
{
|
||||||
|
// auto-repeat
|
||||||
|
if (ev.value == 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const bool pressed = (ev.value == 1);
|
||||||
|
Log_DevPrintf("Key %d %s", ev.code, pressed ? "pressed" : "unpressed");
|
||||||
|
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(cd->buttons.size()); i++)
|
||||||
|
{
|
||||||
|
if (cd->buttons[i].id == ev.code)
|
||||||
|
{
|
||||||
|
HandleButtonEvent(cd, i, ev.code, pressed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevControllerInterface::PollEvents()
|
||||||
|
{
|
||||||
|
if (m_controllers.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct pollfd* fds = static_cast<struct pollfd*>(alloca(sizeof(struct pollfd) * m_controllers.size()));
|
||||||
|
for (size_t i = 0; i < m_controllers.size(); i++)
|
||||||
|
{
|
||||||
|
fds[i].events = POLLIN;
|
||||||
|
fds[i].fd = m_controllers[i].fd;
|
||||||
|
fds[i].revents = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll(fds, static_cast<int>(m_controllers.size()), 0) <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < m_controllers.size(); i++)
|
||||||
|
{
|
||||||
|
if (fds[i].revents & POLLIN)
|
||||||
|
HandleControllerEvents(&m_controllers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevControllerInterface::ClearBindings()
|
||||||
|
{
|
||||||
|
for (ControllerData& cd : m_controllers)
|
||||||
|
{
|
||||||
|
for (ControllerData::Button& btn : cd.buttons)
|
||||||
|
{
|
||||||
|
btn.callback = {};
|
||||||
|
btn.axis_callback = {};
|
||||||
|
}
|
||||||
|
for (ControllerData::Axis& axis : cd.axises)
|
||||||
|
{
|
||||||
|
axis.callback = {};
|
||||||
|
axis.button_callback = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::BindControllerAxis(int controller_index, int axis_number, AxisSide axis_side,
|
||||||
|
AxisCallback callback)
|
||||||
|
{
|
||||||
|
ControllerData* cd = GetControllerById(controller_index);
|
||||||
|
if (!cd || static_cast<u32>(axis_number) >= cd->axises.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cd->axises[axis_number].callback[axis_side] = std::move(callback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::BindControllerButton(int controller_index, int button_number, ButtonCallback callback)
|
||||||
|
{
|
||||||
|
ControllerData* cd = GetControllerById(controller_index);
|
||||||
|
if (!cd || static_cast<u32>(button_number) >= cd->buttons.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cd->buttons[button_number].callback = std::move(callback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
|
||||||
|
ButtonCallback callback)
|
||||||
|
{
|
||||||
|
ControllerData* cd = GetControllerById(controller_index);
|
||||||
|
if (!cd || static_cast<u32>(axis_number) >= cd->axises.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cd->axises[axis_number].button_callback[BoolToUInt8(direction)] = std::move(callback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::BindControllerHatToButton(int controller_index, int hat_number,
|
||||||
|
std::string_view hat_position, ButtonCallback callback)
|
||||||
|
{
|
||||||
|
// Hats don't exist in XInput
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number,
|
||||||
|
AxisCallback callback)
|
||||||
|
{
|
||||||
|
ControllerData* cd = GetControllerById(controller_index);
|
||||||
|
if (!cd || static_cast<u32>(button_number) >= cd->buttons.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cd->buttons[button_number].axis_callback = std::move(callback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::HandleAxisEvent(ControllerData* cd, u32 axis, s32 value)
|
||||||
|
{
|
||||||
|
const float f_value = static_cast<float>(value) / (value < 0 ? 32768.0f : 32767.0f);
|
||||||
|
Log_DevPrintf("controller %u axis %u %d %f", cd->controller_id, axis, value, f_value);
|
||||||
|
|
||||||
|
if (DoEventHook(Hook::Type::Axis, cd->controller_id, axis, f_value))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const AxisCallback& cb = cd->axises[axis].callback[AxisSide::Full];
|
||||||
|
if (cb)
|
||||||
|
{
|
||||||
|
cb(f_value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the other direction to false so large movements don't leave the opposite on
|
||||||
|
const bool outside_deadzone = (std::abs(f_value) >= cd->deadzone);
|
||||||
|
const bool positive = (f_value >= 0.0f);
|
||||||
|
const ButtonCallback& other_button_cb = cd->axises[axis].button_callback[BoolToUInt8(!positive)];
|
||||||
|
const ButtonCallback& button_cb = cd->axises[axis].button_callback[BoolToUInt8(positive)];
|
||||||
|
if (button_cb)
|
||||||
|
{
|
||||||
|
button_cb(outside_deadzone);
|
||||||
|
if (other_button_cb)
|
||||||
|
other_button_cb(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (other_button_cb)
|
||||||
|
{
|
||||||
|
other_button_cb(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static FrontendCommon::ControllerNavigationButton MapEventButtonToNavigationButton(int button_id)
|
||||||
|
{
|
||||||
|
switch (button_id)
|
||||||
|
{
|
||||||
|
case BTN_A:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::Activate;
|
||||||
|
case BTN_B:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::Cancel;
|
||||||
|
case BTN_TL:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::LeftShoulder;
|
||||||
|
case BTN_TR:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::RightShoulder;
|
||||||
|
case BTN_DPAD_LEFT:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::DPadLeft;
|
||||||
|
case BTN_DPAD_RIGHT:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::DPadRight;
|
||||||
|
case BTN_DPAD_UP:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::DPadUp;
|
||||||
|
case BTN_DPAD_DOWN:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::DPadDown;
|
||||||
|
default:
|
||||||
|
return FrontendCommon::ControllerNavigationButton::Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::HandleButtonEvent(ControllerData* cd, u32 button, int button_id, bool pressed)
|
||||||
|
{
|
||||||
|
Log_DevPrintf("controller %d button %u %s", cd->controller_id, button, pressed ? "pressed" : "released");
|
||||||
|
|
||||||
|
if (DoEventHook(Hook::Type::Button, cd->controller_id, button, pressed ? 1.0f : 0.0f))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const FrontendCommon::ControllerNavigationButton nav_button = MapEventButtonToNavigationButton(button_id);
|
||||||
|
if (nav_button < FrontendCommon::ControllerNavigationButton::Count &&
|
||||||
|
m_host_interface->SetControllerNavigationButtonState(nav_button, pressed))
|
||||||
|
{
|
||||||
|
// UI consumed the event
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonCallback& cb = cd->buttons[button].callback;
|
||||||
|
if (cb)
|
||||||
|
{
|
||||||
|
cb(pressed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume a half-axis, i.e. in 0..1 range
|
||||||
|
const AxisCallback& axis_cb = cd->buttons[button].axis_callback;
|
||||||
|
if (axis_cb)
|
||||||
|
{
|
||||||
|
axis_cb(pressed ? 1.0f : 0.0f);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 EvdevControllerInterface::GetControllerRumbleMotorCount(int controller_index)
|
||||||
|
{
|
||||||
|
ControllerData* cd = GetControllerById(controller_index);
|
||||||
|
return cd ? cd->num_motors : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevControllerInterface::SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors)
|
||||||
|
{
|
||||||
|
ControllerData* cd = GetControllerById(controller_index);
|
||||||
|
if (!cd)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* XINPUT_VIBRATION vib;
|
||||||
|
vib.wLeftMotorSpeed = static_cast<u16>(strengths[0] * 65535.0f);
|
||||||
|
vib.wRightMotorSpeed = static_cast<u16>(strengths[1] * 65535.0f);
|
||||||
|
m_xinput_set_state(static_cast<u32>(controller_index), &vib);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevControllerInterface::SetControllerDeadzone(int controller_index, float size /* = 0.25f */)
|
||||||
|
{
|
||||||
|
ControllerData* cd = GetControllerById(controller_index);
|
||||||
|
if (!cd)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cd->deadzone = std::clamp(std::abs(size), 0.01f, 0.99f);
|
||||||
|
Log_InfoPrintf("Controller %d deadzone size set to %f", controller_index, cd->deadzone);
|
||||||
|
return true;
|
||||||
|
}
|
97
src/frontend-common/evdev_controller_interface.h
Normal file
97
src/frontend-common/evdev_controller_interface.h
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#pragma once
|
||||||
|
#include "controller_interface.h"
|
||||||
|
#include "core/types.h"
|
||||||
|
#include <libevdev/libevdev.h>
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class EvdevControllerInterface final : public ControllerInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EvdevControllerInterface();
|
||||||
|
~EvdevControllerInterface() override;
|
||||||
|
|
||||||
|
Backend GetBackend() const override;
|
||||||
|
bool Initialize(CommonHostInterface* host_interface) override;
|
||||||
|
void Shutdown() override;
|
||||||
|
|
||||||
|
// Removes all bindings. Call before setting new bindings.
|
||||||
|
void ClearBindings() override;
|
||||||
|
|
||||||
|
// Binding to events. If a binding for this axis/button already exists, returns false.
|
||||||
|
bool BindControllerAxis(int controller_index, int axis_number, AxisSide axis_side, AxisCallback callback) override;
|
||||||
|
bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override;
|
||||||
|
bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
|
||||||
|
ButtonCallback callback) override;
|
||||||
|
bool BindControllerHatToButton(int controller_index, int hat_number, std::string_view hat_position,
|
||||||
|
ButtonCallback callback) override;
|
||||||
|
bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override;
|
||||||
|
|
||||||
|
// Changing rumble strength.
|
||||||
|
u32 GetControllerRumbleMotorCount(int controller_index) override;
|
||||||
|
void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) override;
|
||||||
|
|
||||||
|
// Set deadzone that will be applied on axis-to-button mappings
|
||||||
|
bool SetControllerDeadzone(int controller_index, float size = 0.25f) override;
|
||||||
|
|
||||||
|
void PollEvents() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Axis : u32
|
||||||
|
{
|
||||||
|
LeftX,
|
||||||
|
LeftY,
|
||||||
|
RightX,
|
||||||
|
RightY,
|
||||||
|
LeftTrigger,
|
||||||
|
RightTrigger
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ControllerData
|
||||||
|
{
|
||||||
|
ControllerData(int fd_, struct libevdev* obj_);
|
||||||
|
ControllerData(const ControllerData&) = delete;
|
||||||
|
ControllerData(ControllerData&& move);
|
||||||
|
~ControllerData();
|
||||||
|
|
||||||
|
ControllerData& operator=(const ControllerData&) = delete;
|
||||||
|
ControllerData& operator=(ControllerData&& move);
|
||||||
|
|
||||||
|
struct libevdev* obj = nullptr;
|
||||||
|
int fd = -1;
|
||||||
|
int controller_id = 0;
|
||||||
|
u32 num_motors = 0;
|
||||||
|
|
||||||
|
float deadzone = 0.25f;
|
||||||
|
|
||||||
|
struct Axis
|
||||||
|
{
|
||||||
|
u32 id;
|
||||||
|
std::array<AxisCallback, 3> callback;
|
||||||
|
std::array<ButtonCallback, 2> button_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Button
|
||||||
|
{
|
||||||
|
u32 id;
|
||||||
|
ButtonCallback callback;
|
||||||
|
AxisCallback axis_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Axis> axises;
|
||||||
|
std::vector<Button> buttons;
|
||||||
|
};
|
||||||
|
|
||||||
|
ControllerData* GetControllerById(int id);
|
||||||
|
bool InitializeController(int index, ControllerData* cd);
|
||||||
|
void HandleControllerEvents(ControllerData* cd);
|
||||||
|
bool HandleAxisEvent(ControllerData* cd, u32 axis, s32 value);
|
||||||
|
bool HandleButtonEvent(ControllerData* cd, u32 button, int button_id, bool pressed);
|
||||||
|
|
||||||
|
std::vector<ControllerData> m_controllers;
|
||||||
|
|
||||||
|
std::mutex m_event_intercept_mutex;
|
||||||
|
Hook::Callback m_event_intercept_callback;
|
||||||
|
};
|
Loading…
Reference in a new issue