EvdevInputSource: Migrate to new abstractions
This commit is contained in:
parent
6df7d9acda
commit
bfafa95f65
|
@ -107,8 +107,8 @@ if(USE_EVDEV)
|
||||||
target_include_directories(frontend-common PRIVATE ${LIBEVDEV_INCLUDE_DIRS})
|
target_include_directories(frontend-common PRIVATE ${LIBEVDEV_INCLUDE_DIRS})
|
||||||
target_link_libraries(frontend-common PRIVATE ${LIBEVDEV_LIBRARIES})
|
target_link_libraries(frontend-common PRIVATE ${LIBEVDEV_LIBRARIES})
|
||||||
target_sources(frontend-common PRIVATE
|
target_sources(frontend-common PRIVATE
|
||||||
evdev_controller_interface.cpp
|
evdev_input_source.cpp
|
||||||
evdev_controller_interface.h
|
evdev_input_source.h
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,6 @@ void CommonHost::Initialize()
|
||||||
{
|
{
|
||||||
// This will call back to Host::LoadSettings() -> ReloadSources().
|
// This will call back to Host::LoadSettings() -> ReloadSources().
|
||||||
System::LoadSettings(false);
|
System::LoadSettings(false);
|
||||||
UpdateLogSettings();
|
|
||||||
|
|
||||||
#ifdef WITH_CHEEVOS
|
#ifdef WITH_CHEEVOS
|
||||||
#ifdef WITH_RAINTEGRATION
|
#ifdef WITH_RAINTEGRATION
|
||||||
|
@ -347,6 +346,7 @@ void CommonHost::SetDefaultHotkeyBindings(SettingsInterface& si)
|
||||||
|
|
||||||
void CommonHost::LoadSettings(SettingsInterface& si, std::unique_lock<std::mutex>& lock)
|
void CommonHost::LoadSettings(SettingsInterface& si, std::unique_lock<std::mutex>& lock)
|
||||||
{
|
{
|
||||||
|
UpdateLogSettings();
|
||||||
InputManager::ReloadSources(si, lock);
|
InputManager::ReloadSources(si, lock);
|
||||||
InputManager::ReloadBindings(si, *Host::GetSettingsInterfaceForBindings());
|
InputManager::ReloadBindings(si, *Host::GetSettingsInterfaceForBindings());
|
||||||
|
|
||||||
|
|
|
@ -1,447 +0,0 @@
|
||||||
#include "evdev_controller_interface.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/file_system.h"
|
|
||||||
#include "common/log.h"
|
|
||||||
#include "core/controller.h"
|
|
||||||
#include "core/system.h"
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <alloca.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
|
|
||||||
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);
|
|
||||||
data.controller_id = static_cast<int>(m_controllers.size());
|
|
||||||
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),
|
|
||||||
axes(std::move(move.axes)), 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;
|
|
||||||
axes = std::move(move.axes);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (u32 axis = 0; axis <= ABS_TOOL_WIDTH; axis++)
|
|
||||||
{
|
|
||||||
if (!libevdev_has_event_code(cd->obj, EV_ABS, axis))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const s32 min = libevdev_get_abs_minimum(cd->obj, axis);
|
|
||||||
const s32 max = libevdev_get_abs_maximum(cd->obj, axis);
|
|
||||||
const char* axis_name = libevdev_event_code_get_name(EV_ABS, axis);
|
|
||||||
Log_DevPrintf("Axis %u: %s -> Axis %zu [%d-%d]", axis, axis_name ? axis_name : "null", cd->axes.size(), min, max);
|
|
||||||
|
|
||||||
ControllerData::Axis ad;
|
|
||||||
ad.id = axis;
|
|
||||||
ad.min = min;
|
|
||||||
ad.range = max - min;
|
|
||||||
cd->axes.push_back(std::move(ad));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Heuristic borrowed from Dolphin's evdev controller interface - ignore bogus devices
|
|
||||||
// which do have less than 2 axes and less than 8 buttons.
|
|
||||||
if (cd->axes.size() < 2 && cd->buttons.size() < 8)
|
|
||||||
{
|
|
||||||
Log_VerbosePrintf("Ignoring device %s with %zu axes and %zu buttons due to heuristic", name, cd->axes.size(),
|
|
||||||
cd->buttons.size());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log_InfoPrintf("Controller %d -> %s with %zu axes and %zu buttons", cd->controller_id, name, cd->axes.size(),
|
|
||||||
cd->buttons.size());
|
|
||||||
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;
|
|
||||||
|
|
||||||
case EV_ABS:
|
|
||||||
{
|
|
||||||
// axis
|
|
||||||
Log_DebugPrintf("Axis %u %d", ev.code, ev.value);
|
|
||||||
|
|
||||||
for (u32 i = 0; i < static_cast<u32>(cd->axes.size()); i++)
|
|
||||||
{
|
|
||||||
if (cd->axes[i].id == ev.code)
|
|
||||||
{
|
|
||||||
HandleAxisEvent(cd, i, ev.value);
|
|
||||||
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.axes)
|
|
||||||
{
|
|
||||||
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->axes.size())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
cd->axes[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->axes.size())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
cd->axes[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 ControllerData::Axis& ad = cd->axes[axis];
|
|
||||||
float f_value = (static_cast<float>(value - ad.min) / static_cast<float>(ad.range));
|
|
||||||
if (ad.min < 0)
|
|
||||||
f_value = (f_value * 2.0f) - 1.0f;
|
|
||||||
|
|
||||||
Log_DevPrintf("controller %u axis %u %d %f range %d", cd->controller_id, axis, value, f_value, ad.range);
|
|
||||||
|
|
||||||
if (DoEventHook(Hook::Type::Axis, cd->controller_id, axis, f_value))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const AxisCallback& cb = ad.callback[AxisSide::Full];
|
|
||||||
if (cb)
|
|
||||||
{
|
|
||||||
cb(f_value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const AxisCallback& positive_cb = ad.callback[AxisSide::Positive];
|
|
||||||
const AxisCallback& negative_cb = ad.callback[AxisSide::Negative];
|
|
||||||
if (positive_cb || negative_cb)
|
|
||||||
{
|
|
||||||
if (positive_cb)
|
|
||||||
positive_cb((f_value < 0.0f) ? 0.0f : f_value);
|
|
||||||
if (negative_cb)
|
|
||||||
negative_cb((f_value >= 0.0f) ? 0.0f : -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 = ad.button_callback[BoolToUInt8(!positive)];
|
|
||||||
const ButtonCallback& button_cb = ad.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);
|
|
||||||
|
|
||||||
if (m_host_interface->IsControllerNavigationActive())
|
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,103 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "input_source.h"
|
|
||||||
#include "core/types.h"
|
|
||||||
#include <array>
|
|
||||||
#include <functional>
|
|
||||||
#include <libevdev/libevdev.h>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
|
|
||||||
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;
|
|
||||||
s32 min;
|
|
||||||
s32 range;
|
|
||||||
std::array<AxisCallback, 3> callback;
|
|
||||||
std::array<ButtonCallback, 2> button_callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Button
|
|
||||||
{
|
|
||||||
u32 id;
|
|
||||||
ButtonCallback callback;
|
|
||||||
AxisCallback axis_callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Axis> axes;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -0,0 +1,534 @@
|
||||||
|
#include "evdev_input_source.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "core/host.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
|
#include "input_manager.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <tuple>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Log_SetChannel(EvdevInputSource);
|
||||||
|
|
||||||
|
static GenericInputBinding GetGenericBindingForButton(int button_id)
|
||||||
|
{
|
||||||
|
switch (button_id)
|
||||||
|
{
|
||||||
|
case BTN_A:
|
||||||
|
return GenericInputBinding::Cross;
|
||||||
|
case BTN_B:
|
||||||
|
return GenericInputBinding::Circle;
|
||||||
|
case BTN_X:
|
||||||
|
return GenericInputBinding::Square;
|
||||||
|
case BTN_Y:
|
||||||
|
return GenericInputBinding::Triangle;
|
||||||
|
case BTN_SELECT:
|
||||||
|
return GenericInputBinding::Select;
|
||||||
|
case BTN_START:
|
||||||
|
return GenericInputBinding::Start;
|
||||||
|
case BTN_MODE:
|
||||||
|
return GenericInputBinding::System;
|
||||||
|
case BTN_TL:
|
||||||
|
return GenericInputBinding::L1;
|
||||||
|
case BTN_TR:
|
||||||
|
return GenericInputBinding::R1;
|
||||||
|
case BTN_TL2:
|
||||||
|
return GenericInputBinding::L2;
|
||||||
|
case BTN_TR2:
|
||||||
|
return GenericInputBinding::R2;
|
||||||
|
case BTN_THUMBL:
|
||||||
|
return GenericInputBinding::L3;
|
||||||
|
case BTN_THUMBR:
|
||||||
|
return GenericInputBinding::R3;
|
||||||
|
case BTN_DPAD_LEFT:
|
||||||
|
return GenericInputBinding::DPadLeft;
|
||||||
|
case BTN_DPAD_RIGHT:
|
||||||
|
return GenericInputBinding::DPadRight;
|
||||||
|
case BTN_DPAD_UP:
|
||||||
|
return GenericInputBinding::DPadUp;
|
||||||
|
case BTN_DPAD_DOWN:
|
||||||
|
return GenericInputBinding::DPadDown;
|
||||||
|
default:
|
||||||
|
return GenericInputBinding::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::tuple<GenericInputBinding, GenericInputBinding> GetGenericBindingForAxis(u32 axis)
|
||||||
|
{
|
||||||
|
switch (axis)
|
||||||
|
{
|
||||||
|
case ABS_X:
|
||||||
|
return std::make_tuple(GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight);
|
||||||
|
case ABS_Y:
|
||||||
|
return std::make_tuple(GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown);
|
||||||
|
case ABS_RX:
|
||||||
|
return std::make_tuple(GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight);
|
||||||
|
case ABS_RY:
|
||||||
|
return std::make_tuple(GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown);
|
||||||
|
default:
|
||||||
|
return std::make_tuple(GenericInputBinding::Unknown, GenericInputBinding::Unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsFullAxis(u32 axis)
|
||||||
|
{
|
||||||
|
// ugh, so min isn't necessarily zero for full axes... :/
|
||||||
|
return (axis >= ABS_X && axis <= ABS_RZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevInputSource::EvdevInputSource() = default;
|
||||||
|
|
||||||
|
EvdevInputSource::~EvdevInputSource() = default;
|
||||||
|
|
||||||
|
bool EvdevInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (errno == ENOENT)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
data.controller_id = static_cast<int>(m_controllers.size());
|
||||||
|
if (InitializeController(index, &data))
|
||||||
|
m_controllers.push_back(std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevInputSource::Shutdown()
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevInputSource::ControllerData::ControllerData(int fd_, struct libevdev* obj_) : obj(obj_), fd(fd_) {}
|
||||||
|
|
||||||
|
EvdevInputSource::ControllerData::ControllerData(ControllerData&& move)
|
||||||
|
: obj(move.obj), fd(move.fd), controller_id(move.controller_id), num_motors(move.num_motors), deadzone(move.deadzone),
|
||||||
|
uniq(std::move(move.uniq)), name(std::move(move.name)), axes(std::move(move.axes)), buttons(std::move(move.buttons))
|
||||||
|
{
|
||||||
|
move.obj = nullptr;
|
||||||
|
move.fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevInputSource::ControllerData::~ControllerData()
|
||||||
|
{
|
||||||
|
if (obj)
|
||||||
|
libevdev_free(obj);
|
||||||
|
if (fd >= 0)
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevInputSource::ControllerData& EvdevInputSource::ControllerData::operator=(EvdevInputSource::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;
|
||||||
|
uniq = std::move(move.uniq);
|
||||||
|
name = std::move(move.name);
|
||||||
|
axes = std::move(move.axes);
|
||||||
|
buttons = std::move(move.buttons);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevInputSource::ControllerData* EvdevInputSource::GetControllerById(int id)
|
||||||
|
{
|
||||||
|
for (ControllerData& cd : m_controllers)
|
||||||
|
{
|
||||||
|
if (cd.controller_id == id)
|
||||||
|
return &cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevInputSource::ControllerData* EvdevInputSource::GetControllerByUniq(const std::string_view& uniq)
|
||||||
|
{
|
||||||
|
for (ControllerData& cd : m_controllers)
|
||||||
|
{
|
||||||
|
if (uniq == cd.uniq)
|
||||||
|
return &cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevInputSource::InitializeController(int index, ControllerData* cd)
|
||||||
|
{
|
||||||
|
const char* name = libevdev_get_name(cd->obj);
|
||||||
|
const char* uniq = libevdev_get_uniq(cd->obj);
|
||||||
|
cd->name = name ? name : "Unknown";
|
||||||
|
cd->uniq = uniq ? fmt::format("Evdev-{}", uniq) : fmt::format("Evdev-Unknown{}", index);
|
||||||
|
|
||||||
|
// Sanitize the name a bit just in case..
|
||||||
|
for (size_t i = 6; i < cd->uniq.length(); i++)
|
||||||
|
{
|
||||||
|
const char ch = cd->uniq[i];
|
||||||
|
if (!(ch >= 'a' && ch <= 'z') && !(ch >= 'A' && ch <= 'Z') && !(ch >= '0' && ch <= '9') && ch != '_')
|
||||||
|
cd->uniq[i] = '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_DevPrintf("Input %d device name: \"%s\" ('%s')", index, cd->name.c_str(), cd->uniq.c_str());
|
||||||
|
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));
|
||||||
|
|
||||||
|
bool has_dpad = false;
|
||||||
|
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_DebugPrintf("Key %d: %s", key, button_name ? button_name : "null");
|
||||||
|
|
||||||
|
ControllerData::Button button;
|
||||||
|
button.name = button_name ? std::string(button_name) : fmt::format("Button{}", key);
|
||||||
|
button.id = key;
|
||||||
|
button.generic = GetGenericBindingForButton(key);
|
||||||
|
cd->buttons.push_back(std::move(button));
|
||||||
|
|
||||||
|
if (key == BTN_DPAD_LEFT || key == BTN_DPAD_RIGHT || key == BTN_DPAD_UP || key == BTN_DPAD_DOWN)
|
||||||
|
has_dpad = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prelookup axes to get the range of them.
|
||||||
|
for (u32 axis = 0; axis <= ABS_TOOL_WIDTH; axis++)
|
||||||
|
{
|
||||||
|
if (!libevdev_has_event_code(cd->obj, EV_ABS, axis))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const s32 min = libevdev_get_abs_minimum(cd->obj, axis);
|
||||||
|
const s32 max = libevdev_get_abs_maximum(cd->obj, axis);
|
||||||
|
const char* axis_name = libevdev_event_code_get_name(EV_ABS, axis);
|
||||||
|
Log_DebugPrintf("Axis %u: %s [%d-%d]", axis, axis_name ? axis_name : "null", min, max);
|
||||||
|
|
||||||
|
ControllerData::Axis ad;
|
||||||
|
ad.name = axis_name ? std::string(axis_name) : fmt::format("Button{}", axis);
|
||||||
|
ad.id = axis;
|
||||||
|
ad.min = min;
|
||||||
|
ad.range = max - min;
|
||||||
|
ad.neg_button = 0;
|
||||||
|
ad.pos_button = 0;
|
||||||
|
std::tie(ad.neg_generic, ad.pos_generic) = GetGenericBindingForAxis(axis);
|
||||||
|
|
||||||
|
if (!has_dpad)
|
||||||
|
{
|
||||||
|
// map hat -> dpad
|
||||||
|
if (axis == ABS_HAT0X)
|
||||||
|
{
|
||||||
|
Log_VerbosePrintf("Redirecting HAT0X to DPad left/right");
|
||||||
|
ad.neg_button = BTN_DPAD_LEFT;
|
||||||
|
ad.pos_button = BTN_DPAD_RIGHT;
|
||||||
|
cd->buttons.emplace_back("BTN_DPAD_LEFT", BTN_DPAD_LEFT, GenericInputBinding::DPadLeft);
|
||||||
|
cd->buttons.emplace_back("BTN_DPAD_RIGHT", BTN_DPAD_RIGHT, GenericInputBinding::DPadRight);
|
||||||
|
}
|
||||||
|
else if (axis == ABS_HAT0Y)
|
||||||
|
{
|
||||||
|
Log_VerbosePrintf("Redirecting HAT0Y to DPad up/down");
|
||||||
|
ad.neg_button = BTN_DPAD_UP;
|
||||||
|
ad.pos_button = BTN_DPAD_DOWN;
|
||||||
|
cd->buttons.emplace_back("BTN_DPAD_UP", BTN_DPAD_UP, GenericInputBinding::DPadUp);
|
||||||
|
cd->buttons.emplace_back("BTN_DPAD_DOWN", BTN_DPAD_DOWN, GenericInputBinding::DPadDown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cd->axes.push_back(std::move(ad));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heuristic borrowed from Dolphin's evdev controller interface - ignore bogus devices
|
||||||
|
// which do have less than 2 axes and less than 8 buttons. Key count of 80 is probably a keyboard.
|
||||||
|
// Axes with no buttons is probably a motion sensor.
|
||||||
|
if ((cd->axes.size() < 2 && cd->buttons.size() < 8) || cd->buttons.size() > 80 || (cd->axes.size() >= 6 && cd->buttons.empty()))
|
||||||
|
{
|
||||||
|
Log_VerbosePrintf("Ignoring device %s with %zu axes and %zu buttons due to heuristic", name, cd->axes.size(),
|
||||||
|
cd->buttons.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_InfoPrintf("Controller %d -> %s with %zu axes and %zu buttons", cd->controller_id, name, cd->axes.size(),
|
||||||
|
cd->buttons.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> EvdevInputSource::EnumerateDevices()
|
||||||
|
{
|
||||||
|
std::vector<std::pair<std::string, std::string>> ret;
|
||||||
|
for (const ControllerData& cd : m_controllers)
|
||||||
|
ret.emplace_back(cd.uniq, cd.name);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<InputBindingKey> EvdevInputSource::ParseKeyString(const std::string_view& device,
|
||||||
|
const std::string_view& binding)
|
||||||
|
{
|
||||||
|
if (!StringUtil::StartsWith(device, "Evdev-") || binding.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
const ControllerData* cd = GetControllerByUniq(device);
|
||||||
|
if (!cd)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
InputBindingKey key = {};
|
||||||
|
key.source_type = InputSourceType::Evdev;
|
||||||
|
key.source_index = static_cast<u32>(cd->controller_id);
|
||||||
|
|
||||||
|
if (binding[0] == '-' || binding[0] == '+')
|
||||||
|
{
|
||||||
|
const std::string_view abinding(binding.substr(1));
|
||||||
|
for (const ControllerData::Axis& axis : cd->axes)
|
||||||
|
{
|
||||||
|
if (abinding == axis.name)
|
||||||
|
{
|
||||||
|
key.source_subtype = InputSubclass::ControllerAxis;
|
||||||
|
key.negative = (binding[0] == '-');
|
||||||
|
key.data = axis.id;
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const ControllerData::Button& button : cd->buttons)
|
||||||
|
{
|
||||||
|
if (binding == button.name)
|
||||||
|
{
|
||||||
|
key.source_subtype = InputSubclass::ControllerButton;
|
||||||
|
key.data = button.id;
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EvdevInputSource::ConvertKeyToString(InputBindingKey key)
|
||||||
|
{
|
||||||
|
std::string ret;
|
||||||
|
|
||||||
|
if (key.source_type == InputSourceType::Evdev)
|
||||||
|
{
|
||||||
|
const ControllerData* cd = GetControllerById(key.source_index);
|
||||||
|
if (cd)
|
||||||
|
{
|
||||||
|
if (key.source_subtype == InputSubclass::ControllerAxis)
|
||||||
|
{
|
||||||
|
for (const ControllerData::Axis& axis : cd->axes)
|
||||||
|
{
|
||||||
|
if (static_cast<u32>(axis.id) == key.data)
|
||||||
|
{
|
||||||
|
ret = fmt::format("{}/{}{}", cd->uniq, key.negative ? "-" : "+", axis.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (key.source_subtype == InputSubclass::ControllerButton)
|
||||||
|
{
|
||||||
|
for (const ControllerData::Button& button : cd->buttons)
|
||||||
|
{
|
||||||
|
if (static_cast<u32>(button.id) == key.data)
|
||||||
|
{
|
||||||
|
ret = fmt::format("{}/{}", cd->uniq, button.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping)
|
||||||
|
{
|
||||||
|
const ControllerData* cd = GetControllerByUniq(device);
|
||||||
|
if (!cd)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const ControllerData::Button& button : cd->buttons)
|
||||||
|
{
|
||||||
|
if (button.generic != GenericInputBinding::Unknown)
|
||||||
|
mapping->emplace_back(button.generic, fmt::format("{}/{}", cd->uniq, button.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ControllerData::Axis& axis : cd->axes)
|
||||||
|
{
|
||||||
|
if (axis.neg_generic != GenericInputBinding::Unknown)
|
||||||
|
mapping->emplace_back(axis.neg_generic, fmt::format("{}/-{}", cd->uniq, axis.name));
|
||||||
|
if (axis.pos_generic != GenericInputBinding::Unknown)
|
||||||
|
mapping->emplace_back(axis.pos_generic, fmt::format("{}/+{}", cd->uniq, axis.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<InputBindingKey> EvdevInputSource::EnumerateMotors()
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevInputSource::UpdateMotorState(InputBindingKey key, float intensity)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
|
||||||
|
float small_intensity)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevInputSource::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 EvdevInputSource::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_DebugPrintf("%s %s Key %d %s", cd->uniq.c_str(), cd->name.c_str(), ev.code, pressed ? "pressed" : "unpressed");
|
||||||
|
InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::Evdev, cd->controller_id, ev.code),
|
||||||
|
pressed ? 1.0f : 0.0f, GetGenericBindingForButton(ev.code));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EV_ABS:
|
||||||
|
{
|
||||||
|
// axis
|
||||||
|
Log_DebugPrintf("%s %s Axis %u %d", cd->uniq.c_str(), cd->name.c_str(), ev.code, ev.value);
|
||||||
|
|
||||||
|
for (ControllerData::Axis& axis : cd->axes)
|
||||||
|
{
|
||||||
|
if (axis.id == ev.code)
|
||||||
|
{
|
||||||
|
const float norm_value = static_cast<float>(static_cast<s32>(ev.value) - static_cast<s32>(axis.min)) /
|
||||||
|
static_cast<float>(axis.range);
|
||||||
|
const float real_value = (axis.min < 0 || IsFullAxis(ev.code)) ? ((norm_value * 2.0f) - 1.0f) : norm_value;
|
||||||
|
|
||||||
|
// hat -> dpad mapping
|
||||||
|
static constexpr float MAPPING_DEADZONE = 0.5f;
|
||||||
|
if (axis.neg_button != 0)
|
||||||
|
{
|
||||||
|
if (real_value <= -MAPPING_DEADZONE && axis.last_value > -MAPPING_DEADZONE)
|
||||||
|
{
|
||||||
|
// gone negative
|
||||||
|
InputManager::InvokeEvents(
|
||||||
|
MakeGenericControllerButtonKey(InputSourceType::Evdev, cd->controller_id, axis.neg_button), 1.0f,
|
||||||
|
GetGenericBindingForButton(axis.neg_button));
|
||||||
|
}
|
||||||
|
else if (real_value > -MAPPING_DEADZONE && axis.last_value <= -MAPPING_DEADZONE)
|
||||||
|
{
|
||||||
|
// no longer negative
|
||||||
|
InputManager::InvokeEvents(
|
||||||
|
MakeGenericControllerButtonKey(InputSourceType::Evdev, cd->controller_id, axis.neg_button), 0.0f,
|
||||||
|
GetGenericBindingForButton(axis.neg_button));
|
||||||
|
}
|
||||||
|
else if (real_value >= MAPPING_DEADZONE && axis.last_value < MAPPING_DEADZONE)
|
||||||
|
{
|
||||||
|
// gone positive
|
||||||
|
InputManager::InvokeEvents(
|
||||||
|
MakeGenericControllerButtonKey(InputSourceType::Evdev, cd->controller_id, axis.pos_button), 1.0f,
|
||||||
|
GetGenericBindingForButton(axis.pos_button));
|
||||||
|
}
|
||||||
|
else if (real_value < MAPPING_DEADZONE && axis.last_value >= MAPPING_DEADZONE)
|
||||||
|
{
|
||||||
|
// no longer positive
|
||||||
|
InputManager::InvokeEvents(
|
||||||
|
MakeGenericControllerButtonKey(InputSourceType::Evdev, cd->controller_id, axis.pos_button), 0.0f,
|
||||||
|
GetGenericBindingForButton(axis.pos_button));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (axis.last_value != real_value)
|
||||||
|
{
|
||||||
|
const GenericInputBinding generic = (real_value < 0.0f) ? axis.neg_generic : axis.pos_generic;
|
||||||
|
InputManager::InvokeEvents(
|
||||||
|
MakeGenericControllerAxisKey(InputSourceType::Evdev, cd->controller_id, ev.code), real_value, generic);
|
||||||
|
}
|
||||||
|
|
||||||
|
axis.last_value = real_value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<InputSource> InputSource::CreateEvdevSource()
|
||||||
|
{
|
||||||
|
return std::make_unique<EvdevInputSource>();
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
#pragma once
|
||||||
|
#include "core/types.h"
|
||||||
|
#include "input_source.h"
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <libevdev/libevdev.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class EvdevInputSource final : public InputSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EvdevInputSource();
|
||||||
|
~EvdevInputSource() override;
|
||||||
|
|
||||||
|
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
|
||||||
|
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
|
||||||
|
void Shutdown() override;
|
||||||
|
|
||||||
|
void PollEvents() override;
|
||||||
|
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
|
||||||
|
std::vector<InputBindingKey> EnumerateMotors() override;
|
||||||
|
bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override;
|
||||||
|
void UpdateMotorState(InputBindingKey key, float intensity) override;
|
||||||
|
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
|
||||||
|
float small_intensity) override;
|
||||||
|
|
||||||
|
std::optional<InputBindingKey> ParseKeyString(const std::string_view& device,
|
||||||
|
const std::string_view& binding) override;
|
||||||
|
std::string ConvertKeyToString(InputBindingKey key) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
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
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
u32 id;
|
||||||
|
s32 min;
|
||||||
|
s32 range;
|
||||||
|
u32 neg_button;
|
||||||
|
u32 pos_button;
|
||||||
|
GenericInputBinding neg_generic;
|
||||||
|
GenericInputBinding pos_generic;
|
||||||
|
float last_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Button
|
||||||
|
{
|
||||||
|
Button() = default;
|
||||||
|
Button(std::string name_, u32 id_, GenericInputBinding generic_)
|
||||||
|
: name(std::move(name_)), id(id_), generic(generic_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
u32 id;
|
||||||
|
GenericInputBinding generic;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string uniq;
|
||||||
|
std::string name;
|
||||||
|
std::vector<Axis> axes;
|
||||||
|
std::vector<Button> buttons;
|
||||||
|
};
|
||||||
|
|
||||||
|
ControllerData* GetControllerById(int id);
|
||||||
|
ControllerData* GetControllerByUniq(const std::string_view& uniq);
|
||||||
|
bool InitializeController(int index, ControllerData* cd);
|
||||||
|
void HandleControllerEvents(ControllerData* cd);
|
||||||
|
|
||||||
|
std::vector<ControllerData> m_controllers;
|
||||||
|
};
|
|
@ -440,6 +440,9 @@ static std::array<const char*, static_cast<u32>(InputSourceType::Count)> s_input
|
||||||
#ifdef WITH_SDL2
|
#ifdef WITH_SDL2
|
||||||
"SDL",
|
"SDL",
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
"Evdev",
|
||||||
|
#endif
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
"Android",
|
"Android",
|
||||||
#endif
|
#endif
|
||||||
|
@ -1528,6 +1531,9 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock<std::mu
|
||||||
#ifdef WITH_SDL2
|
#ifdef WITH_SDL2
|
||||||
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource, true);
|
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource, true);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
UpdateInputSourceState(si, settings_lock, InputSourceType::Evdev, &InputSource::CreateEvdevSource, true);
|
||||||
|
#endif
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource, true);
|
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource, true);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -24,6 +24,9 @@ enum class InputSourceType : u32
|
||||||
#ifdef WITH_SDL2
|
#ifdef WITH_SDL2
|
||||||
SDL,
|
SDL,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
Evdev,
|
||||||
|
#endif
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
Android,
|
Android,
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -69,6 +69,9 @@ public:
|
||||||
#ifdef WITH_SDL2
|
#ifdef WITH_SDL2
|
||||||
static std::unique_ptr<InputSource> CreateSDLSource();
|
static std::unique_ptr<InputSource> CreateSDLSource();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_EVDEV
|
||||||
|
static std::unique_ptr<InputSource> CreateEvdevSource();
|
||||||
|
#endif
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
static std::unique_ptr<InputSource> CreateAndroidSource();
|
static std::unique_ptr<InputSource> CreateAndroidSource();
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue