diff --git a/CMakeLists.txt b/CMakeLists.txt index 19748984..df41e6fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ add_subdirectory(src/core) if (UNIX AND NOT APPLE) add_subdirectory(src/fake-udev) + add_subdirectory(src/fake-uinput) endif () option(BUILD_MOONLIGHT "Build Moonlight server" ON) diff --git a/src/fake-uinput/CMakeLists.txt b/src/fake-uinput/CMakeLists.txt new file mode 100644 index 00000000..1cdeaf4a --- /dev/null +++ b/src/fake-uinput/CMakeLists.txt @@ -0,0 +1,18 @@ +option(BUILD_FAKE_UINPUT "Build fake-uinput library" ON) +if (BUILD_FAKE_UINPUT) + message(STATUS "Building fake-uinput") + + add_library(fake_uinput SHARED + fake-uinput.cpp) + add_library(fake-uinput::lib ALIAS fake_uinput) + + target_compile_features(fake_uinput PRIVATE cxx_std_17) + option(FAKE_UINPUT_32BIT "Build fake-uinput 32-bit library" OFF) + if (FAKE_UINPUT_32BIT) + set_target_properties(fake_uinput PROPERTIES + # Force it to 32 bit + COMPILE_FLAGS "-m32" LINK_FLAGS "-m32" + # Change the name of the library to indicate it's 32-bit + OUTPUT_NAME "fake-uinput-32") + endif () +endif () \ No newline at end of file diff --git a/src/fake-uinput/fake-uinput.cpp b/src/fake-uinput/fake-uinput.cpp new file mode 100644 index 00000000..57b48868 --- /dev/null +++ b/src/fake-uinput/fake-uinput.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** https://github.com/whot/libevdev/blob/c3953e1bb8f813f3e248047d23fb0775b147da9f/libevdev/libevdev-uinput.c#L41 **/ +#define SYS_INPUT_DIR "/sys/devices/virtual/input/" + +#define LOG(Thing) std::cout << "[fake-uinput] " << Thing << std::endl; + +typedef int (*ioctl_t)(int, unsigned long, ...); +static ioctl_t real_ioctl = nullptr; + +bool load_library() { + LOG("Loading ...") + // Load the real ioctl + real_ioctl = reinterpret_cast(dlsym(RTLD_NEXT, "ioctl")); + if (!real_ioctl) { + LOG("Error: " << dlerror()); + return false; + } + return true; +} + +void mount(const std::filesystem::path &sysfs_path, const std::string &filename) { + std::filesystem::path path(filename); + if (!std::filesystem::exists(path)) { + // Get the major and minor numbers of the device + // They are handily available in the sysfs directory + std::ifstream major_minor_file(sysfs_path / "dev"); + unsigned int major, minor; + char sep; + major_minor_file >> major >> sep >> minor; + + int rc = mknod(filename.c_str(), S_IFCHR | 0666, makedev(major, minor)); + if (rc == -1) { + LOG("Error creating device node: " << strerror(errno)); + } else { + LOG("Created device node: " << filename); + } + } +} + +/** + * glibc, BSD + * TODO: musl uses int op instead of unsigned long + */ +extern "C" int ioctl(int fd, unsigned long request, ...) { + if (!real_ioctl) { + if (!load_library()) { + LOG("Error: real_ioctl is not initialized"); + return -1; + } + } + + // Call the real ioctl + va_list args; + va_start(args, request); + void *arg = va_arg(args, void *); + va_end(args); + int result = real_ioctl(fd, request, arg); + + if (result >= 0 && request == UI_DEV_CREATE) { // Creating a new uinput device + LOG("Intercepted UI_DEV_CREATE ioctl call"); + + // Get the sysfs name of the created uinput device + char buf[sizeof(SYS_INPUT_DIR) + 64] = SYS_INPUT_DIR; + auto rc = real_ioctl(fd, UI_GET_SYSNAME(sizeof(buf) - strlen(SYS_INPUT_DIR)), &buf[strlen(SYS_INPUT_DIR)]); + if (rc != -1) { + // iterate over the files under /sys/devices/virtual/input/ and find "eventX" and "jsX" files + for (auto &p : std::filesystem::directory_iterator(buf)) { + if (p.is_directory()) { + auto path = p.path(); + auto filename = path.filename().string(); + if (filename.find("event") != std::string::npos) { + mount(path, "/dev/input/" + filename); + } else if (filename.find("js") != std::string::npos) { + mount(path, "/dev/input/" + filename); + } + } + } + + // TODO: udev? using /sys/devices/virtual/input/{buf}/uevent ? + + } else { + LOG("Error getting sysname: " << strerror(errno)); + } + } + + // return the original result + return result; +} diff --git a/src/moonlight-server/control/input_handler.cpp b/src/moonlight-server/control/input_handler.cpp index 34650f7d..c4831dd5 100644 --- a/src/moonlight-server/control/input_handler.cpp +++ b/src/moonlight-server/control/input_handler.cpp @@ -497,21 +497,26 @@ void controller_multi(const CONTROLLER_MULTI_PACKET &pkt, // Remove the joypad, this will delete the last reference session.joypads->update([&](state::JoypadList joypads) { return joypads.erase(pkt.controller_number); }); + return; } } else { // Old Moonlight doesn't support CONTROLLER_ARRIVAL, we create a default pad when it's first mentioned selected_pad = create_new_joypad(session, connected_clients, pkt.controller_number, XBOX, ANALOG_TRIGGERS | RUMBLE); } - std::visit( - [pkt](inputtino::Joypad &pad) { - std::uint16_t bf = pkt.button_flags; - std::uint32_t bf2 = pkt.buttonFlags2; - pad.set_pressed_buttons(bf | (bf2 << 16)); - pad.set_stick(inputtino::Joypad::LS, pkt.left_stick_x, pkt.left_stick_y); - pad.set_stick(inputtino::Joypad::RS, pkt.right_stick_x, pkt.right_stick_y); - pad.set_triggers(pkt.left_trigger, pkt.right_trigger); - }, - *selected_pad); + + // Update the joypad state + if (selected_pad) { + std::visit( + [pkt](inputtino::Joypad &pad) { + std::uint16_t bf = pkt.button_flags; + std::uint32_t bf2 = pkt.buttonFlags2; + pad.set_pressed_buttons(bf | (bf2 << 16)); + pad.set_stick(inputtino::Joypad::LS, pkt.left_stick_x, pkt.left_stick_y); + pad.set_stick(inputtino::Joypad::RS, pkt.right_stick_x, pkt.right_stick_y); + pad.set_triggers(pkt.left_trigger, pkt.right_trigger); + }, + *selected_pad); + } } void controller_touch(const CONTROLLER_TOUCH_PACKET &pkt, state::StreamSession &session) {