Skip to content

Commit

Permalink
feat(input/linux): add support for more virtual input devices (Lizard…
Browse files Browse the repository at this point in the history
…Byte#2606)

Co-authored-by: ABeltramo <[email protected]>
Co-authored-by: ReenigneArcher <[email protected]>
  • Loading branch information
3 people authored and KuleRucket committed Oct 9, 2024
1 parent 076b9b5 commit 0ac4e3c
Show file tree
Hide file tree
Showing 31 changed files with 1,163 additions and 77 deletions.
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
path = third-party/googletest
url = https://github.com/google/googletest/
branch = v1.14.x
[submodule "third-party/inputtino"]
path = third-party/inputtino
url = https://github.com/games-on-whales/inputtino.git
branch = stable
[submodule "third-party/moonlight-common-c"]
path = third-party/moonlight-common-c
url = https://github.com/moonlight-stream/moonlight-common-c.git
Expand Down
21 changes: 20 additions & 1 deletion cmake/compile_definitions/linux.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,33 @@ if(${SUNSHINE_ENABLE_TRAY} AND ${SUNSHINE_TRAY} EQUAL 0 AND SUNSHINE_REQUIRE_TRA
message(FATAL_ERROR "Tray icon is required")
endif()

if(${SUNSHINE_USE_LEGACY_INPUT}) # TODO: Remove this legacy option after the next stable release
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/input/legacy_input.cpp")
else()
# These need to be set before adding the inputtino subdirectory in order for them to be picked up
set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}")
set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}")

add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
file(GLOB_RECURSE INPUTTINO_SOURCES
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})

# build libevdev before the libinputtino target
if(EXTERNAL_PROJECT_LIBEVDEV_USED)
add_dependencies(libinputtino libevdev)
endif()
endif()

list(APPEND PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/linux/publish.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/graphics.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/graphics.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/misc.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/misc.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/audio.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/input.cpp"
"${CMAKE_SOURCE_DIR}/third-party/glad/src/egl.c"
"${CMAKE_SOURCE_DIR}/third-party/glad/src/gl.c"
"${CMAKE_SOURCE_DIR}/third-party/glad/include/EGL/eglplatform.h"
Expand Down
3 changes: 3 additions & 0 deletions cmake/dependencies/libevdev_Sunshine.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ else()
endif()

if(EVDEV_INCLUDE_DIR AND EVDEV_LIBRARY)
message(STATUS "Found libevdev library: ${EVDEV_LIBRARY}")
message(STATUS "Found libevdev include directory: ${EVDEV_INCLUDE_DIR}")

include_directories(SYSTEM ${EVDEV_INCLUDE_DIR})
list(APPEND PLATFORM_LIBRARIES ${EVDEV_LIBRARY})
else()
Expand Down
16 changes: 1 addition & 15 deletions cmake/macros/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,11 @@ macro(find_package) # cmake-lint: disable=C0103
string(TOLOWER "${ARGV0}" ARGV0_LOWER)
if(
(("${ARGV0_LOWER}" STREQUAL "boost") AND DEFINED FETCH_CONTENT_BOOST_USED) OR
(("${ARGV0_LOWER}" STREQUAL "libevdev") AND DEFINED FETCH_CONTENT_LIBEVDEV_USED)
(("${ARGV0_LOWER}" STREQUAL "libevdev") AND DEFINED EXTERNAL_PROJECT_LIBEVDEV_USED)
)
# Do nothing, as the package has already been fetched
else()
# Call the original find_package function
_find_package(${ARGV})
endif()
endmacro()

# override pkg_check_modules function
macro(pkg_check_modules) # cmake-lint: disable=C0103
string(TOLOWER "${ARGV0}" ARGV0_LOWER)
if(
(("${ARGV0_LOWER}" STREQUAL "boost") AND DEFINED FETCH_CONTENT_BOOST_USED) OR
(("${ARGV0_LOWER}" STREQUAL "libevdev") AND DEFINED FETCH_CONTENT_LIBEVDEV_USED)
)
# Do nothing, as the package has already been fetched
else()
# Call the original pkg_check_modules function
_pkg_check_modules(${ARGV})
endif()
endmacro()
2 changes: 2 additions & 0 deletions cmake/prep/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ elseif(UNIX) # Linux
"Enable building wayland specific code." ON)
option(SUNSHINE_ENABLE_X11
"Enable X11 grab if available." ON)
option(SUNSHINE_USE_LEGACY_INPUT # TODO: Remove this legacy option after the next stable release
"Use the legacy virtual input implementation." OFF)
endif()
7 changes: 4 additions & 3 deletions docs/source/about/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,13 @@ Install
The `deb`, `rpm`, `zst`, `Flatpak` and `AppImage` packages should handle these steps automatically.
Third party packages may not.

Sunshine needs access to `uinput` to create mouse and gamepad events.
Sunshine needs access to `uinput` to create mouse and gamepad virtual devices and (optionally) to `uhid`
in order to emulate a PS5 DualSense joypad with Gyro, Acceleration and Touchpad support.

#. Create and reload `udev` rules for uinput.
#. Create and reload `udev` rules for `uinput` and `uhid`.
.. code-block:: bash
echo 'KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"' | \
echo 'KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"\nKERNEL=="uhid", TAG+="uaccess"' | \
sudo tee /etc/udev/rules.d/60-sunshine.rules
sudo udevadm control --reload-rules
sudo udevadm trigger
Expand Down
17 changes: 14 additions & 3 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,8 @@ namespace config {
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period

{
platf::supported_gamepads().front().data(),
platf::supported_gamepads().front().size(),
platf::supported_gamepads(nullptr).front().name.data(),
platf::supported_gamepads(nullptr).front().name.size(),
}, // Default gamepad
true, // back as touchpad click enabled (manual DS4 only)
true, // client gamepads with motion events are emulated as DS4
Expand Down Expand Up @@ -938,6 +938,17 @@ namespace config {
return ret;
}

std::vector<std::string_view> &
get_supported_gamepad_options() {
const auto options = platf::supported_gamepads(nullptr);
static std::vector<std::string_view> opts {};
opts.reserve(options.size());
for (auto &opt : options) {
opts.emplace_back(opt.name);
}
return opts;
}

void
apply_config(std::unordered_map<std::string, std::string> &&vars) {
if (!fs::exists(stream.file_apps.c_str())) {
Expand Down Expand Up @@ -1086,7 +1097,7 @@ namespace config {
input.key_repeat_delay = std::chrono::milliseconds { to };
}

string_restricted_f(vars, "gamepad"s, input.gamepad, platf::supported_gamepads());
string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options());
bool_f(vars, "ds4_back_as_touchpad_click", input.ds4_back_as_touchpad_click);
bool_f(vars, "motion_as_ds4", input.motion_as_ds4);
bool_f(vars, "touchpad_as_ds4", input.touchpad_as_ds4);
Expand Down
42 changes: 27 additions & 15 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ namespace input {

void
free_gamepad(platf::input_t &platf_input, int id) {
platf::gamepad(platf_input, id, platf::gamepad_state_t {});
platf::gamepad_update(platf_input, id, platf::gamepad_state_t {});
platf::free_gamepad(platf_input, id);

free_id(gamepadMask, id);
Expand Down Expand Up @@ -711,28 +711,28 @@ namespace input {
if (!release) {
// Press any synthetic modifiers required for this key
if (synthetic_modifiers & MODIFIER_SHIFT) {
platf::keyboard(platf_input, VKEY_SHIFT, false, flags);
platf::keyboard_update(platf_input, VKEY_SHIFT, false, flags);
}
if (synthetic_modifiers & MODIFIER_CTRL) {
platf::keyboard(platf_input, VKEY_CONTROL, false, flags);
platf::keyboard_update(platf_input, VKEY_CONTROL, false, flags);
}
if (synthetic_modifiers & MODIFIER_ALT) {
platf::keyboard(platf_input, VKEY_MENU, false, flags);
platf::keyboard_update(platf_input, VKEY_MENU, false, flags);
}
}

platf::keyboard(platf_input, map_keycode(key_code), release, flags);
platf::keyboard_update(platf_input, map_keycode(key_code), release, flags);

if (!release) {
// Raise any synthetic modifier keys we pressed
if (synthetic_modifiers & MODIFIER_SHIFT) {
platf::keyboard(platf_input, VKEY_SHIFT, true, flags);
platf::keyboard_update(platf_input, VKEY_SHIFT, true, flags);
}
if (synthetic_modifiers & MODIFIER_CTRL) {
platf::keyboard(platf_input, VKEY_CONTROL, true, flags);
platf::keyboard_update(platf_input, VKEY_CONTROL, true, flags);
}
if (synthetic_modifiers & MODIFIER_ALT) {
platf::keyboard(platf_input, VKEY_MENU, true, flags);
platf::keyboard_update(platf_input, VKEY_MENU, true, flags);
}
}
}
Expand Down Expand Up @@ -963,7 +963,7 @@ namespace input {
contact_area.second,
};

platf::touch(input->client_context.get(), abs_port, touch);
platf::touch_update(input->client_context.get(), abs_port, touch);
}

/**
Expand Down Expand Up @@ -1022,7 +1022,7 @@ namespace input {
contact_area.second,
};

platf::pen(input->client_context.get(), abs_port, pen);
platf::pen_update(input->client_context.get(), abs_port, pen);
}

/**
Expand Down Expand Up @@ -1211,18 +1211,18 @@ namespace input {
// Force the back button up
gamepad.back_button_state = button_state_e::UP;
state.buttonFlags &= ~platf::BACK;
platf::gamepad(platf_input, gamepad.id, state);
platf::gamepad_update(platf_input, gamepad.id, state);

// Press Home button
state.buttonFlags |= platf::HOME;
platf::gamepad(platf_input, gamepad.id, state);
platf::gamepad_update(platf_input, gamepad.id, state);

// Sleep for a short time to allow the input to be detected
std::this_thread::sleep_for(std::chrono::milliseconds(100));

// Release Home button
state.buttonFlags &= ~platf::HOME;
platf::gamepad(platf_input, gamepad.id, state);
platf::gamepad_update(platf_input, gamepad.id, state);

gamepad.back_timeout_id = nullptr;
};
Expand All @@ -1236,7 +1236,7 @@ namespace input {
}
}

platf::gamepad(platf_input, gamepad.id, gamepad_state);
platf::gamepad_update(platf_input, gamepad.id, gamepad_state);

gamepad.gamepad_state = gamepad_state;
}
Expand Down Expand Up @@ -1665,7 +1665,7 @@ namespace input {
// already released
continue;
}
platf::keyboard(platf_input, vk_from_kpid(kp.first) & 0x00FF, true, flags_from_kpid(kp.first));
platf::keyboard_update(platf_input, vk_from_kpid(kp.first) & 0x00FF, true, flags_from_kpid(kp.first));
key_press[kp.first] = false;
}
});
Expand All @@ -1685,6 +1685,18 @@ namespace input {
return std::make_unique<deinit_t>();
}

bool
probe_gamepads() {
auto input = static_cast<platf::input_t *>(platf_input.get());
const auto gamepads = platf::supported_gamepads(input);
for (auto &gamepad : gamepads) {
if (gamepad.is_enabled && gamepad.name != "auto") {
return false;
}
}
return true;
}

std::shared_ptr<input_t>
alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>(
Expand Down
3 changes: 3 additions & 0 deletions src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace input {
[[nodiscard]] std::unique_ptr<platf::deinit_t>
init();

bool
probe_gamepads();

std::shared_ptr<input_t>
alloc(safe::mail_t mail);

Expand Down
5 changes: 5 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ main(int argc, char *argv[]) {

reed_solomon_init();
auto input_deinit_guard = input::init();

if (input::probe_gamepads()) {
BOOST_LOG(warning) << "No gamepad input is available"sv;
}

if (video::probe_encoders()) {
BOOST_LOG(error) << "Video failed to find working encoder"sv;
}
Expand Down
22 changes: 16 additions & 6 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ namespace platf {
constexpr std::uint32_t TOUCHPAD_BUTTON = 0x100000;
constexpr std::uint32_t MISC_BUTTON = 0x200000;

struct supported_gamepad_t {
std::string name;
bool is_enabled;
std::string reason_disabled;
};

enum class gamepad_feedback_e {
rumble,
rumble_triggers,
Expand Down Expand Up @@ -695,9 +701,9 @@ namespace platf {
void
hscroll(input_t &input, int distance);
void
keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags);
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
void
gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state);
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
void
unicode(input_t &input, char *utf8, int size);

Expand All @@ -718,7 +724,7 @@ namespace platf {
* @param touch The touch event.
*/
void
touch(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);

/**
* @brief Sends a pen event to the OS.
Expand All @@ -727,7 +733,7 @@ namespace platf {
* @param pen The pen event.
*/
void
pen(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);

/**
* @brief Sends a gamepad touch event to the OS.
Expand Down Expand Up @@ -784,6 +790,10 @@ namespace platf {
[[nodiscard]] std::unique_ptr<deinit_t>
init();

std::vector<std::string_view> &
supported_gamepads();
/**
* @brief Gets the supported gamepads for this platform backend.
* @return Vector of gamepad options and status.
*/
std::vector<supported_gamepad_t> &
supported_gamepads(input_t *input);
} // namespace platf
Loading

0 comments on commit 0ac4e3c

Please sign in to comment.