sunshine-sdk/sunshine/platform/linux/input.cpp
Mathias Tillman e61bbe87b4 Read revents from the temporary pollfds instead of copying them over to the old vector.
Decrement index for polls when erasing to make sure none of the events are skipped.
2022-03-09 22:23:38 +01:00

1274 lines
40 KiB
C++

#include <fcntl.h>
#include <linux/uinput.h>
#include <poll.h>
#include <libevdev/libevdev-uinput.h>
#include <libevdev/libevdev.h>
#include <cmath>
#include <cstring>
#include <filesystem>
#include "sunshine/main.h"
#include "sunshine/platform/common.h"
#include "sunshine/utility.h"
#include "sunshine/platform/common.h"
// Support older versions
#ifndef REL_HWHEEL_HI_RES
#define REL_HWHEEL_HI_RES 0x0c
#endif
#ifndef REL_WHEEL_HI_RES
#define REL_WHEEL_HI_RES 0x0b
#endif
using namespace std::literals;
namespace platf {
constexpr auto mail_evdev = "platf::evdev"sv;
using evdev_t = util::safe_ptr<libevdev, libevdev_free>;
using uinput_t = util::safe_ptr<libevdev_uinput, libevdev_uinput_destroy>;
constexpr pollfd read_pollfd { -1, 0, 0 };
KITTY_USING_MOVE_T(pollfd_t, pollfd, read_pollfd, {
if(el.fd >= 0) {
ioctl(el.fd, EVIOCGRAB, (void *)0);
close(el.fd);
}
});
using mail_evdev_t = std::tuple<int, uinput_t::pointer, rumble_queue_t, pollfd_t>;
struct keycode_t {
std::uint32_t keycode;
std::uint32_t scancode;
int pressed;
};
constexpr auto UNKNOWN = 0;
static constexpr std::array<keycode_t, 0xE3> init_keycodes() {
std::array<keycode_t, 0xE3> keycodes {};
#define __CONVERT(wincode, linuxcode, scancode) \
static_assert(wincode < keycodes.size(), "Keycode doesn't fit into keycode array"); \
static_assert(wincode >= 0, "Are you mad?, keycode needs to be greater than zero"); \
keycodes[wincode] = keycode_t { linuxcode, scancode };
constexpr auto VK_NUMPAD = 0x60;
constexpr auto VK_F1 = 0x70;
constexpr auto VK_F13 = 0x7C;
constexpr auto VK_0 = 0x30;
for(auto x = 0; x < 9; ++x) {
keycodes[x + VK_NUMPAD + 1] = keycode_t { (std::uint32_t)KEY_KP1 + x, (std::uint32_t)0x70059 + x };
keycodes[x + VK_0 + 1] = keycode_t { (std::uint32_t)KEY_1 + x, (std::uint32_t)0x7001E + x };
}
for(auto x = 0; x < 10; ++x) {
keycodes[x + VK_F1] = keycode_t { (std::uint32_t)KEY_F1 + x, (std::uint32_t)0x70046 + x };
}
for(auto x = 0; x < 12; ++x) {
keycodes[x + VK_F13] = keycode_t { (std::uint32_t)KEY_F13 + x, (std::uint32_t)0x7003A + x };
}
__CONVERT(0x08 /* VKEY_BACK */, KEY_BACKSPACE, 0x7002A);
__CONVERT(0x09 /* VKEY_TAB */, KEY_TAB, 0x7002B);
__CONVERT(0x0C /* VKEY_CLEAR */, KEY_CLEAR, UNKNOWN);
__CONVERT(0x0D /* VKEY_RETURN */, KEY_ENTER, 0x70028);
__CONVERT(0x10 /* VKEY_SHIFT */, KEY_LEFTSHIFT, 0x700E1);
__CONVERT(0x11 /* VKEY_CONTROL */, KEY_LEFTCTRL, 0x700E0);
__CONVERT(0x12 /* VKEY_MENU */, KEY_LEFTALT, UNKNOWN);
__CONVERT(0x13 /* VKEY_PAUSE */, KEY_PAUSE, UNKNOWN);
__CONVERT(0x14 /* VKEY_CAPITAL */, KEY_CAPSLOCK, 0x70039);
__CONVERT(0x15 /* VKEY_KANA */, KEY_KATAKANAHIRAGANA, UNKNOWN);
__CONVERT(0x16 /* VKEY_HANGUL */, KEY_HANGEUL, UNKNOWN);
__CONVERT(0x17 /* VKEY_JUNJA */, KEY_HANJA, UNKNOWN);
__CONVERT(0x19 /* VKEY_KANJI */, KEY_KATAKANA, UNKNOWN);
__CONVERT(0x1B /* VKEY_ESCAPE */, KEY_ESC, 0x70029);
__CONVERT(0x20 /* VKEY_SPACE */, KEY_SPACE, 0x7002C);
__CONVERT(0x21 /* VKEY_PRIOR */, KEY_PAGEUP, 0x7004B);
__CONVERT(0x22 /* VKEY_NEXT */, KEY_PAGEDOWN, 0x7004E);
__CONVERT(0x23 /* VKEY_END */, KEY_END, 0x7004D);
__CONVERT(0x24 /* VKEY_HOME */, KEY_HOME, 0x7004A);
__CONVERT(0x25 /* VKEY_LEFT */, KEY_LEFT, 0x70050);
__CONVERT(0x26 /* VKEY_UP */, KEY_UP, 0x70052);
__CONVERT(0x27 /* VKEY_RIGHT */, KEY_RIGHT, 0x7004F);
__CONVERT(0x28 /* VKEY_DOWN */, KEY_DOWN, 0x70051);
__CONVERT(0x29 /* VKEY_SELECT */, KEY_SELECT, UNKNOWN);
__CONVERT(0x2A /* VKEY_PRINT */, KEY_PRINT, UNKNOWN);
__CONVERT(0x2C /* VKEY_SNAPSHOT */, KEY_SYSRQ, 0x70046);
__CONVERT(0x2D /* VKEY_INSERT */, KEY_INSERT, 0x70049);
__CONVERT(0x2E /* VKEY_DELETE */, KEY_DELETE, 0x7004C);
__CONVERT(0x2F /* VKEY_HELP */, KEY_HELP, UNKNOWN);
__CONVERT(0x30 /* VKEY_0 */, KEY_0, 0x70027);
__CONVERT(0x31 /* VKEY_1 */, KEY_1, 0x7001E);
__CONVERT(0x32 /* VKEY_2 */, KEY_2, 0x7001F);
__CONVERT(0x33 /* VKEY_3 */, KEY_3, 0x70020);
__CONVERT(0x34 /* VKEY_4 */, KEY_4, 0x70021);
__CONVERT(0x35 /* VKEY_5 */, KEY_5, 0x70022);
__CONVERT(0x36 /* VKEY_6 */, KEY_6, 0x70023);
__CONVERT(0x37 /* VKEY_7 */, KEY_7, 0x70024);
__CONVERT(0x38 /* VKEY_8 */, KEY_8, 0x70025);
__CONVERT(0x39 /* VKEY_9 */, KEY_9, 0x70026);
__CONVERT(0x41 /* VKEY_A */, KEY_A, 0x70004);
__CONVERT(0x42 /* VKEY_B */, KEY_B, 0x70005);
__CONVERT(0x43 /* VKEY_C */, KEY_C, 0x70006);
__CONVERT(0x44 /* VKEY_D */, KEY_D, 0x70007);
__CONVERT(0x45 /* VKEY_E */, KEY_E, 0x70008);
__CONVERT(0x46 /* VKEY_F */, KEY_F, 0x70009);
__CONVERT(0x47 /* VKEY_G */, KEY_G, 0x7000A);
__CONVERT(0x48 /* VKEY_H */, KEY_H, 0x7000B);
__CONVERT(0x49 /* VKEY_I */, KEY_I, 0x7000C);
__CONVERT(0x4A /* VKEY_J */, KEY_J, 0x7000D);
__CONVERT(0x4B /* VKEY_K */, KEY_K, 0x7000E);
__CONVERT(0x4C /* VKEY_L */, KEY_L, 0x7000F);
__CONVERT(0x4D /* VKEY_M */, KEY_M, 0x70010);
__CONVERT(0x4E /* VKEY_N */, KEY_N, 0x70011);
__CONVERT(0x4F /* VKEY_O */, KEY_O, 0x70012);
__CONVERT(0x50 /* VKEY_P */, KEY_P, 0x70013);
__CONVERT(0x51 /* VKEY_Q */, KEY_Q, 0x70014);
__CONVERT(0x52 /* VKEY_R */, KEY_R, 0x70015);
__CONVERT(0x53 /* VKEY_S */, KEY_S, 0x70016);
__CONVERT(0x54 /* VKEY_T */, KEY_T, 0x70017);
__CONVERT(0x55 /* VKEY_U */, KEY_U, 0x70018);
__CONVERT(0x56 /* VKEY_V */, KEY_V, 0x70019);
__CONVERT(0x57 /* VKEY_W */, KEY_W, 0x7001A);
__CONVERT(0x58 /* VKEY_X */, KEY_X, 0x7001B);
__CONVERT(0x59 /* VKEY_Y */, KEY_Y, 0x7001C);
__CONVERT(0x5A /* VKEY_Z */, KEY_Z, 0x7001D);
__CONVERT(0x5B /* VKEY_LWIN */, KEY_LEFTMETA, 0x700E3);
__CONVERT(0x5C /* VKEY_RWIN */, KEY_RIGHTMETA, 0x700E7);
__CONVERT(0x5F /* VKEY_SLEEP */, KEY_SLEEP, UNKNOWN);
__CONVERT(0x60 /* VKEY_NUMPAD0 */, KEY_KP0, 0x70062);
__CONVERT(0x61 /* VKEY_NUMPAD1 */, KEY_KP1, 0x70059);
__CONVERT(0x62 /* VKEY_NUMPAD2 */, KEY_KP2, 0x7005A);
__CONVERT(0x63 /* VKEY_NUMPAD3 */, KEY_KP3, 0x7005B);
__CONVERT(0x64 /* VKEY_NUMPAD4 */, KEY_KP4, 0x7005C);
__CONVERT(0x65 /* VKEY_NUMPAD5 */, KEY_KP5, 0x7005D);
__CONVERT(0x66 /* VKEY_NUMPAD6 */, KEY_KP6, 0x7005E);
__CONVERT(0x67 /* VKEY_NUMPAD7 */, KEY_KP7, 0x7005F);
__CONVERT(0x68 /* VKEY_NUMPAD8 */, KEY_KP8, 0x70060);
__CONVERT(0x69 /* VKEY_NUMPAD9 */, KEY_KP9, 0x70061);
__CONVERT(0x6A /* VKEY_MULTIPLY */, KEY_KPASTERISK, 0x70055);
__CONVERT(0x6B /* VKEY_ADD */, KEY_KPPLUS, 0x70057);
__CONVERT(0x6C /* VKEY_SEPARATOR */, KEY_KPCOMMA, UNKNOWN);
__CONVERT(0x6D /* VKEY_SUBTRACT */, KEY_KPMINUS, 0x70056);
__CONVERT(0x6E /* VKEY_DECIMAL */, KEY_KPDOT, 0x70063);
__CONVERT(0x6F /* VKEY_DIVIDE */, KEY_KPSLASH, 0x70054);
__CONVERT(0x7A /* VKEY_F11 */, KEY_F11, 70044);
__CONVERT(0x7B /* VKEY_F12 */, KEY_F12, 70045);
__CONVERT(0x90 /* VKEY_NUMLOCK */, KEY_NUMLOCK, 0x70053);
__CONVERT(0x91 /* VKEY_SCROLL */, KEY_SCROLLLOCK, 0x70047);
__CONVERT(0xA0 /* VKEY_LSHIFT */, KEY_LEFTSHIFT, 0x700E1);
__CONVERT(0xA1 /* VKEY_RSHIFT */, KEY_RIGHTSHIFT, 0x700E5);
__CONVERT(0xA2 /* VKEY_LCONTROL */, KEY_LEFTCTRL, 0x700E0);
__CONVERT(0xA3 /* VKEY_RCONTROL */, KEY_RIGHTCTRL, 0x700E4);
__CONVERT(0xA4 /* VKEY_LMENU */, KEY_LEFTALT, 0x7002E);
__CONVERT(0xA5 /* VKEY_RMENU */, KEY_RIGHTALT, 0x700E6);
__CONVERT(0xBA /* VKEY_OEM_1 */, KEY_SEMICOLON, 0x70033);
__CONVERT(0xBB /* VKEY_OEM_PLUS */, KEY_EQUAL, 0x7002E);
__CONVERT(0xBC /* VKEY_OEM_COMMA */, KEY_COMMA, 0x70036);
__CONVERT(0xBD /* VKEY_OEM_MINUS */, KEY_MINUS, 0x7002D);
__CONVERT(0xBE /* VKEY_OEM_PERIOD */, KEY_DOT, 0x70037);
__CONVERT(0xBF /* VKEY_OEM_2 */, KEY_SLASH, 0x70038);
__CONVERT(0xC0 /* VKEY_OEM_3 */, KEY_GRAVE, 0x70035);
__CONVERT(0xDB /* VKEY_OEM_4 */, KEY_LEFTBRACE, 0x7002F);
__CONVERT(0xDC /* VKEY_OEM_5 */, KEY_BACKSLASH, 0x70031);
__CONVERT(0xDD /* VKEY_OEM_6 */, KEY_RIGHTBRACE, 0x70030);
__CONVERT(0xDE /* VKEY_OEM_7 */, KEY_APOSTROPHE, 0x70034);
__CONVERT(0xE2 /* VKEY_NON_US_BACKSLASH */, KEY_102ND, 0x70064);
#undef __CONVERT
return keycodes;
}
static constexpr auto keycodes = init_keycodes();
constexpr touch_port_t target_touch_port {
0, 0,
19200, 12000
};
static std::pair<std::uint32_t, std::uint32_t> operator*(const std::pair<std::uint32_t, std::uint32_t> &l, int r) {
return {
l.first * r,
l.second * r,
};
}
static std::pair<std::uint32_t, std::uint32_t> operator/(const std::pair<std::uint32_t, std::uint32_t> &l, int r) {
return {
l.first / r,
l.second / r,
};
}
static std::pair<std::uint32_t, std::uint32_t> &operator+=(std::pair<std::uint32_t, std::uint32_t> &l, const std::pair<std::uint32_t, std::uint32_t> &r) {
l.first += r.first;
l.second += r.second;
return l;
}
static inline void print(const ff_envelope &envelope) {
BOOST_LOG(debug)
<< "Envelope:"sv << std::endl
<< " attack_length: " << envelope.attack_length << std::endl
<< " attack_level: " << envelope.attack_level << std::endl
<< " fade_length: " << envelope.fade_length << std::endl
<< " fade_level: " << envelope.fade_level;
}
static inline void print(const ff_replay &replay) {
BOOST_LOG(debug)
<< "Replay:"sv << std::endl
<< " length: "sv << replay.length << std::endl
<< " delay: "sv << replay.delay;
}
static inline void print(const ff_trigger &trigger) {
BOOST_LOG(debug)
<< "Trigger:"sv << std::endl
<< " button: "sv << trigger.button << std::endl
<< " interval: "sv << trigger.interval;
}
static inline void print(const ff_effect &effect) {
BOOST_LOG(debug)
<< std::endl
<< std::endl
<< "Received rumble effect with id: ["sv << effect.id << ']';
switch(effect.type) {
case FF_CONSTANT:
BOOST_LOG(debug)
<< "FF_CONSTANT:"sv << std::endl
<< " direction: "sv << effect.direction << std::endl
<< " level: "sv << effect.u.constant.level;
print(effect.u.constant.envelope);
break;
case FF_PERIODIC:
BOOST_LOG(debug)
<< "FF_CONSTANT:"sv << std::endl
<< " direction: "sv << effect.direction << std::endl
<< " waveform: "sv << effect.u.periodic.waveform << std::endl
<< " period: "sv << effect.u.periodic.period << std::endl
<< " magnitude: "sv << effect.u.periodic.magnitude << std::endl
<< " offset: "sv << effect.u.periodic.offset << std::endl
<< " phase: "sv << effect.u.periodic.phase;
print(effect.u.periodic.envelope);
break;
case FF_RAMP:
BOOST_LOG(debug)
<< "FF_RAMP:"sv << std::endl
<< " direction: "sv << effect.direction << std::endl
<< " start_level:" << effect.u.ramp.start_level << std::endl
<< " end_level:" << effect.u.ramp.end_level;
print(effect.u.ramp.envelope);
break;
case FF_RUMBLE:
BOOST_LOG(debug)
<< "FF_RUMBLE:" << std::endl
<< " direction: "sv << effect.direction << std::endl
<< " strong_magnitude: " << effect.u.rumble.strong_magnitude << std::endl
<< " weak_magnitude: " << effect.u.rumble.weak_magnitude;
break;
case FF_SPRING:
BOOST_LOG(debug)
<< "FF_SPRING:" << std::endl
<< " direction: "sv << effect.direction;
break;
case FF_FRICTION:
BOOST_LOG(debug)
<< "FF_FRICTION:" << std::endl
<< " direction: "sv << effect.direction;
break;
case FF_DAMPER:
BOOST_LOG(debug)
<< "FF_DAMPER:" << std::endl
<< " direction: "sv << effect.direction;
break;
case FF_INERTIA:
BOOST_LOG(debug)
<< "FF_INERTIA:" << std::endl
<< " direction: "sv << effect.direction;
break;
case FF_CUSTOM:
BOOST_LOG(debug)
<< "FF_CUSTOM:" << std::endl
<< " direction: "sv << effect.direction;
break;
default:
BOOST_LOG(debug)
<< "FF_UNKNOWN:" << std::endl
<< " direction: "sv << effect.direction;
break;
}
print(effect.replay);
print(effect.trigger);
}
// Emulate rumble effects
class effect_t {
public:
KITTY_DEFAULT_CONSTR_MOVE(effect_t)
effect_t(int gamepadnr, uinput_t::pointer dev, rumble_queue_t &&q)
: gamepadnr { gamepadnr }, dev { dev }, rumble_queue { std::move(q) }, gain { 0xFFFF }, id_to_data {} {}
class data_t {
public:
KITTY_DEFAULT_CONSTR(data_t)
data_t(const ff_effect &effect)
: delay { effect.replay.delay },
length { effect.replay.length },
end_point { std::chrono::steady_clock::time_point::min() },
envelope {},
start {},
end {} {
switch(effect.type) {
case FF_CONSTANT:
start.weak = effect.u.constant.level;
start.strong = effect.u.constant.level;
end.weak = effect.u.constant.level;
end.strong = effect.u.constant.level;
envelope = effect.u.constant.envelope;
break;
case FF_PERIODIC:
start.weak = effect.u.periodic.magnitude;
start.strong = effect.u.periodic.magnitude;
end.weak = effect.u.periodic.magnitude;
end.strong = effect.u.periodic.magnitude;
envelope = effect.u.periodic.envelope;
break;
case FF_RAMP:
start.weak = effect.u.ramp.start_level;
start.strong = effect.u.ramp.start_level;
end.weak = effect.u.ramp.end_level;
end.strong = effect.u.ramp.end_level;
envelope = effect.u.ramp.envelope;
break;
case FF_RUMBLE:
start.weak = effect.u.rumble.weak_magnitude;
start.strong = effect.u.rumble.strong_magnitude;
end.weak = effect.u.rumble.weak_magnitude;
end.strong = effect.u.rumble.strong_magnitude;
break;
default:
BOOST_LOG(warning) << "Effect type ["sv << effect.id << "] not implemented"sv;
}
}
std::uint32_t magnitude(std::chrono::milliseconds time_left, std::uint32_t start, std::uint32_t end) {
auto rel = end - start;
return start + (rel * time_left.count() / length.count());
}
std::pair<std::uint32_t, std::uint32_t> rumble(std::chrono::steady_clock::time_point tp) {
if(end_point < tp) {
return {};
}
auto time_left =
std::chrono::duration_cast<std::chrono::milliseconds>(
end_point - tp);
// If it needs to be delayed'
if(time_left > length) {
return {};
}
auto t = length - time_left;
auto weak = magnitude(t, start.weak, end.weak);
auto strong = magnitude(t, start.strong, end.strong);
if(t.count() < envelope.attack_length) {
weak = (envelope.attack_level * t.count() + weak * (envelope.attack_length - t.count())) / envelope.attack_length;
strong = (envelope.attack_level * t.count() + strong * (envelope.attack_length - t.count())) / envelope.attack_length;
}
else if(time_left.count() < envelope.fade_length) {
auto dt = (t - length).count() + envelope.fade_length;
weak = (envelope.fade_level * dt + weak * (envelope.fade_length - dt)) / envelope.fade_length;
strong = (envelope.fade_level * dt + strong * (envelope.fade_length - dt)) / envelope.fade_length;
}
return {
weak, strong
};
}
void activate() {
end_point = std::chrono::steady_clock::now() + delay + length;
}
void deactivate() {
end_point = std::chrono::steady_clock::time_point::min();
}
std::chrono::milliseconds delay;
std::chrono::milliseconds length;
std::chrono::steady_clock::time_point end_point;
ff_envelope envelope;
struct {
std::uint32_t weak, strong;
} start;
struct {
std::uint32_t weak, strong;
} end;
};
std::pair<std::uint32_t, std::uint32_t> rumble(std::chrono::steady_clock::time_point tp) {
std::pair<std::uint32_t, std::uint32_t> weak_strong {};
for(auto &[_, data] : id_to_data) {
weak_strong += data.rumble(tp);
}
std::clamp<std::uint32_t>(weak_strong.first, 0, 0xFFFF);
std::clamp<std::uint32_t>(weak_strong.second, 0, 0xFFFF);
old_rumble = weak_strong * gain / 0xFFFF;
return old_rumble;
}
void upload(const ff_effect &effect) {
print(effect);
auto it = id_to_data.find(effect.id);
if(it == std::end(id_to_data)) {
id_to_data.emplace(effect.id, effect);
return;
}
data_t data { effect };
data.end_point = it->second.end_point;
it->second = data;
}
void activate(int id) {
auto it = id_to_data.find(id);
if(it != std::end(id_to_data)) {
it->second.activate();
}
}
void deactivate(int id) {
auto it = id_to_data.find(id);
if(it != std::end(id_to_data)) {
it->second.deactivate();
}
}
void erase(int id) {
id_to_data.erase(id);
BOOST_LOG(debug) << "Removed rumble effect id ["sv << id << ']';
}
// Used as ID for rumble notifications
int gamepadnr;
// Used as ID for adding/removinf devices from evdev notifications
uinput_t::pointer dev;
rumble_queue_t rumble_queue;
int gain;
// No need to send rumble data when old values equals the new values
std::pair<std::uint32_t, std::uint32_t> old_rumble;
std::unordered_map<int, data_t> id_to_data;
};
struct rumble_ctx_t {
std::thread rumble_thread;
safe::queue_t<mail_evdev_t> rumble_queue_queue;
};
void broadcastRumble(safe::queue_t<mail_evdev_t> &ctx);
int startRumble(rumble_ctx_t &ctx) {
ctx.rumble_thread = std::thread { broadcastRumble, std::ref(ctx.rumble_queue_queue) };
return 0;
}
void stopRumble(rumble_ctx_t &ctx) {
ctx.rumble_queue_queue.stop();
BOOST_LOG(debug) << "Waiting for Gamepad notifications to stop..."sv;
ctx.rumble_thread.join();
BOOST_LOG(debug) << "Gamepad notifications stopped"sv;
}
static auto notifications = safe::make_shared<rumble_ctx_t>(startRumble, stopRumble);
struct input_raw_t {
public:
void clear_touchscreen() {
std::filesystem::path touch_path { appdata() / "sunshine_touchscreen"sv };
if(std::filesystem::is_symlink(touch_path)) {
std::filesystem::remove(touch_path);
}
touch_input.reset();
}
void clear_keyboard() {
std::filesystem::path key_path { appdata() / "sunshine_keyboard"sv };
if(std::filesystem::is_symlink(key_path)) {
std::filesystem::remove(key_path);
}
keyboard_input.reset();
}
void clear_mouse() {
std::filesystem::path mouse_path { appdata() / "sunshine_mouse"sv };
if(std::filesystem::is_symlink(mouse_path)) {
std::filesystem::remove(mouse_path);
}
mouse_input.reset();
}
void clear_gamepad(int nr) {
auto &[dev, _] = gamepads[nr];
if(!dev) {
return;
}
// Remove this gamepad from notifications
rumble_ctx->rumble_queue_queue.raise(nr, dev.get(), nullptr, pollfd_t {});
std::stringstream ss;
ss << "sunshine_gamepad_"sv << nr;
auto gamepad_path = platf::appdata() / ss.str();
if(std::filesystem::is_symlink(gamepad_path)) {
std::filesystem::remove(gamepad_path);
}
gamepads[nr] = std::make_pair(uinput_t {}, gamepad_state_t {});
}
int create_mouse() {
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &mouse_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Mouse: "sv << strerror(-err);
return -1;
}
std::filesystem::create_symlink(libevdev_uinput_get_devnode(mouse_input.get()), appdata() / "sunshine_mouse"sv);
return 0;
}
int create_touchscreen() {
int err = libevdev_uinput_create_from_device(touch_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &touch_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Touchscreen: "sv << strerror(-err);
return -1;
}
std::filesystem::create_symlink(libevdev_uinput_get_devnode(touch_input.get()), appdata() / "sunshine_touchscreen"sv);
return 0;
}
int create_keyboard() {
int err = libevdev_uinput_create_from_device(keyboard_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &keyboard_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Keyboard: "sv << strerror(-err);
return -1;
}
std::filesystem::create_symlink(libevdev_uinput_get_devnode(keyboard_input.get()), appdata() / "sunshine_keyboard"sv);
return 0;
}
int alloc_gamepad(int nr, rumble_queue_t &&rumble_queue) {
TUPLE_2D_REF(input, gamepad_state, gamepads[nr]);
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input);
gamepad_state = gamepad_state_t {};
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Gamepad: "sv << strerror(-err);
return -1;
}
std::stringstream ss;
ss << "sunshine_gamepad_"sv << nr;
auto gamepad_path = platf::appdata() / ss.str();
if(std::filesystem::is_symlink(gamepad_path)) {
std::filesystem::remove(gamepad_path);
}
auto dev_node = libevdev_uinput_get_devnode(input.get());
rumble_ctx->rumble_queue_queue.raise(
nr,
input.get(),
std::move(rumble_queue),
pollfd_t {
dup(libevdev_uinput_get_fd(input.get())),
(std::int16_t)POLLIN,
(std::int16_t)0,
});
std::filesystem::create_symlink(dev_node, gamepad_path);
return 0;
}
void clear() {
clear_touchscreen();
clear_keyboard();
clear_mouse();
for(int x = 0; x < gamepads.size(); ++x) {
clear_gamepad(x);
}
}
~input_raw_t() {
clear();
}
safe::shared_t<rumble_ctx_t>::ptr_t rumble_ctx;
std::vector<std::pair<uinput_t, gamepad_state_t>> gamepads;
uinput_t mouse_input;
uinput_t touch_input;
uinput_t keyboard_input;
evdev_t gamepad_dev;
evdev_t touch_dev;
evdev_t mouse_dev;
evdev_t keyboard_dev;
};
inline void rumbleIterate(std::vector<effect_t> &effects, std::vector<pollfd_t> &polls, std::chrono::milliseconds to) {
std::vector<pollfd> polls_recv;
polls_recv.reserve(polls.size());
for(auto &poll : polls) {
polls_recv.emplace_back(poll.el);
}
auto res = poll(polls_recv.data(), polls_recv.size(), to.count());
// If timed out
if(!res) {
return;
}
if(res < 0) {
char err_str[1024];
BOOST_LOG(error) << "Couldn't poll Gamepad file descriptors: "sv << strerror_r(errno, err_str, 1024);
return;
}
for(int x = 0; x < polls.size(); ++x) {
auto poll = std::begin(polls) + x;
auto effect_it = std::begin(effects) + x;
auto fd = (*poll)->fd;
// TUPLE_2D_REF(dev, q, *dev_q_it);
// on error
if(polls_recv[x].revents & (POLLHUP | POLLRDHUP | POLLERR)) {
BOOST_LOG(warning) << "Gamepad ["sv << x << "] file discriptor closed unexpectedly"sv;
polls.erase(poll);
effects.erase(effect_it);
--x;
continue;
}
if(!(polls_recv[x].revents & POLLIN)) {
continue;
}
input_event events[64];
// Read all available events
auto bytes = read(fd, &events, sizeof(events));
if(bytes < 0) {
char err_str[1024];
BOOST_LOG(error) << "Couldn't read evdev input ["sv << errno << "]: "sv << strerror_r(errno, err_str, 1024);
polls.erase(poll);
effects.erase(effect_it);
--x;
continue;
}
if(bytes < sizeof(input_event)) {
BOOST_LOG(warning) << "Reading evdev input: Expected at least "sv << sizeof(input_event) << " bytes, got "sv << bytes << " instead"sv;
continue;
}
auto event_count = bytes / sizeof(input_event);
for(auto event = events; event != (events + event_count); ++event) {
switch(event->type) {
case EV_FF:
// BOOST_LOG(debug) << "EV_FF: "sv << event->value << " aka "sv << util::hex(event->value).to_string_view();
if(event->code == FF_GAIN) {
BOOST_LOG(debug) << "EV_FF: code [FF_GAIN]: value: "sv << event->value << " aka "sv << util::hex(event->value).to_string_view();
effect_it->gain = std::clamp(event->value, 0, 0xFFFF);
break;
}
BOOST_LOG(debug) << "EV_FF: id ["sv << event->code << "]: value: "sv << event->value << " aka "sv << util::hex(event->value).to_string_view();
if(event->value) {
effect_it->activate(event->code);
}
else {
effect_it->deactivate(event->code);
}
break;
case EV_UINPUT:
switch(event->code) {
case UI_FF_UPLOAD: {
uinput_ff_upload upload {};
// *VERY* important, without this you break
// the kernel and have to reboot due to dead
// hanging process
upload.request_id = event->value;
ioctl(fd, UI_BEGIN_FF_UPLOAD, &upload);
auto fg = util::fail_guard([&]() {
upload.retval = 0;
ioctl(fd, UI_END_FF_UPLOAD, &upload);
});
effect_it->upload(upload.effect);
} break;
case UI_FF_ERASE: {
uinput_ff_erase erase {};
// *VERY* important, without this you break
// the kernel and have to reboot due to dead
// hanging process
erase.request_id = event->value;
ioctl(fd, UI_BEGIN_FF_ERASE, &erase);
auto fg = util::fail_guard([&]() {
erase.retval = 0;
ioctl(fd, UI_END_FF_ERASE, &erase);
});
effect_it->erase(erase.effect_id);
} break;
}
break;
default:
BOOST_LOG(debug)
<< util::hex(event->type).to_string_view() << ": "sv
<< util::hex(event->code).to_string_view() << ": "sv
<< event->value << " aka "sv << util::hex(event->value).to_string_view();
}
}
}
}
void broadcastRumble(safe::queue_t<mail_evdev_t> &rumble_queue_queue) {
std::vector<effect_t> effects;
std::vector<pollfd_t> polls;
while(rumble_queue_queue.running()) {
while(rumble_queue_queue.peek()) {
auto dev_rumble_queue = rumble_queue_queue.pop();
if(!dev_rumble_queue) {
// rumble_queue_queue is no longer running
return;
}
auto gamepadnr = std::get<0>(*dev_rumble_queue);
auto dev = std::get<1>(*dev_rumble_queue);
auto &rumble_queue = std::get<2>(*dev_rumble_queue);
auto &pollfd = std::get<3>(*dev_rumble_queue);
{
auto effect_it = std::find_if(std::begin(effects), std::end(effects), [dev](auto &curr_effect) {
return dev == curr_effect.dev;
});
if(effect_it != std::end(effects)) {
auto poll_it = std::begin(polls) + (effect_it - std::begin(effects));
polls.erase(poll_it);
effects.erase(effect_it);
BOOST_LOG(debug) << "Removed Gamepad device from notifications"sv;
continue;
}
// There may be an attepmt to remove, that which not exists
if(!rumble_queue) {
BOOST_LOG(warning) << "Attempting to remove a gamepad device from notifications that isn't already registered"sv;
continue;
}
}
polls.emplace_back(std::move(pollfd));
effects.emplace_back(gamepadnr, dev, std::move(rumble_queue));
BOOST_LOG(debug) << "Added Gamepad device to notifications"sv;
}
if(polls.empty()) {
std::this_thread::sleep_for(250ms);
}
else {
rumbleIterate(effects, polls, 100ms);
auto now = std::chrono::steady_clock::now();
for(auto &effect : effects) {
TUPLE_2D(old_weak, old_strong, effect.old_rumble);
TUPLE_2D(weak, strong, effect.rumble(now));
if(old_weak != weak || old_strong != strong) {
BOOST_LOG(debug) << "Sending haptic feedback: lowfreq [0x"sv << util::hex(weak).to_string_view() << "]: highfreq [0x"sv << util::hex(strong).to_string_view() << ']';
effect.rumble_queue->raise(effect.gamepadnr, weak, strong);
}
}
}
}
}
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto touchscreen = ((input_raw_t *)input.get())->touch_input.get();
auto scaled_x = (int)std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
auto scaled_y = (int)std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, scaled_x);
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, scaled_y);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 1);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 0);
libevdev_uinput_write_event(touchscreen, EV_SYN, SYN_REPORT, 0);
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
if(deltaX) {
libevdev_uinput_write_event(mouse, EV_REL, REL_X, deltaX);
}
if(deltaY) {
libevdev_uinput_write_event(mouse, EV_REL, REL_Y, deltaY);
}
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
}
void button_mouse(input_t &input, int button, bool release) {
int btn_type;
int scan;
if(button == 1) {
btn_type = BTN_LEFT;
scan = 90001;
}
else if(button == 2) {
btn_type = BTN_MIDDLE;
scan = 90003;
}
else if(button == 3) {
btn_type = BTN_RIGHT;
scan = 90002;
}
else if(button == 4) {
btn_type = BTN_SIDE;
scan = 90004;
}
else {
btn_type = BTN_EXTRA;
scan = 90005;
}
auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
libevdev_uinput_write_event(mouse, EV_MSC, MSC_SCAN, scan);
libevdev_uinput_write_event(mouse, EV_KEY, btn_type, release ? 0 : 1);
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
}
void scroll(input_t &input, int high_res_distance) {
int distance = high_res_distance / 120;
auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL, distance);
libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL_HI_RES, high_res_distance);
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
}
static keycode_t keysym(std::uint16_t modcode) {
if(modcode <= keycodes.size()) {
return keycodes[modcode];
}
return {};
}
void keyboard(input_t &input, uint16_t modcode, bool release) {
auto keyboard = ((input_raw_t *)input.get())->keyboard_input.get();
auto keycode = keysym(modcode);
if(keycode.keycode == UNKNOWN) {
return;
}
if(keycode.scancode != UNKNOWN && (release || !keycode.pressed)) {
libevdev_uinput_write_event(keyboard, EV_MSC, MSC_SCAN, keycode.scancode);
}
libevdev_uinput_write_event(keyboard, EV_KEY, keycode.keycode, release ? 0 : (1 + keycode.pressed));
libevdev_uinput_write_event(keyboard, EV_SYN, SYN_REPORT, 0);
keycode.pressed = 1;
}
int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) {
return ((input_raw_t *)input.get())->alloc_gamepad(nr, std::move(rumble_queue));
}
void free_gamepad(input_t &input, int nr) {
((input_raw_t *)input.get())->clear_gamepad(nr);
}
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t *)input.get())->gamepads[nr]);
auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags;
auto bf_new = gamepad_state.buttonFlags;
if(bf) {
// up pressed == -1, down pressed == 1, else 0
if((DPAD_UP | DPAD_DOWN) & bf) {
int button_state = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0);
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0Y, button_state);
}
if((DPAD_LEFT | DPAD_RIGHT) & bf) {
int button_state = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0);
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0X, button_state);
}
if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0);
if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0);
if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0);
}
if(gamepad_state_old.lt != gamepad_state.lt) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_Z, gamepad_state.lt);
}
if(gamepad_state_old.rt != gamepad_state.rt) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_RZ, gamepad_state.rt);
}
if(gamepad_state_old.lsX != gamepad_state.lsX) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_X, gamepad_state.lsX);
}
if(gamepad_state_old.lsY != gamepad_state.lsY) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_Y, -gamepad_state.lsY);
}
if(gamepad_state_old.rsX != gamepad_state.rsX) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_RX, gamepad_state.rsX);
}
if(gamepad_state_old.rsY != gamepad_state.rsY) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_RY, -gamepad_state.rsY);
}
gamepad_state_old = gamepad_state;
libevdev_uinput_write_event(uinput.get(), EV_SYN, SYN_REPORT, 0);
}
evdev_t keyboard() {
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Keyboard");
libevdev_set_id_product(dev.get(), 0xDEAD);
libevdev_set_id_vendor(dev.get(), 0xBEEF);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x111);
libevdev_set_name(dev.get(), "Keyboard passthrough");
libevdev_enable_event_type(dev.get(), EV_KEY);
for(const auto &keycode : keycodes) {
libevdev_enable_event_code(dev.get(), EV_KEY, keycode.keycode, nullptr);
}
libevdev_enable_event_type(dev.get(), EV_MSC);
libevdev_enable_event_code(dev.get(), EV_MSC, MSC_SCAN, nullptr);
return dev;
}
evdev_t mouse() {
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Mouse");
libevdev_set_id_product(dev.get(), 0x4038);
libevdev_set_id_vendor(dev.get(), 0x46D);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x111);
libevdev_set_name(dev.get(), "Logitech Wireless Mouse PID:4038");
libevdev_enable_event_type(dev.get(), EV_KEY);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_LEFT, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_RIGHT, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_MIDDLE, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_SIDE, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_EXTRA, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_FORWARD, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_BACK, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TASK, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 280, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 281, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 282, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 283, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 284, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 285, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 286, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, 287, nullptr);
libevdev_enable_event_type(dev.get(), EV_REL);
libevdev_enable_event_code(dev.get(), EV_REL, REL_X, nullptr);
libevdev_enable_event_code(dev.get(), EV_REL, REL_Y, nullptr);
libevdev_enable_event_code(dev.get(), EV_REL, REL_WHEEL, nullptr);
libevdev_enable_event_code(dev.get(), EV_REL, REL_WHEEL_HI_RES, nullptr);
libevdev_enable_event_code(dev.get(), EV_REL, REL_HWHEEL, nullptr);
libevdev_enable_event_code(dev.get(), EV_REL, REL_HWHEEL_HI_RES, nullptr);
libevdev_enable_event_type(dev.get(), EV_MSC);
libevdev_enable_event_code(dev.get(), EV_MSC, MSC_SCAN, nullptr);
return dev;
}
evdev_t touchscreen() {
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Touch");
libevdev_set_id_product(dev.get(), 0xDEAD);
libevdev_set_id_vendor(dev.get(), 0xBEEF);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x111);
libevdev_set_name(dev.get(), "Touchscreen passthrough");
libevdev_enable_property(dev.get(), INPUT_PROP_DIRECT);
libevdev_enable_event_type(dev.get(), EV_KEY);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOUCH, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_PEN, nullptr); // Needed to be enabled for BTN_TOOL_FINGER to work.
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_FINGER, nullptr);
input_absinfo absx {
0,
0,
target_touch_port.width,
1,
0,
28
};
input_absinfo absy {
0,
0,
target_touch_port.height,
1,
0,
28
};
libevdev_enable_event_type(dev.get(), EV_ABS);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_X, &absx);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Y, &absy);
return dev;
}
evdev_t x360() {
evdev_t dev { libevdev_new() };
input_absinfo stick {
0,
-32768, 32767,
16,
128,
0
};
input_absinfo trigger {
0,
0, 255,
0,
0,
0
};
input_absinfo dpad {
0,
-1, 1,
0,
0,
0
};
libevdev_set_uniq(dev.get(), "Sunshine Gamepad");
libevdev_set_id_product(dev.get(), 0x28E);
libevdev_set_id_vendor(dev.get(), 0x45E);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x110);
libevdev_set_name(dev.get(), "Microsoft X-Box 360 pad");
libevdev_enable_event_type(dev.get(), EV_KEY);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_WEST, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_EAST, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_NORTH, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_SOUTH, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_THUMBL, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_THUMBR, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TR, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TL, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_SELECT, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_MODE, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_START, nullptr);
libevdev_enable_event_type(dev.get(), EV_ABS);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_HAT0Y, &dpad);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_HAT0X, &dpad);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Z, &trigger);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_RZ, &trigger);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_X, &stick);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_RX, &stick);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Y, &stick);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_RY, &stick);
libevdev_enable_event_type(dev.get(), EV_FF);
libevdev_enable_event_code(dev.get(), EV_FF, FF_RUMBLE, nullptr);
libevdev_enable_event_code(dev.get(), EV_FF, FF_CONSTANT, nullptr);
libevdev_enable_event_code(dev.get(), EV_FF, FF_PERIODIC, nullptr);
libevdev_enable_event_code(dev.get(), EV_FF, FF_SINE, nullptr);
libevdev_enable_event_code(dev.get(), EV_FF, FF_RAMP, nullptr);
libevdev_enable_event_code(dev.get(), EV_FF, FF_GAIN, nullptr);
return dev;
}
input_t input() {
input_t result { new input_raw_t() };
auto &gp = *(input_raw_t *)result.get();
gp.rumble_ctx = notifications.ref();
gp.gamepads.resize(MAX_GAMEPADS);
// Ensure starting from clean slate
gp.clear();
gp.keyboard_dev = keyboard();
gp.touch_dev = touchscreen();
gp.mouse_dev = mouse();
gp.gamepad_dev = x360();
// If we do not have a keyboard, gamepad or mouse, no input is possible and we should abort
if(gp.create_mouse() || gp.create_touchscreen() || gp.create_keyboard()) {
log_flush();
std::abort();
}
return result;
}
void freeInput(void *p) {
auto *input = (input_raw_t *)p;
delete input;
}
std::vector<std::string_view> &supported_gamepads() {
static std::vector<std::string_view> gamepads { "x360"sv };
return gamepads;
}
} // namespace platf