From 85aa4f095b119e98620451a0c19c80f656d944a6 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Tue, 15 Aug 2023 07:37:37 +0000 Subject: [PATCH] Linux/MacOS: Add wiimote support via HIDAPI (#934) --- CMakeLists.txt | 9 ++ src/input/CMakeLists.txt | 38 ++++-- src/input/InputManager.h | 3 + .../api/Wiimote/WiimoteControllerProvider.cpp | 128 +++++++----------- src/input/api/Wiimote/WiimoteMessages.h | 2 +- .../api/Wiimote/hidapi/HidapiWiimote.cpp | 55 ++++++++ src/input/api/Wiimote/hidapi/HidapiWiimote.h | 23 ++++ vcpkg.json | 4 + 8 files changed, 173 insertions(+), 89 deletions(-) create mode 100644 src/input/api/Wiimote/hidapi/HidapiWiimote.cpp create mode 100644 src/input/api/Wiimote/hidapi/HidapiWiimote.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 57615e86..34a28a06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,9 @@ if (WIN32) option(ENABLE_XINPUT "Enables the usage of XInput" ON) option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON) add_compile_definitions(HAS_DIRECTINPUT) + set(ENABLE_WIIMOTE ON) +elseif (UNIX) + option(ENABLE_HIDAPI "Build with HIDAPI" ON) endif() option(ENABLE_SDL "Enables the SDLController backend" ON) @@ -155,6 +158,12 @@ if (ENABLE_DISCORD_RPC) target_include_directories(discord-rpc INTERFACE ./dependencies/discord-rpc/include) endif() +if (ENABLE_HIDAPI) + find_package(hidapi REQUIRED) + set(ENABLE_WIIMOTE ON) + add_compile_definitions(HAS_HIDAPI) +endif () + if(UNIX AND NOT APPLE) if(ENABLE_FERAL_GAMEMODE) add_compile_definitions(ENABLE_FERAL_GAMEMODE) diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index ecb88cd2..9f542371 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -44,18 +44,6 @@ add_library(CemuInput set_property(TARGET CemuInput PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") if(WIN32) - # Native wiimote (Win32 only for now) - target_sources(CemuInput PRIVATE - api/Wiimote/WiimoteControllerProvider.h - api/Wiimote/windows/WinWiimoteDevice.cpp - api/Wiimote/windows/WinWiimoteDevice.h - api/Wiimote/WiimoteControllerProvider.cpp - api/Wiimote/WiimoteMessages.h - api/Wiimote/NativeWiimoteController.h - api/Wiimote/NativeWiimoteController.cpp - api/Wiimote/WiimoteDevice.h - ) - # XInput target_sources(CemuInput PRIVATE api/XInput/XInputControllerProvider.cpp @@ -73,6 +61,29 @@ if(WIN32) ) endif() +if (ENABLE_WIIMOTE) + target_sources(CemuInput PRIVATE + api/Wiimote/WiimoteControllerProvider.h + api/Wiimote/WiimoteControllerProvider.cpp + api/Wiimote/WiimoteMessages.h + api/Wiimote/NativeWiimoteController.h + api/Wiimote/NativeWiimoteController.cpp + api/Wiimote/WiimoteDevice.h + ) + if (ENABLE_HIDAPI) + target_sources(CemuInput PRIVATE + api/Wiimote/hidapi/HidapiWiimote.cpp + api/Wiimote/hidapi/HidapiWiimote.h + ) + elseif (WIN32) + target_sources(CemuInput PRIVATE + api/Wiimote/windows/WinWiimoteDevice.cpp + api/Wiimote/windows/WinWiimoteDevice.h + ) + endif() +endif () + + target_include_directories(CemuInput PUBLIC "../") target_link_libraries(CemuInput PRIVATE @@ -87,6 +98,9 @@ target_link_libraries(CemuInput PRIVATE pugixml::pugixml SDL2::SDL2 ) +if (ENABLE_HIDAPI) + target_link_libraries(CemuInput PRIVATE hidapi::hidapi) +endif() if (ENABLE_WXWIDGETS) target_link_libraries(CemuInput PRIVATE wx::base wx::core) diff --git a/src/input/InputManager.h b/src/input/InputManager.h index 848c4810..345f7ba0 100644 --- a/src/input/InputManager.h +++ b/src/input/InputManager.h @@ -3,6 +3,9 @@ #if BOOST_OS_WINDOWS #include "input/api/DirectInput/DirectInputControllerProvider.h" #include "input/api/XInput/XInputControllerProvider.h" +#endif + +#if defined(HAS_HIDAPI) || BOOST_OS_WINDOWS #include "input/api/Wiimote/WiimoteControllerProvider.h" #endif diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index a742a9d3..0ebf88aa 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -2,7 +2,9 @@ #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteMessages.h" -#if BOOST_OS_WINDOWS +#ifdef HAS_HIDAPI +#include "input/api/Wiimote/hidapi/HidapiWiimote.h" +#elif BOOST_OS_WINDOWS #include "input/api/Wiimote/windows/WinWiimoteDevice.h" #endif @@ -36,7 +38,7 @@ std::vector> WiimoteControllerProvider::get_cont { // only add unknown, connected devices to our list const bool is_new_device = std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(), - [&device](const auto& it) { return *it.device == *device; }); + [device](const auto& it) { return *it.device == *device; }); if (is_new_device) { m_wiimotes.push_back(std::make_unique(device)); @@ -163,9 +165,7 @@ void WiimoteControllerProvider::reader_thread() { case kStatus: { -#ifdef WIIMOTE_DEBUG - printf("WiimoteControllerProvider::read_thread: kStatus\n"); -#endif + cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kStatus"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; new_state.flags = *data; @@ -183,9 +183,7 @@ void WiimoteControllerProvider::reader_thread() if (HAS_FLAG(new_state.flags, kExtensionConnected)) { -#ifdef WIIMOTE_DEBUG - printf("\tExtension flag is set\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension flag is set"); if(new_state.m_extension.index() == 0) request_extension(index); } @@ -199,9 +197,7 @@ void WiimoteControllerProvider::reader_thread() break; case kRead: { -#ifdef WIIMOTE_DEBUG - printf("WiimoteControllerProvider::read_thread: kRead\n"); -#endif + cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kRead"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; const uint8 error_flag = *data & 0xF, size = (*data >> 4) + 1; @@ -209,10 +205,9 @@ void WiimoteControllerProvider::reader_thread() if (error_flag) { + // 7 means that wiimote is already enabled or not available -#ifdef WIIMOTE_DEBUG - printf("Received error on data read 0x%x\n", error_flag); -#endif + cemuLog_logDebug(LogType::Force,"Received error on data read {:#x}", error_flag); continue; } @@ -220,9 +215,7 @@ void WiimoteControllerProvider::reader_thread() data += 2; if (address == (kRegisterCalibration & 0xFFFF)) { -#ifdef WIIMOTE_DEBUG - printf("Calibration received\n"); -#endif + cemuLog_logDebug(LogType::Force,"Calibration received"); cemu_assert(size == 8); @@ -255,17 +248,10 @@ void WiimoteControllerProvider::reader_thread() { if (size == 0xf) { -#ifdef WIIMOTE_DEBUG - printf("Extension type received but no extension connected\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension type received but no extension connected"); continue; } - -#ifdef WIIMOTE_DEBUG - printf("Extension type received\n"); -#endif - cemu_assert(size == 6); auto be_type = *(betype*)data; data += 6; // 48 @@ -274,42 +260,38 @@ void WiimoteControllerProvider::reader_thread() switch (be_type.value()) { case kExtensionNunchuck: -#ifdef WIIMOTE_DEBUG - printf("\tNunchuck\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: Nunchuck"); new_state.m_extension = NunchuckData{}; break; case kExtensionClassic: -#ifdef WIIMOTE_DEBUG - printf("\tClassic\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic"); new_state.m_extension = ClassicData{}; break; case kExtensionClassicPro: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic Pro"); + break; case kExtensionGuitar: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Guitar"); + break; case kExtensionDrums: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Drums"); + break; case kExtensionBalanceBoard: - break; + cemuLog_logDebug(LogType::Force,"Extension Type Received: Balance Board"); + break; case kExtensionMotionPlus: - //m_motion_plus = true; -#ifdef WIIMOTE_DEBUG - printf("\tMotion plus detected\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension Type Received: MotionPlus"); set_motion_plus(index, true); new_state.m_motion_plus = MotionPlusData{}; break; case kExtensionPartialyInserted: -#ifdef WIIMOTE_DEBUG - printf("\tExtension only partially inserted!\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension only partially inserted"); new_state.m_extension = {}; request_status(index); break; default: - new_state.m_extension = {}; + cemuLog_logDebug(LogType::Force,"Unknown extension: {:#x}", be_type.value()); + new_state.m_extension = {}; break; } @@ -319,9 +301,7 @@ void WiimoteControllerProvider::reader_thread() else if (address == (kRegisterExtensionCalibration & 0xFFFF)) { cemu_assert(size == 0x10); -#ifdef WIIMOTE_DEBUG - printf("Extension calibration received\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension calibration received"); std::visit( overloaded { @@ -337,9 +317,7 @@ void WiimoteControllerProvider::reader_thread() std::array zero{}; if (memcmp(zero.data(), data, zero.size()) == 0) { -#ifdef WIIMOTE_DEBUG - printf("\tExtension calibration data is zero!\n"); -#endif + cemuLog_logDebug(LogType::Force,"Extension calibration data is zero"); return; } @@ -372,15 +350,23 @@ void WiimoteControllerProvider::reader_thread() } else { -#ifdef WIIMOTE_DEBUG - printf("Unhandled read data received\n"); -#endif - continue; + cemuLog_logDebug(LogType::Force,"Unhandled read data received"); + continue; } update_report = true; } break; + case kAcknowledge: + { + new_state.buttons = *(uint16*)data & (~0x60E0); + data += 2; + const auto report_id = *data++; + const auto error = *data++; + if (error) + cemuLog_logDebug(LogType::Force, "Error {:#x} from output report {:#x}", error, report_id); + break; + } case kDataCore: { // 30 BB BB @@ -476,10 +462,7 @@ void WiimoteControllerProvider::reader_thread() orientation /= tmp;*/ mp.orientation = orientation; -#ifdef WIIMOTE_DEBUG - printf("\tmp: %.2lf %.2lf %.2lf\n", mp.orientation.x, mp.orientation.y, - mp.orientation.z); -#endif + cemuLog_logDebug(LogType::Force,"MotionPlus: {:.2f}, {:.2f} {:.2f}", mp.orientation.x, mp.orientation.y, mp.orientation.z); }, [data](NunchuckData& nunchuck) mutable { @@ -553,12 +536,11 @@ void WiimoteControllerProvider::reader_thread() zero3, zero4 ); -#ifdef WIIMOTE_DEBUG - printf("\tn: %d,%d | %lf - %lf | %.2lf %.2lf %.2lf\n", nunchuck.z, nunchuck.c, - nunchuck.axis.x, nunchuck.axis.y, - RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), - RadToDeg(nunchuck.acceleration.z)); -#endif + cemuLog_logDebug(LogType::Force,"Nunchuck: Z={}, C={} | {}, {} | {:.2f}, {:.2f}, {:.2f}", + nunchuck.z, nunchuck.c, + nunchuck.axis.x, nunchuck.axis.y, + RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), + RadToDeg(nunchuck.acceleration.z)); }, [data](ClassicData& classic) mutable { @@ -592,11 +574,11 @@ void WiimoteControllerProvider::reader_thread() classic.trigger = classic.raw_trigger; classic.trigger /= 31.0f; -#ifdef WIIMOTE_DEBUG - printf("\tc: %d | %lf - %lf | %lf - %lf | %lf - %lf\n", classic.buttons, - classic.left_axis.x, classic.left_axis.y, classic.right_axis.x, - classic.right_axis.y, classic.trigger.x, classic.trigger.y); -#endif + cemuLog_logDebug(LogType::Force,"Classic Controller: Buttons={:b} | {}, {} | {}, {} | {}, {}", + classic.buttons, classic.left_axis.x, classic.left_axis.y, + classic.right_axis.x, classic.right_axis.y, classic.trigger.x, + classic.trigger.y); + } }, new_state.m_extension); @@ -609,9 +591,7 @@ void WiimoteControllerProvider::reader_thread() break; } default: -#ifdef WIIMOTE_DEBUG - printf("unhandled input packet id %d for wiimote\n", data[0]); -#endif + cemuLog_logDebug(LogType::Force,"unhandled input packet id {} for wiimote {}", id, index); } // update motion data @@ -694,7 +674,6 @@ void WiimoteControllerProvider::parse_acceleration(WiimoteState& wiimote_state, tmp -= calib.zero; acceleration = (wiimote_state.m_acceleration / tmp); - //printf("%d, %d, %d\n", (int)m_acceleration.x, (int)m_acceleration.y, (int)m_acceleration.z); const float pi_2 = (float)std::numbers::pi / 2.0f; wiimote_state.m_roll = std::atan2(acceleration.z, acceleration.x) - pi_2; } @@ -713,7 +692,6 @@ void WiimoteControllerProvider::rotate_ir(WiimoteState& wiimote_state) i++; if (!dot.visible) continue; - //printf("%d:\t%.02lf | %.02lf\n", i, dot.pos.x, dot.pos.y); // move to center, rotate and move back dot.pos -= 0.5f; dot.pos.x = (dot.pos.x * cos) + (dot.pos.y * (-sin)); @@ -973,9 +951,7 @@ void WiimoteControllerProvider::update_report_type(size_t index) else report_type = kDataCore; -#ifdef WIIMOTE_DEBUG - printf("Setting report type to %d\n", report_type); -#endif + cemuLog_logDebug(LogType::Force,"Setting report type to {}", report_type); send_packet(index, {kType, 0x04, report_type}); state.ir_camera.mode = set_ir_camera(index, true); diff --git a/src/input/api/Wiimote/WiimoteMessages.h b/src/input/api/Wiimote/WiimoteMessages.h index 712c3a5c..32dd4658 100644 --- a/src/input/api/Wiimote/WiimoteMessages.h +++ b/src/input/api/Wiimote/WiimoteMessages.h @@ -8,7 +8,7 @@ enum InputReportId : uint8 kStatus = 0x20, kRead = 0x21, - kWrite = 0x22, + kAcknowledge = 0x22, kDataCore = 0x30, kDataCoreAcc = 0x31, diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp new file mode 100644 index 00000000..7baad55d --- /dev/null +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp @@ -0,0 +1,55 @@ +#include "HidapiWiimote.h" + +static constexpr uint16 WIIMOTE_VENDOR_ID = 0x057e; +static constexpr uint16 WIIMOTE_PRODUCT_ID = 0x0306; +static constexpr uint16 WIIMOTE_MP_PRODUCT_ID = 0x0330; +static constexpr uint16 WIIMOTE_MAX_INPUT_REPORT_LENGTH = 22; + +HidapiWiimote::HidapiWiimote(hid_device* dev, uint64_t identifier) + : m_handle(dev), m_identifier(identifier) { + +} + +bool HidapiWiimote::write_data(const std::vector &data) { + return hid_write(m_handle, data.data(), data.size()) >= 0; +} + +std::optional> HidapiWiimote::read_data() { + std::array read_data{}; + const auto result = hid_read(m_handle, read_data.data(), WIIMOTE_MAX_INPUT_REPORT_LENGTH); + if (result < 0) + return {}; + return {{read_data.cbegin(), read_data.cbegin() + result}}; +} + +std::vector HidapiWiimote::get_devices() { + std::vector wiimote_devices; + hid_init(); + const auto device_enumeration = hid_enumerate(WIIMOTE_VENDOR_ID, 0x0); + + for (auto it = device_enumeration; it != nullptr; it = it->next){ + if (it->product_id != WIIMOTE_PRODUCT_ID && it->product_id != WIIMOTE_MP_PRODUCT_ID) + continue; + auto dev = hid_open_path(it->path); + if (!dev){ + cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, boost::nowide::narrow(hid_error(nullptr))); + } + else { + // Enough to have a unique id for each device within a session + uint64_t id = (static_cast(it->interface_number) << 32) | + (static_cast(it->usage_page) << 16) | + (it->usage); + wiimote_devices.push_back(std::make_shared(dev, id)); + } + } + hid_free_enumeration(device_enumeration); + return wiimote_devices; +} + +bool HidapiWiimote::operator==(WiimoteDevice& o) const { + return m_identifier == static_cast(o).m_identifier; +} + +HidapiWiimote::~HidapiWiimote() { + hid_close(m_handle); +} diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.h b/src/input/api/Wiimote/hidapi/HidapiWiimote.h new file mode 100644 index 00000000..6bd90dac --- /dev/null +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class HidapiWiimote : public WiimoteDevice { +public: + HidapiWiimote(hid_device* dev, uint64_t identifier); + ~HidapiWiimote() override; + + bool write_data(const std::vector &data) override; + std::optional> read_data() override; + bool operator==(WiimoteDevice& o) const override; + + static std::vector get_devices(); + +private: + hid_device* m_handle; + uint64_t m_identifier; + +}; + +using WiimoteDevice_t = HidapiWiimote; \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index d0facf8b..940ed748 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -26,6 +26,10 @@ "boost-static-string", "boost-random", "fmt", + { + "name": "hidapi", + "platform": "!windows" + }, "libpng", "glm", {