2019-02-21 11:46:56 +00:00
|
|
|
#include "evdev.h"
|
2020-03-28 16:58:01 +00:00
|
|
|
#include "input/gamepad_device.h"
|
|
|
|
#include "oslib/oslib.h"
|
|
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <unistd.h>
|
2021-09-19 16:27:21 +00:00
|
|
|
#include <climits>
|
2019-02-21 11:46:56 +00:00
|
|
|
|
2020-05-11 13:59:30 +00:00
|
|
|
class DefaultEvdevInputMapping : public InputMapping
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DefaultEvdevInputMapping() {
|
|
|
|
name = "Default";
|
|
|
|
set_button(DC_BTN_START, BTN_START);
|
|
|
|
set_button(DC_BTN_A, BTN_SOUTH);
|
|
|
|
set_button(DC_BTN_B, BTN_EAST);
|
|
|
|
set_button(DC_BTN_X, BTN_WEST);
|
|
|
|
set_button(DC_BTN_Y, BTN_NORTH);
|
|
|
|
set_button(DC_BTN_C, BTN_C);
|
|
|
|
set_button(DC_BTN_Z, BTN_Z);
|
|
|
|
set_button(DC_DPAD_UP, BTN_DPAD_UP);
|
|
|
|
set_button(DC_DPAD_DOWN, BTN_DPAD_DOWN);
|
|
|
|
set_button(DC_DPAD_LEFT, BTN_DPAD_LEFT);
|
|
|
|
set_button(DC_DPAD_RIGHT, BTN_DPAD_RIGHT);
|
|
|
|
set_button(EMU_BTN_MENU, BTN_SELECT);
|
|
|
|
|
2021-09-19 16:27:21 +00:00
|
|
|
set_axis(DC_AXIS_LEFT, ABS_X, false);
|
|
|
|
set_axis(DC_AXIS_RIGHT, ABS_X, true);
|
|
|
|
set_axis(DC_AXIS_UP, ABS_Y, false);
|
|
|
|
set_axis(DC_AXIS_DOWN, ABS_Y, true);
|
|
|
|
set_axis(DC_AXIS_LT, ABS_Z, true);
|
|
|
|
set_axis(DC_AXIS_RT, ABS_RZ, true);
|
|
|
|
set_axis(DC_AXIS2_LEFT, ABS_RX, false);
|
|
|
|
set_axis(DC_AXIS2_RIGHT, ABS_RX, true);
|
|
|
|
set_axis(DC_AXIS_UP, ABS_RY, false);
|
|
|
|
set_axis(DC_AXIS_DOWN, ABS_RY, true);
|
2020-05-11 13:59:30 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-02-21 11:46:56 +00:00
|
|
|
class EvdevGamepadDevice : public GamepadDevice
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
EvdevGamepadDevice(int maple_port, const char *devnode, int fd, const char *mapping_file = NULL)
|
2021-03-11 11:27:00 +00:00
|
|
|
: GamepadDevice(maple_port, "evdev"), _fd(fd), _devnode(devnode), _rumble_effect_id(-1)
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
|
|
|
fcntl(fd, F_SETFL, O_NONBLOCK);
|
2019-03-29 16:19:18 +00:00
|
|
|
char buf[256] = "Unknown";
|
|
|
|
if (ioctl(fd, EVIOCGNAME(sizeof(buf) - 1), buf) < 0)
|
2019-02-21 11:46:56 +00:00
|
|
|
perror("evdev: ioctl(EVIOCGNAME)");
|
|
|
|
else
|
2019-06-30 19:06:46 +00:00
|
|
|
INFO_LOG(INPUT, "evdev: Opened device '%s'", buf);
|
2019-03-29 16:19:18 +00:00
|
|
|
_name = buf;
|
|
|
|
buf[0] = 0;
|
|
|
|
if (ioctl(fd, EVIOCGUNIQ(sizeof(buf) - 1), buf) == 0)
|
|
|
|
_unique_id = buf;
|
|
|
|
if (_unique_id.empty())
|
|
|
|
_unique_id = devnode;
|
|
|
|
|
2021-11-10 19:35:30 +00:00
|
|
|
if (!find_mapping())
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
|
|
|
#if defined(TARGET_PANDORA)
|
|
|
|
mapping_file = "controller_pandora.cfg";
|
|
|
|
#else
|
2019-03-29 16:19:18 +00:00
|
|
|
if (_name == "Microsoft X-Box 360 pad"
|
|
|
|
|| _name == "Xbox 360 Wireless Receiver"
|
|
|
|
|| _name == "Xbox 360 Wireless Receiver (XBOX)")
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
|
|
|
mapping_file = "controller_xpad.cfg";
|
|
|
|
}
|
2019-03-29 16:19:18 +00:00
|
|
|
else if (_name.find("Xbox Gamepad (userspace driver)") != std::string::npos)
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
|
|
|
mapping_file = "controller_xboxdrv.cfg";
|
|
|
|
}
|
2019-03-29 16:19:18 +00:00
|
|
|
else if (_name.find("keyboard") != std::string::npos
|
|
|
|
|| _name.find("Keyboard") != std::string::npos)
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
|
|
|
mapping_file = "keyboard.cfg";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mapping_file = "controller_generic.cfg";
|
|
|
|
}
|
|
|
|
#endif
|
2021-11-10 19:35:30 +00:00
|
|
|
if (find_mapping())
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
2019-06-30 19:06:46 +00:00
|
|
|
INFO_LOG(INPUT, "using default mapping '%s'", input_mapper->name.c_str());
|
2020-03-12 15:09:05 +00:00
|
|
|
input_mapper = std::make_shared<InputMapping>(*input_mapper);
|
2019-02-21 11:46:56 +00:00
|
|
|
}
|
|
|
|
else
|
2021-05-19 16:13:52 +00:00
|
|
|
input_mapper = getDefaultMapping();
|
2019-02-21 11:46:56 +00:00
|
|
|
input_mapper->name = _name + " mapping";
|
|
|
|
save_mapping();
|
|
|
|
}
|
|
|
|
else
|
2019-06-30 19:06:46 +00:00
|
|
|
INFO_LOG(INPUT, "using custom mapping '%s'", input_mapper->name.c_str());
|
2019-02-21 11:46:56 +00:00
|
|
|
}
|
2021-05-31 17:44:55 +00:00
|
|
|
~EvdevGamepadDevice() override
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
2019-06-30 19:06:46 +00:00
|
|
|
INFO_LOG(INPUT, "evdev: Device '%s' on port %d disconnected", _name.c_str(), maple_port());
|
2019-02-21 11:46:56 +00:00
|
|
|
close(_fd);
|
|
|
|
}
|
|
|
|
|
2021-06-07 18:53:31 +00:00
|
|
|
std::shared_ptr<InputMapping> getDefaultMapping() override {
|
2021-05-19 16:13:52 +00:00
|
|
|
return std::make_shared<DefaultEvdevInputMapping>();
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:44:55 +00:00
|
|
|
void rumble(float power, float inclination, u32 duration_ms) override
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
2019-02-22 18:23:03 +00:00
|
|
|
vib_inclination = inclination * power;
|
|
|
|
vib_stop_time = os_GetSeconds() + duration_ms / 1000.0;
|
2019-02-21 11:46:56 +00:00
|
|
|
|
2019-02-22 18:23:03 +00:00
|
|
|
do_rumble(power, duration_ms);
|
|
|
|
}
|
2021-05-31 17:44:55 +00:00
|
|
|
void update_rumble() override
|
2019-02-22 18:23:03 +00:00
|
|
|
{
|
|
|
|
if (vib_inclination > 0)
|
|
|
|
{
|
|
|
|
int rem_time = (vib_stop_time - os_GetSeconds()) * 1000;
|
|
|
|
if (rem_time <= 0)
|
|
|
|
vib_inclination = 0;
|
|
|
|
else
|
|
|
|
do_rumble(vib_inclination * rem_time, rem_time);
|
2019-02-21 11:46:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:44:55 +00:00
|
|
|
const char *get_button_name(u32 code) override
|
2020-11-20 21:10:14 +00:00
|
|
|
{
|
|
|
|
switch (code)
|
|
|
|
{
|
|
|
|
case BTN_START:
|
|
|
|
return "Start";
|
|
|
|
case BTN_SELECT:
|
|
|
|
return "Select";
|
|
|
|
case BTN_MODE:
|
|
|
|
return "Mode";
|
|
|
|
case BTN_NORTH:
|
|
|
|
return "North";
|
|
|
|
case BTN_SOUTH:
|
|
|
|
return "South";
|
|
|
|
case BTN_EAST:
|
|
|
|
return "East";
|
|
|
|
case BTN_WEST:
|
|
|
|
return "West";
|
|
|
|
case BTN_C:
|
|
|
|
return "C";
|
|
|
|
case BTN_Z:
|
|
|
|
return "Z";
|
|
|
|
case BTN_DPAD_UP:
|
|
|
|
return "DPad Up";
|
|
|
|
case BTN_DPAD_DOWN:
|
|
|
|
return "DPad Down";
|
|
|
|
case BTN_DPAD_LEFT:
|
|
|
|
return "DPad Left";
|
|
|
|
case BTN_DPAD_RIGHT:
|
|
|
|
return "DPad Right";
|
|
|
|
case BTN_TL:
|
|
|
|
return "Trigger L";
|
|
|
|
case BTN_TR:
|
|
|
|
return "Trigger R";
|
|
|
|
case BTN_TL2:
|
|
|
|
return "Trigger L2";
|
|
|
|
case BTN_TR2:
|
|
|
|
return "Trigger R2";
|
|
|
|
case BTN_THUMBL:
|
|
|
|
return "Thumb L";
|
|
|
|
case BTN_THUMBR:
|
|
|
|
return "Thumb R";
|
|
|
|
default:
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
2021-05-31 17:44:55 +00:00
|
|
|
const char *get_axis_name(u32 code) override
|
2020-11-20 21:10:14 +00:00
|
|
|
{
|
|
|
|
switch (code)
|
|
|
|
{
|
|
|
|
case ABS_X:
|
|
|
|
return "Abs X";
|
|
|
|
case ABS_Y:
|
|
|
|
return "Abs Y";
|
|
|
|
case ABS_Z:
|
|
|
|
return "Abs Z";
|
|
|
|
case ABS_RX:
|
|
|
|
return "Abs RX";
|
|
|
|
case ABS_RY:
|
|
|
|
return "Abs RY";
|
|
|
|
case ABS_RZ:
|
|
|
|
return "Abs RZ";
|
|
|
|
default:
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-21 13:49:27 +00:00
|
|
|
static std::shared_ptr<EvdevGamepadDevice> GetControllerForPort(int port)
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
2019-06-08 11:04:35 +00:00
|
|
|
for (auto& pair : evdev_gamepads)
|
2019-02-21 11:46:56 +00:00
|
|
|
if (pair.second->maple_port() == port)
|
|
|
|
return pair.second;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-02-21 13:49:27 +00:00
|
|
|
static std::shared_ptr<EvdevGamepadDevice> GetControllerForDevnode(const char *devnode)
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
|
|
|
auto it = evdev_gamepads.find(devnode);
|
|
|
|
if (it == evdev_gamepads.end())
|
|
|
|
return NULL;
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void PollDevices()
|
|
|
|
{
|
2019-06-08 11:04:35 +00:00
|
|
|
for (auto& pair : evdev_gamepads)
|
2019-02-21 11:46:56 +00:00
|
|
|
pair.second->read_input();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void CloseDevices()
|
|
|
|
{
|
|
|
|
while (!evdev_gamepads.empty())
|
2019-02-21 13:49:27 +00:00
|
|
|
RemoveDevice(evdev_gamepads.begin()->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void AddDevice(std::shared_ptr<EvdevGamepadDevice> gamepad)
|
|
|
|
{
|
|
|
|
evdev_gamepads[gamepad->_devnode] = gamepad;
|
|
|
|
GamepadDevice::Register(gamepad);
|
|
|
|
}
|
|
|
|
static void RemoveDevice(std::shared_ptr<EvdevGamepadDevice> gamepad)
|
|
|
|
{
|
|
|
|
evdev_gamepads.erase(gamepad->_devnode);
|
|
|
|
GamepadDevice::Unregister(gamepad);
|
2019-02-21 11:46:56 +00:00
|
|
|
}
|
|
|
|
|
2021-09-19 16:27:21 +00:00
|
|
|
private:
|
|
|
|
int get_axis_min_value(u32 axis)
|
|
|
|
{
|
|
|
|
auto it = axis_min_values.find(axis);
|
|
|
|
if (it == axis_min_values.end()) {
|
|
|
|
load_axis_min_max(axis);
|
|
|
|
it = axis_min_values.find(axis);
|
|
|
|
if (it == axis_min_values.end())
|
|
|
|
return INT_MIN;
|
|
|
|
}
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int get_axis_range(u32 axis)
|
|
|
|
{
|
|
|
|
auto it = axis_ranges.find(axis);
|
|
|
|
if (it == axis_ranges.end()) {
|
|
|
|
load_axis_min_max(axis);
|
|
|
|
it = axis_ranges.find(axis);
|
|
|
|
if (it == axis_ranges.end())
|
|
|
|
return UINT_MAX;
|
|
|
|
}
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
void load_axis_min_max(u32 axis)
|
2019-02-21 11:46:56 +00:00
|
|
|
{
|
|
|
|
struct input_absinfo abs;
|
|
|
|
if (ioctl(_fd, EVIOCGABS(axis), &abs))
|
|
|
|
{
|
|
|
|
perror("evdev: ioctl(EVIOCGABS)");
|
|
|
|
axis_ranges[axis] = 255;
|
|
|
|
axis_min_values[axis] = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
axis_min_values[axis] = abs.minimum;
|
|
|
|
axis_ranges[axis] = abs.maximum - abs.minimum;
|
2019-06-30 19:06:46 +00:00
|
|
|
DEBUG_LOG(INPUT, "evdev: range of axis %d is from %d to %d", axis, axis_min_values[axis], axis_min_values[axis] + axis_ranges[axis]);
|
2019-02-21 11:46:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void read_input()
|
|
|
|
{
|
2019-02-22 18:23:03 +00:00
|
|
|
update_rumble();
|
2019-02-21 11:46:56 +00:00
|
|
|
input_event ie;
|
|
|
|
|
|
|
|
while (read(_fd, &ie, sizeof(ie)) == sizeof(ie))
|
|
|
|
{
|
|
|
|
switch (ie.type)
|
|
|
|
{
|
|
|
|
case EV_KEY:
|
|
|
|
gamepad_btn_input(ie.code, ie.value != 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EV_ABS:
|
2021-09-19 16:27:21 +00:00
|
|
|
{
|
|
|
|
// TODO no way to distinguish between half and full axes
|
|
|
|
int min = get_axis_min_value(ie.code);
|
|
|
|
unsigned range = get_axis_range(ie.code);
|
|
|
|
gamepad_axis_input(ie.code, (ie.value - min) * 65535 / range - 32768);
|
|
|
|
}
|
2019-02-21 11:46:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-02-22 18:23:03 +00:00
|
|
|
void do_rumble(float power, u32 duration_ms)
|
|
|
|
{
|
|
|
|
// Remove previous effect
|
|
|
|
if (_rumble_effect_id != -1)
|
|
|
|
ioctl(_fd, EVIOCRMFF, _rumble_effect_id);
|
|
|
|
|
|
|
|
// Upload new effect
|
|
|
|
struct ff_effect effect;
|
|
|
|
effect.type = FF_RUMBLE;
|
|
|
|
effect.id = -1; // Let the driver assign one
|
|
|
|
effect.direction = 0;
|
|
|
|
effect.replay.length = (u16)duration_ms;
|
|
|
|
effect.replay.delay = 0;
|
|
|
|
effect.u.rumble.strong_magnitude = (s16)(power * 32767);
|
|
|
|
effect.u.rumble.weak_magnitude = (s16)(power * 32767);
|
|
|
|
if (ioctl(_fd, EVIOCSFF, &effect) == -1)
|
|
|
|
{
|
|
|
|
perror("evdev: Force feedback error");
|
|
|
|
_rumble_effect_id = -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_rumble_effect_id = effect.id;
|
|
|
|
|
|
|
|
// Let's play the effect
|
|
|
|
input_event play;
|
|
|
|
play.type = EV_FF;
|
|
|
|
play.code = effect.id;
|
|
|
|
play.value = 1;
|
|
|
|
if (write(_fd, (const void*) &play, sizeof(play)) == -1)
|
|
|
|
{
|
|
|
|
perror("evdev: Force feedback error");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-21 11:46:56 +00:00
|
|
|
|
|
|
|
int _fd;
|
|
|
|
std::string _devnode;
|
2019-02-22 18:23:03 +00:00
|
|
|
int _rumble_effect_id = -1;
|
|
|
|
float vib_inclination = 0;
|
|
|
|
double vib_stop_time = 0;
|
2021-09-19 16:27:21 +00:00
|
|
|
std::map<u32, int> axis_min_values;
|
|
|
|
std::map<u32, unsigned int> axis_ranges;
|
2019-02-21 13:49:27 +00:00
|
|
|
static std::map<std::string, std::shared_ptr<EvdevGamepadDevice>> evdev_gamepads;
|
2019-02-21 11:46:56 +00:00
|
|
|
};
|
|
|
|
|
2019-02-21 13:49:27 +00:00
|
|
|
std::map<std::string, std::shared_ptr<EvdevGamepadDevice>> EvdevGamepadDevice::evdev_gamepads;
|