Files
Sunshine/src/platform/linux/input/inputtino_gamepad.cpp
ReenigneArcher c2420427b1 style: adjust clang-format rules (#2186)
Co-authored-by: Vithorio Polten <reach@vithor.io>
2025-01-19 22:34:47 -05:00

286 lines
12 KiB
C++

/**
* @file src/platform/linux/input/inputtino_gamepad.cpp
* @brief Definitions for inputtino gamepad input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
using namespace std::literals;
namespace platf::gamepad {
enum GamepadStatus {
UHID_NOT_AVAILABLE = 0, ///< UHID is not available
UINPUT_NOT_AVAILABLE, ///< UINPUT is not available
XINPUT_NOT_AVAILABLE, ///< XINPUT is not available
GAMEPAD_STATUS ///< Helper to indicate the number of status
};
auto create_xbox_one() {
return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408});
}
auto create_switch() {
return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e,
.product_id = 0x2009,
.version = 0x8111});
}
auto create_ds5() {
return inputtino::PS5Joypad::create({.name = "Sunshine DualSense (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
}
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
ControllerType selectedGamepadType;
if (config::input.gamepad == "xone"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
selectedGamepadType = XboxOneWired;
} else if (config::input.gamepad == "ds5"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv;
selectedGamepadType = DualSenseWired;
} else if (config::input.gamepad == "switch"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv;
selectedGamepadType = SwitchProWired;
} else if (metadata.type == LI_CTYPE_XBOX) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (auto-selected by client-reported type)"sv;
selectedGamepadType = XboxOneWired;
} else if (metadata.type == LI_CTYPE_PS) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = DualSenseWired;
} else if (metadata.type == LI_CTYPE_NINTENDO) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (auto-selected by client-reported type)"sv;
selectedGamepadType = SwitchProWired;
} else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by motion sensor presence)"sv;
selectedGamepadType = DualSenseWired;
} else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by touchpad presence)"sv;
selectedGamepadType = DualSenseWired;
} else {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv;
selectedGamepadType = XboxOneWired;
}
if (selectedGamepadType == XboxOneWired || selectedGamepadType == SwitchProWired) {
if (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO)) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has motion sensors, but they are not usable when emulating a joypad different from DS5"sv;
}
if (metadata.capabilities & LI_CCAP_TOUCHPAD) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has a touchpad, but it is not usable when emulating a joypad different from DS5"sv;
}
if (metadata.capabilities & LI_CCAP_RGB_LED) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating a joypad different from DS5"sv;
}
} else if (selectedGamepadType == DualSenseWired) {
if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have motion sensors active"sv;
}
if (!(metadata.capabilities & LI_CCAP_TOUCHPAD)) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have a touchpad"sv;
}
}
auto gamepad = std::make_shared<joypad_state>(joypad_state {});
auto on_rumble_fn = [feedback_queue, idx = id.clientRelativeIndex, gamepad](int low_freq, int high_freq) {
// Don't resend duplicate rumble data
if (gamepad->last_rumble.type == platf::gamepad_feedback_e::rumble && gamepad->last_rumble.data.rumble.lowfreq == low_freq && gamepad->last_rumble.data.rumble.highfreq == high_freq) {
return;
}
gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rumble(idx, low_freq, high_freq);
feedback_queue->raise(msg);
gamepad->last_rumble = msg;
};
switch (selectedGamepadType) {
case XboxOneWired:
{
auto xOne = create_xbox_one();
if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1;
}
}
case SwitchProWired:
{
auto switchPro = create_switch();
if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1;
}
}
case DualSenseWired:
{
auto ds5 = create_ds5();
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
// Don't resend duplicate LED data
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
return;
}
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
feedback_queue->raise(msg);
gamepad->last_rgb_led = msg;
});
// Activate the motion sensors
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1;
}
}
}
return -1;
}
void free(input_raw_t *raw, int nr) {
// This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device)
raw->gamepads[nr]->joypad.reset();
raw->gamepads[nr].reset();
}
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
auto gamepad = raw->gamepads[nr];
if (!gamepad) {
return;
}
std::visit([gamepad_state](inputtino::Joypad &gc) {
gc.set_pressed_buttons(gamepad_state.buttonFlags);
gc.set_stick(inputtino::Joypad::LS, gamepad_state.lsX, gamepad_state.lsY);
gc.set_stick(inputtino::Joypad::RS, gamepad_state.rsX, gamepad_state.rsY);
gc.set_triggers(gamepad_state.lt, gamepad_state.rt);
},
*gamepad->joypad);
}
void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
auto gamepad = raw->gamepads[touch.id.globalIndex];
if (!gamepad) {
return;
}
// Only the PS5 controller supports touch input
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
if (touch.pressure > 0.5) {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).place_finger(touch.pointerId, touch.x * inputtino::PS5Joypad::touchpad_width, touch.y * inputtino::PS5Joypad::touchpad_height);
} else {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId);
}
}
}
void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
auto gamepad = raw->gamepads[motion.id.globalIndex];
if (!gamepad) {
return;
}
// Only the PS5 controller supports motion
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
switch (motion.motionType) {
case LI_MOTION_TYPE_ACCEL:
std::get<inputtino::PS5Joypad>(*gamepad->joypad).set_motion(inputtino::PS5Joypad::ACCELERATION, motion.x, motion.y, motion.z);
break;
case LI_MOTION_TYPE_GYRO:
std::get<inputtino::PS5Joypad>(*gamepad->joypad).set_motion(inputtino::PS5Joypad::GYROSCOPE, deg2rad(motion.x), deg2rad(motion.y), deg2rad(motion.z));
break;
}
}
}
void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
auto gamepad = raw->gamepads[battery.id.globalIndex];
if (!gamepad) {
return;
}
// Only the PS5 controller supports battery reports
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
inputtino::PS5Joypad::BATTERY_STATE state;
switch (battery.state) {
case LI_BATTERY_STATE_CHARGING:
state = inputtino::PS5Joypad::BATTERY_CHARGHING;
break;
case LI_BATTERY_STATE_DISCHARGING:
state = inputtino::PS5Joypad::BATTERY_DISCHARGING;
break;
case LI_BATTERY_STATE_FULL:
state = inputtino::PS5Joypad::BATTERY_FULL;
break;
case LI_BATTERY_STATE_UNKNOWN:
case LI_BATTERY_STATE_NOT_PRESENT:
default:
return;
}
if (battery.percentage != LI_BATTERY_PERCENTAGE_UNKNOWN) {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).set_battery(state, battery.percentage);
}
}
}
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
if (!input) {
static std::vector gps {
supported_gamepad_t {"auto", true, ""},
supported_gamepad_t {"xone", false, ""},
supported_gamepad_t {"ds5", false, ""},
supported_gamepad_t {"switch", false, ""},
};
return gps;
}
auto ds5 = create_ds5();
auto switchPro = create_switch();
auto xOne = create_xbox_one();
static std::vector gps {
supported_gamepad_t {"auto", true, ""},
supported_gamepad_t {"xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : ""},
supported_gamepad_t {"ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : ""},
supported_gamepad_t {"switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : ""},
};
for (auto &[name, is_enabled, reason_disabled] : gps) {
if (!is_enabled) {
BOOST_LOG(warning) << "Gamepad " << name << " is disabled due to " << reason_disabled;
}
}
return gps;
}
} // namespace platf::gamepad