diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index 597a90291f..068ef13b8e 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -192,3 +192,13 @@ if(MSVC) # Add precompiled header target_link_libraries(inputcommon PRIVATE use_pch) endif() + +if(TARGET Hidapi::Hidapi) + target_sources(inputcommon + PRIVATE + ControllerInterface/SteamDeck/SteamDeck.cpp + ControllerInterface/SteamDeck/SteamDeck.h + ) + target_link_libraries(inputcommon PUBLIC Hidapi::Hidapi) + target_compile_definitions(inputcommon PRIVATE -DHAVE_HIDAPI=1) +endif() diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 5b7e27ffe1..dfe60c1eae 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -34,6 +34,9 @@ #ifdef CIFACE_USE_DUALSHOCKUDPCLIENT #include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h" #endif +#ifdef CIFACE_USE_STEAMDECK +#include "InputCommon/ControllerInterface/SteamDeck/SteamDeck.h" +#endif ControllerInterface g_controller_interface; @@ -78,6 +81,9 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi) #ifdef CIFACE_USE_DUALSHOCKUDPCLIENT m_input_backends.emplace_back(ciface::DualShockUDPClient::CreateInputBackend(this)); #endif +#ifdef CIFACE_USE_STEAMDECK + m_input_backends.emplace_back(ciface::SteamDeck::CreateInputBackend(this)); +#endif // Don't allow backends to add devices before the first RefreshDevices() as they will be cleaned // there. Or they'd end up waiting on the devices mutex if populated from another thread. diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 59909aeec8..6dc0afafe8 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -34,6 +34,9 @@ #if defined(HAVE_SDL2) #define CIFACE_USE_SDL #endif +#if defined(HAVE_HIDAPI) +#define CIFACE_USE_STEAMDECK +#endif namespace ciface { diff --git a/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.cpp b/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.cpp new file mode 100644 index 0000000000..cfdf3e9649 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.cpp @@ -0,0 +1,304 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "InputCommon/ControllerInterface/SteamDeck/SteamDeck.h" + +#include + +#include + +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/MathUtil.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +namespace ciface::SteamDeck +{ +constexpr std::string_view STEAMDECK_SOURCE_NAME = "SteamDeck"; + +struct DeckInputReport +{ + // +0 + u8 major_ver; + u8 minor_ver; + u8 report_type; + u8 report_sz; + + u32 frame; + + // +8 + u32 buttons0; + u32 buttons1; + + // +16 + s16 l_pad_x; + s16 l_pad_y; + s16 r_pad_x; + s16 r_pad_y; + + // +24 + s16 accel_x; + s16 accel_y; + s16 accel_z; + s16 gyro_pitch; + s16 gyro_roll; + s16 gyro_yaw; + s16 pose_quat_w; + s16 pose_quat_x; + s16 pose_quat_y; + s16 pose_quat_z; + + // +44 + u16 l_trig; + u16 r_trig; + + // +48 + s16 l_stick_x; + s16 l_stick_y; + s16 r_stick_x; + s16 r_stick_y; + + // +56 + u16 l_pad_force; + u16 r_pad_force; + s16 l_stick_capa; + s16 r_stick_capa; +}; +static_assert(sizeof(DeckInputReport) == 64); + +class Device final : public Core::Device +{ +private: + class Button final : public Input + { + public: + Button(const char* name, const u32& buttons, u32 mask) + : m_name(name), m_buttons(buttons), m_mask(mask) + { + } + std::string GetName() const override { return m_name; } + ControlState GetState() const override { return (m_buttons & m_mask) != 0; } + + private: + const char* const m_name; + const u32& m_buttons; + const u32 m_mask; + }; + + template + class AnalogInput : public Input + { + public: + AnalogInput(const char* name, const T& input, ControlState range) + : m_name(name), m_input(input), m_range(range) + { + } + std::string GetName() const final override { return m_name; } + ControlState GetState() const final override { return ControlState(m_input) / m_range; } + + private: + const char* m_name; + const T& m_input; + const ControlState m_range; + }; + + class MotionInput final : public AnalogInput + { + public: + using AnalogInput::AnalogInput; + bool IsDetectable() const override { return false; } + }; + +public: + Device(hid_device* device); + std::string GetName() const final override; + std::string GetSource() const final override; + void UpdateInput() override; + +private: + hid_device* m_device; + DeckInputReport m_latest_input; +}; + +class InputBackend final : public ciface::InputBackend +{ +public: + InputBackend(ControllerInterface* controller_interface); + void PopulateDevices() override; +}; + +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) +{ + return std::make_unique(controller_interface); +} + +InputBackend::InputBackend(ControllerInterface* controller_interface) + : ciface::InputBackend(controller_interface) +{ +} + +void InputBackend::PopulateDevices() +{ + INFO_LOG_FMT(CONTROLLERINTERFACE, "SteamDeck PopulateDevices"); + + auto possible_devices = hid_enumerate(0x28de, 0x1205); + bool was_found = false; + std::string found_path; + auto this_device = possible_devices; + + while (this_device) + { + INFO_LOG_FMT(CONTROLLERINTERFACE, "Found {} (interface {})", this_device->path, + this_device->interface_number); + + if (this_device->interface_number == 2) + { + was_found = true; + found_path = this_device->path; + break; + } + + this_device = this_device->next; + } + + hid_free_enumeration(possible_devices); + + if (was_found) + { + INFO_LOG_FMT(CONTROLLERINTERFACE, "Found Steam Deck controls at {}", found_path); + } + else + { + INFO_LOG_FMT(CONTROLLERINTERFACE, "No Steam Deck interface found"); + return; + } + + auto deck_dev = hid_open_path(found_path.c_str()); + if (!deck_dev) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "Steam Deck controls could not be opened"); + return; + } + if (hid_set_nonblocking(deck_dev, 1) != 0) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "Steam Deck controls could not be set nonblocking"); + return; + } + + GetControllerInterface().RemoveDevice( + [](const auto* dev) { return dev->GetSource() == STEAMDECK_SOURCE_NAME; }); + + GetControllerInterface().AddDevice(std::make_shared(deck_dev)); +} + +Device::Device(hid_device* device) : m_device{device} +{ + // clang-format off + AddInput(new Button("R2 Full Pull", m_latest_input.buttons0, 0x00000001)); + AddInput(new Button("L2 Full Pull", m_latest_input.buttons0, 0x00000002)); + AddInput(new Button("R1", m_latest_input.buttons0, 0x00000004)); + AddInput(new Button("L1", m_latest_input.buttons0, 0x00000008)); + AddInput(new Button("Y", m_latest_input.buttons0, 0x00000010)); + AddInput(new Button("B", m_latest_input.buttons0, 0x00000020)); + AddInput(new Button("X", m_latest_input.buttons0, 0x00000040)); + AddInput(new Button("A", m_latest_input.buttons0, 0x00000080)); + AddInput(new Button("D-Pad Up", m_latest_input.buttons0, 0x00000100)); + AddInput(new Button("D-Pad Right", m_latest_input.buttons0, 0x00000200)); + AddInput(new Button("D-Pad Left", m_latest_input.buttons0, 0x00000400)); + AddInput(new Button("D-Pad Down", m_latest_input.buttons0, 0x00000800)); + AddInput(new Button("View", m_latest_input.buttons0, 0x00001000)); + AddInput(new Button("Steam", m_latest_input.buttons0, 0x00002000)); + AddInput(new Button("Menu", m_latest_input.buttons0, 0x00004000)); + AddInput(new Button("L5", m_latest_input.buttons0, 0x00008000)); + AddInput(new Button("R5", m_latest_input.buttons0, 0x00010000)); + AddInput(new Button("Left Trackpad Click", m_latest_input.buttons0, 0x00020000)); + AddInput(new Button("Right Trackpad Click", m_latest_input.buttons0, 0x00040000)); + AddInput(new Button("Left Trackpad Touch", m_latest_input.buttons0, 0x00080000)); + AddInput(new Button("Right Trackpad Touch", m_latest_input.buttons0, 0x00100000)); + AddInput(new Button("L3", m_latest_input.buttons0, 0x00400000)); + AddInput(new Button("R3", m_latest_input.buttons0, 0x04000000)); + AddInput(new Button("L4", m_latest_input.buttons1, 0x00000200)); + AddInput(new Button("R4", m_latest_input.buttons1, 0x00000400)); + AddInput(new Button("Left Stick Touch", m_latest_input.buttons1, 0x00004000)); + AddInput(new Button("Right Stick Touch", m_latest_input.buttons1, 0x00008000)); + AddInput(new Button("Quick Access", m_latest_input.buttons1, 0x00040000)); + + AddInput(new AnalogInput("L2", m_latest_input.l_trig, 32767)); + AddInput(new AnalogInput("R2", m_latest_input.r_trig, 32767)); + AddInput(new AnalogInput("Left Stick X-", m_latest_input.l_stick_x, -32767)); + AddInput(new AnalogInput("Left Stick X+", m_latest_input.l_stick_x, 32767)); + AddInput(new AnalogInput("Left Stick Y-", m_latest_input.l_stick_y, -32767)); + AddInput(new AnalogInput("Left Stick Y+", m_latest_input.l_stick_y, 32767)); + AddInput(new AnalogInput("Right Stick X-", m_latest_input.r_stick_x, -32767)); + AddInput(new AnalogInput("Right Stick X+", m_latest_input.r_stick_x, 32767)); + AddInput(new AnalogInput("Right Stick Y-", m_latest_input.r_stick_y, -32767)); + AddInput(new AnalogInput("Right Stick Y+", m_latest_input.r_stick_y, 32767)); + AddInput(new AnalogInput("Left Trackpad X-", m_latest_input.l_pad_x, -32767)); + AddInput(new AnalogInput("Left Trackpad X+", m_latest_input.l_pad_x, 32767)); + AddInput(new AnalogInput("Left Trackpad Y-", m_latest_input.l_pad_y, -32767)); + AddInput(new AnalogInput("Left Trackpad Y+", m_latest_input.l_pad_y, 32767)); + AddInput(new AnalogInput("Right Trackpad X-", m_latest_input.r_pad_x, -32767)); + AddInput(new AnalogInput("Right Trackpad X+", m_latest_input.r_pad_x, 32767)); + AddInput(new AnalogInput("Right Trackpad Y-", m_latest_input.r_pad_y, -32767)); + AddInput(new AnalogInput("Right Trackpad Y+", m_latest_input.r_pad_y, 32767)); + AddInput(new AnalogInput("Left Trackpad Pressure", m_latest_input.l_pad_force, 32767)); + AddInput(new AnalogInput("Right Trackpad Pressure", m_latest_input.r_pad_force, 32767)); + AddInput(new AnalogInput("Left Stick Capacitance", m_latest_input.l_stick_capa, 32767)); + AddInput(new AnalogInput("Right Stick Capacitance", m_latest_input.r_stick_capa, 32767)); + + // 0x4000 LSBs = 1 g + // final output in m/s^2 + constexpr auto accel_scale = 16384.0 / MathUtil::GRAVITY_ACCELERATION; + AddInput(new MotionInput("Accel Up", m_latest_input.accel_z, -accel_scale)); + AddInput(new MotionInput("Accel Down", m_latest_input.accel_z, accel_scale)); + AddInput(new MotionInput("Accel Left", m_latest_input.accel_x, accel_scale)); + AddInput(new MotionInput("Accel Right", m_latest_input.accel_x, -accel_scale)); + AddInput(new MotionInput("Accel Forward", m_latest_input.accel_y, -accel_scale)); + AddInput(new MotionInput("Accel Backward", m_latest_input.accel_y, accel_scale)); + + // 16.384 (?) LSBs = 1 deg / s + // final output in rads / s + constexpr auto gyro_scale = 16.384 * 360.0 / MathUtil::TAU; + AddInput(new MotionInput("Gyro Pitch Up", m_latest_input.gyro_pitch, gyro_scale)); + AddInput(new MotionInput("Gyro Pitch Down", m_latest_input.gyro_pitch, -gyro_scale)); + AddInput(new MotionInput("Gyro Roll Left", m_latest_input.gyro_roll, -gyro_scale)); + AddInput(new MotionInput("Gyro Roll Right", m_latest_input.gyro_roll, gyro_scale)); + AddInput(new MotionInput("Gyro Yaw Left", m_latest_input.gyro_yaw, gyro_scale)); + AddInput(new MotionInput("Gyro Yaw Right", m_latest_input.gyro_yaw, -gyro_scale)); + // clang-format on +} + +std::string Device::GetName() const +{ + return "Steam Deck"; +} + +std::string Device::GetSource() const +{ + return std::string(STEAMDECK_SOURCE_NAME); +} + +void Device::UpdateInput() +{ + DeckInputReport rpt; + bool got_anything = false; + // Read all available input reports (processing only the most recent one). + while (hid_read(m_device, reinterpret_cast(&rpt), sizeof(rpt)) > 0) + { + got_anything = true; + } + // In case there were no reports available to be read, bail early. + if (!got_anything) + return; + + if (rpt.major_ver != 0x01 || rpt.minor_ver != 0x00 || rpt.report_type != 0x09 || + rpt.report_sz != sizeof(rpt)) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "Steam Deck bad report"); + return; + } + + m_latest_input = rpt; +} + +} // namespace ciface::SteamDeck diff --git a/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.h b/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.h new file mode 100644 index 0000000000..c22ef2b5f1 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.h @@ -0,0 +1,13 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "InputCommon/ControllerInterface/InputBackend.h" + +#include + +namespace ciface::SteamDeck +{ +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); +}