diff --git a/platform/linux_evdev.cpp b/platform/linux_evdev.cpp new file mode 100644 index 00000000..485db827 --- /dev/null +++ b/platform/linux_evdev.cpp @@ -0,0 +1,364 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "common.h" +#include "utility.h" + +namespace platf { +constexpr std::uint16_t DPAD_UP = 0x0001; +constexpr std::uint16_t DPAD_DOWN = 0x0002; +constexpr std::uint16_t DPAD_LEFT = 0x0004; +constexpr std::uint16_t DPAD_RIGHT = 0x0008; +constexpr std::uint16_t START = 0x0010; +constexpr std::uint16_t BACK = 0x0020; +constexpr std::uint16_t LEFT_STICK = 0x0040; +constexpr std::uint16_t RIGHT_STICK = 0x0080; +constexpr std::uint16_t LEFT_BUTTON = 0x0100; +constexpr std::uint16_t RIGHT_BUTTON = 0x0200; +constexpr std::uint16_t HOME = 0x0400; +constexpr std::uint16_t A = 0x1000; +constexpr std::uint16_t B = 0x2000; +constexpr std::uint16_t X = 0x4000; +constexpr std::uint16_t Y = 0x8000; + +using namespace std::literals; +using evdev_t = util::safe_ptr; +using uinput_t = util::safe_ptr; + +struct input_raw_t { + evdev_t dev; + uinput_t uinput; + display_t display; + + gamepad_state_t gamepad_state; +}; + +//TODO: Use libevdev for keyboard and mouse, then any mention of X11 can be removed from linux_input.cpp +struct display_attr_t { + display_attr_t() : display { XOpenDisplay(nullptr) }, window { DefaultRootWindow(display) }, attr {} { + XGetWindowAttributes(display, window, &attr); + } + + ~display_attr_t() { + XCloseDisplay(display); + } + + Display *display; + Window window; + XWindowAttributes attr; +}; + +void move_mouse(input_t &input, int deltaX, int deltaY) { + auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get()); + + XWarpPointer(disp.display, None, None, 0, 0, 0, 0, deltaX, deltaY); + XFlush(disp.display); +} + +void button_mouse(input_t &input, int button, bool release) { + auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get()); + + XTestFakeButtonEvent(disp.display, button, !release, CurrentTime); + + XFlush(disp.display); +} + +void scroll(input_t &input, int distance) { + auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get()); + + int button = distance > 0 ? 4 : 5; + + distance = std::abs(distance / 120); + while(distance > 0) { + --distance; + + XTestFakeButtonEvent(disp.display, button, True, CurrentTime); + XTestFakeButtonEvent(disp.display, button, False, CurrentTime); + + XSync(disp.display, 0); + } + + XFlush(disp.display); +} + +uint16_t keysym(uint16_t modcode) { + constexpr auto VK_NUMPAD = 0x60; + constexpr auto VK_F1 = 0x70; + + if(modcode >= VK_NUMPAD && modcode < VK_NUMPAD + 10) { + return XK_KP_0 + (modcode - VK_NUMPAD); + } + + if(modcode >= VK_F1 && modcode < VK_F1 + 13) { + return XK_F1 + (modcode - VK_F1); + } + + + switch(modcode) { + case 0x08: + return XK_BackSpace; + case 0x09: + return XK_Tab; + case 0x0D: + return XK_Return; + case 0x13: + return XK_Pause; + case 0x14: + return XK_Caps_Lock; + case 0x1B: + return XK_Escape; + case 0x21: + return XK_Page_Up; + case 0x22: + return XK_Page_Down; + case 0x23: + return XK_End; + case 0x24: + return XK_Home; + case 0x25: + return XK_Left; + case 0x26: + return XK_Up; + case 0x27: + return XK_Right; + case 0x28: + return XK_Down; + case 0x29: + return XK_Select; + case 0x2B: + return XK_Execute; + case 0x2C: + return XK_Print; //FIXME: is this correct? (printscreen) + case 0x2D: + return XK_Insert; + case 0x2E: + return XK_Delete; + case 0x2F: + return XK_Help; + case 0x6A: + return XK_KP_Multiply; + case 0x6B: + return XK_KP_Add; + case 0x6C: + return XK_KP_Decimal; //FIXME: is this correct? (Comma) + case 0x6D: + return XK_KP_Subtract; + case 0x6E: + return XK_KP_Separator; //FIXME: is this correct? (Period) + case 0x6F: + return XK_KP_Divide; + case 0x90: + return XK_Num_Lock; //FIXME: is this correct: (NumlockClear) + case 0x91: + return XK_Scroll_Lock; + case 0xA0: + return XK_Shift_L; + case 0xA1: + return XK_Shift_R; + case 0xA2: + return XK_Control_L; + case 0xA3: + return XK_Control_R; + case 0xA4: + return XK_Alt_L; + case 0xA5: /* return XK_Alt_R; */ + return XK_Super_L; + case 0xBA: + return XK_semicolon; + case 0xBB: + return XK_equal; + case 0xBC: + return XK_comma; + case 0xBD: + return XK_minus; + case 0xBE: + return XK_period; + case 0xBF: + return XK_slash; + case 0xC0: + return XK_grave; + case 0xDB: + return XK_bracketleft; + case 0xDC: + return XK_backslash; + case 0xDD: + return XK_bracketright; + case 0xDE: + return XK_apostrophe; + case 0x01: //FIXME: Moonlight doesn't support Super key + return XK_Super_L; + case 0x02: + return XK_Super_R; + } + + return modcode; +} + +void keyboard(input_t &input, uint16_t modcode, bool release) { + auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get()); + KeyCode kc = XKeysymToKeycode(disp.display, keysym(modcode)); + + if(!kc) { + return; + } + + XTestFakeKeyEvent(disp.display, kc, !release, 0); + + XSync(disp.display, 0); + XFlush(disp.display); +} + +void gamepad(input_t &input, const gamepad_state_t &gamepad_state) { + auto &gp = *(input_raw_t*)input.get(); + + auto bf = gamepad_state.buttonFlags ^ gp.gamepad_state.buttonFlags; + auto bf_new = gamepad_state.buttonFlags; + + if(bf) { + // up pressed == -1, down pressed == 1, else 0 + if(DPAD_UP & bf) { + int val = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0); + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0Y, val); + } + else if(DPAD_DOWN & bf) { + int val = bf_new & DPAD_DOWN ? 1 : 0; + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0Y, val); + } + + if(DPAD_LEFT & bf) { + int val = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0); + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0X, val); + } + else if(DPAD_RIGHT & bf) { + int val = bf_new & DPAD_RIGHT ? 1 : 0; + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_HAT0X, val); + } + + if(START & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0); + if(BACK & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0); + if(LEFT_STICK & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0); + if(RIGHT_STICK & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0); + if(LEFT_BUTTON & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0); + if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0); + if(HOME & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0); + if(A & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0); + if(B & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0); + if(X & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0); + if(Y & bf) libevdev_uinput_write_event(gp.uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0); + } + + if(gp.gamepad_state.lt != gamepad_state.lt) { + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_Z, gamepad_state.lt); + } + + if(gp.gamepad_state.rt != gamepad_state.rt) { + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_RZ, gamepad_state.rt); + } + + if(gp.gamepad_state.lsX != gamepad_state.lsX) { + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_X, gamepad_state.lsX); + } + + if(gp.gamepad_state.lsY != gamepad_state.lsY) { + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_Y, -gamepad_state.lsY); + } + + if(gp.gamepad_state.rsX != gamepad_state.rsX) { + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_RX, gamepad_state.rsX); + } + + if(gp.gamepad_state.rsY != gamepad_state.rsY) { + libevdev_uinput_write_event(gp.uinput.get(), EV_ABS, ABS_RY, -gamepad_state.rsY); + } + + gp.gamepad_state = gamepad_state; + libevdev_uinput_write_event(gp.uinput.get(), EV_SYN, SYN_REPORT, 0); +} + +input_t input() { + input_t result { new input_raw_t() }; + auto &gp = *(input_raw_t*)result.get(); + + gp.dev.reset(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(gp.dev.get(), "Sunshine Gamepad"); + libevdev_set_id_product(gp.dev.get(), 0x28E); + libevdev_set_id_vendor(gp.dev.get(), 0x45E); + libevdev_set_id_bustype(gp.dev.get(), 0x3); + libevdev_set_id_version(gp.dev.get(), 0x110); + libevdev_set_name(gp.dev.get(), "Microsoft X-Box 360 pad"); + + libevdev_enable_event_type(gp.dev.get(), EV_KEY); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_WEST, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_EAST, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_NORTH, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_SOUTH, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_THUMBL, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_THUMBR, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_TR, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_TL, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_SELECT, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_MODE, nullptr); + libevdev_enable_event_code(gp.dev.get(), EV_KEY, BTN_START, nullptr); + + libevdev_enable_event_type(gp.dev.get(), EV_ABS); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_HAT0Y, &dpad); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_HAT0X, &dpad); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_Z, &trigger); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_RZ, &trigger); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_X, &stick); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_RX, &stick); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_Y, &stick); + libevdev_enable_event_code(gp.dev.get(), EV_ABS, ABS_RY, &stick); + + libevdev_uinput *buf; + int err = libevdev_uinput_create_from_device(gp.dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf); + + gp.uinput.reset(buf); + if(err) { + std::cout << "Could not create Sunshine Gamepad: "sv << strerror(-err) << std::endl; + return nullptr; + } + + gp.display = display(); + return result; +} + +void freeInput(void *p) { + auto *input = (input_raw_t*)p; + delete input; +} +}