diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 2b098b7c60..9da0d28297 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -119,11 +119,11 @@ using ButtonDevice = InputDevice; using AnalogDevice = InputDevice>; /** - * A motion device is an input device that returns a tuple of accelerometer state vector and - * gyroscope state vector. + * A motion status is an object that returns a tuple of accelerometer state vector, + * gyroscope state vector, rotation state vector and orientation state matrix. * * For both vectors: - * x+ is the same direction as LEFT on D-pad. + * x+ is the same direction as RIGHT on D-pad. * y+ is normal to the touch screen, pointing outward. * z+ is the same direction as UP on D-pad. * @@ -133,8 +133,22 @@ using AnalogDevice = InputDevice>; * For gyroscope state vector: * Orientation is determined by right-hand rule. * Units: deg/sec + * + * For rotation state vector + * Units: rotations + * + * For orientation state matrix + * x vector + * y vector + * z vector */ -using MotionDevice = InputDevice, Common::Vec3>>; +using MotionStatus = std::tuple, Common::Vec3, Common::Vec3, + std::array>; + +/** + * A motion device is an input device that returns a motion status object + */ +using MotionDevice = InputDevice; /** * A touch device is an input device that returns a tuple of two floats and a bool. The floats are diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 7818c098fc..b65d59373b 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -250,6 +250,9 @@ void Controller_NPad::OnLoadInputDevices() { std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks[i].begin(), Input::CreateDevice); + std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, + players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END, + motions[i].begin(), Input::CreateDevice); } } @@ -266,6 +269,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { auto& rstick_entry = npad_pad_states[controller_idx].r_stick; const auto& button_state = buttons[controller_idx]; const auto& analog_state = sticks[controller_idx]; + const auto& motion_state = motions[controller_idx]; const auto [stick_l_x_f, stick_l_y_f] = analog_state[static_cast(JoystickId::Joystick_Left)]->GetStatus(); const auto [stick_r_x_f, stick_r_y_f] = @@ -360,6 +364,45 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* continue; } const u32 npad_index = static_cast(i); + + const std::array controller_sixaxes{ + &npad.sixaxis_full, &npad.sixaxis_handheld, &npad.sixaxis_dual_left, + &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right, + }; + + for (auto* sixaxis_sensor : controller_sixaxes) { + sixaxis_sensor->common.entry_count = 16; + sixaxis_sensor->common.total_entry_count = 17; + + const auto& last_entry = + sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index]; + + sixaxis_sensor->common.timestamp = core_timing.GetCPUTicks(); + sixaxis_sensor->common.last_entry_index = + (sixaxis_sensor->common.last_entry_index + 1) % 17; + + auto& cur_entry = sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index]; + + cur_entry.timestamp = last_entry.timestamp + 1; + cur_entry.timestamp2 = cur_entry.timestamp; + } + + // Try to read sixaxis sensor states + std::array motion_devices; + + if (sixaxis_sensors_enabled && Settings::values.motion_enabled) { + sixaxis_at_rest = true; + for (std::size_t e = 0; e < motion_devices.size(); ++e) { + const auto& device = motions[i][e]; + if (device) { + std::tie(motion_devices[e].accel, motion_devices[e].gyro, + motion_devices[e].rotation, motion_devices[e].orientation) = + device->GetStatus(); + sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f; + } + } + } + RequestPadStateUpdate(npad_index); auto& pad_state = npad_pad_states[npad_index]; @@ -377,6 +420,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* libnx_entry.connection_status.raw = 0; libnx_entry.connection_status.IsConnected.Assign(1); + auto& full_sixaxis_entry = + npad.sixaxis_full.sixaxis[npad.sixaxis_full.common.last_entry_index]; + auto& handheld_sixaxis_entry = + npad.sixaxis_handheld.sixaxis[npad.sixaxis_handheld.common.last_entry_index]; + auto& dual_left_sixaxis_entry = + npad.sixaxis_dual_left.sixaxis[npad.sixaxis_dual_left.common.last_entry_index]; + auto& dual_right_sixaxis_entry = + npad.sixaxis_dual_right.sixaxis[npad.sixaxis_dual_right.common.last_entry_index]; + auto& left_sixaxis_entry = + npad.sixaxis_left.sixaxis[npad.sixaxis_left.common.last_entry_index]; + auto& right_sixaxis_entry = + npad.sixaxis_right.sixaxis[npad.sixaxis_right.common.last_entry_index]; switch (controller_type) { case NPadControllerType::None: @@ -391,6 +446,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* main_controller.pad.r_stick = pad_state.r_stick; libnx_entry.connection_status.IsWired.Assign(1); + + if (sixaxis_sensors_enabled && motions[i][0]) { + full_sixaxis_entry.accel = motion_devices[0].accel; + full_sixaxis_entry.gyro = motion_devices[0].gyro; + full_sixaxis_entry.rotation = motion_devices[0].rotation; + full_sixaxis_entry.orientation = motion_devices[0].orientation; + } break; case NPadControllerType::Handheld: handheld_entry.connection_status.raw = 0; @@ -409,6 +471,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* libnx_entry.connection_status.IsRightJoyConnected.Assign(1); libnx_entry.connection_status.IsLeftJoyWired.Assign(1); libnx_entry.connection_status.IsRightJoyWired.Assign(1); + + if (sixaxis_sensors_enabled && motions[i][0]) { + handheld_sixaxis_entry.accel = motion_devices[0].accel; + handheld_sixaxis_entry.gyro = motion_devices[0].gyro; + handheld_sixaxis_entry.rotation = motion_devices[0].rotation; + handheld_sixaxis_entry.orientation = motion_devices[0].orientation; + } break; case NPadControllerType::JoyDual: dual_entry.connection_status.raw = 0; @@ -421,6 +490,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + + if (sixaxis_sensors_enabled && motions[i][0]) { + // Set motion for the left joycon + dual_left_sixaxis_entry.accel = motion_devices[0].accel; + dual_left_sixaxis_entry.gyro = motion_devices[0].gyro; + dual_left_sixaxis_entry.rotation = motion_devices[0].rotation; + dual_left_sixaxis_entry.orientation = motion_devices[0].orientation; + } + if (sixaxis_sensors_enabled && motions[i][1]) { + // Set motion for the right joycon + dual_right_sixaxis_entry.accel = motion_devices[1].accel; + dual_right_sixaxis_entry.gyro = motion_devices[1].gyro; + dual_right_sixaxis_entry.rotation = motion_devices[1].rotation; + dual_right_sixaxis_entry.orientation = motion_devices[1].orientation; + } break; case NPadControllerType::JoyLeft: left_entry.connection_status.raw = 0; @@ -431,6 +515,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* left_entry.pad.r_stick = pad_state.r_stick; libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); + + if (sixaxis_sensors_enabled && motions[i][0]) { + left_sixaxis_entry.accel = motion_devices[0].accel; + left_sixaxis_entry.gyro = motion_devices[0].gyro; + left_sixaxis_entry.rotation = motion_devices[0].rotation; + left_sixaxis_entry.orientation = motion_devices[0].orientation; + } break; case NPadControllerType::JoyRight: right_entry.connection_status.raw = 0; @@ -441,6 +532,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* right_entry.pad.r_stick = pad_state.r_stick; libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + + if (sixaxis_sensors_enabled && motions[i][1]) { + right_sixaxis_entry.accel = motion_devices[1].accel; + right_sixaxis_entry.gyro = motion_devices[1].gyro; + right_sixaxis_entry.rotation = motion_devices[1].rotation; + right_sixaxis_entry.orientation = motion_devices[1].orientation; + } break; case NPadControllerType::Pokeball: pokeball_entry.connection_status.raw = 0; @@ -582,6 +680,14 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo return gyroscope_zero_drift_mode; } +bool Controller_NPad::IsSixAxisSensorAtRest() const { + return sixaxis_at_rest; +} + +void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) { + sixaxis_sensors_enabled = six_axis_status; +} + void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) { const auto npad_index_1 = NPadIdToIndex(npad_id_1); const auto npad_index_2 = NPadIdToIndex(npad_id_2); diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index e9788da8d0..78e7c320bc 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -130,6 +130,8 @@ public: void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; + bool IsSixAxisSensorAtRest() const; + void SetSixAxisEnabled(bool six_axis_status); LedPattern GetLedPattern(u32 npad_id); void SetVibrationEnabled(bool can_vibrate); bool IsVibrationEnabled() const; @@ -252,6 +254,24 @@ private: }; static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size"); + struct SixAxisStates { + s64_le timestamp{}; + INSERT_PADDING_WORDS(2); + s64_le timestamp2{}; + Common::Vec3f accel{}; + Common::Vec3f gyro{}; + Common::Vec3f rotation{}; + std::array orientation{}; + s64_le always_one{1}; + }; + static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size"); + + struct SixAxisGeneric { + CommonHeader common{}; + std::array sixaxis{}; + }; + static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size"); + enum class ColorReadError : u32_le { ReadOk = 0, ColorDoesntExist = 1, @@ -281,6 +301,13 @@ private: }; }; + struct MotionDevice { + Common::Vec3f accel; + Common::Vec3f gyro; + Common::Vec3f rotation; + std::array orientation; + }; + struct NPadEntry { NPadType joy_styles; NPadAssignments pad_assignment; @@ -300,9 +327,12 @@ private: NPadGeneric pokeball_states; NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be // relying on this for the time being - INSERT_PADDING_BYTES( - 0x708 * - 6); // TODO(ogniK): SixAxis states, require more information before implementation + SixAxisGeneric sixaxis_full; + SixAxisGeneric sixaxis_handheld; + SixAxisGeneric sixaxis_dual_left; + SixAxisGeneric sixaxis_dual_right; + SixAxisGeneric sixaxis_left; + SixAxisGeneric sixaxis_right; NPadDevice device_type; NPadProperties properties; INSERT_PADDING_WORDS(1); @@ -325,14 +355,18 @@ private: NPadType style{}; std::array shared_memory_entries{}; - std::array< + using ButtonArray = std::array< std::array, Settings::NativeButton::NUM_BUTTONS_HID>, - 10> - buttons; - std::array< + 10>; + using StickArray = std::array< std::array, Settings::NativeAnalog::NUM_STICKS_HID>, - 10> - sticks; + 10>; + using MotionArray = std::array< + std::array, Settings::NativeMotion::NUM_MOTION_HID>, + 10>; + ButtonArray buttons; + StickArray sticks; + MotionArray motions; std::vector supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; // Each controller should have their own styleset changed event @@ -341,6 +375,8 @@ private: std::array connected_controllers{}; GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; bool can_controllers_vibrate{true}; + bool sixaxis_sensors_enabled{true}; + bool sixaxis_at_rest{true}; std::array npad_pad_states{}; bool is_in_lr_assignment_mode{false}; Core::System& system; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index bd3c2f26b9..302a255401 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -164,8 +164,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {56, nullptr, "ActivateJoyXpad"}, {58, nullptr, "GetJoyXpadLifoHandle"}, {59, nullptr, "GetJoyXpadIds"}, - {60, nullptr, "ActivateSixAxisSensor"}, - {61, nullptr, "DeactivateSixAxisSensor"}, + {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"}, + {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"}, {62, nullptr, "GetSixAxisSensorLifoHandle"}, {63, nullptr, "ActivateJoySixAxisSensor"}, {64, nullptr, "DeactivateJoySixAxisSensor"}, @@ -329,6 +329,31 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { rb.Push(0); } +void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop()}; + const auto applet_resource_user_id{rp.Pop()}; + applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(true); + LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop()}; + const auto applet_resource_user_id{rp.Pop()}; + applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(false); + + LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; @@ -484,13 +509,13 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { const auto handle{rp.Pop()}; const auto applet_resource_user_id{rp.Pop()}; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - // TODO (Hexagon12): Properly implement reading gyroscope values from controllers. - rb.Push(true); + rb.Push(applet_resource->GetController(HidController::NPad) + .IsSixAxisSensorAtRest()); } void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index efb07547f7..e04aaf1e95 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -86,6 +86,8 @@ private: void CreateAppletResource(Kernel::HLERequestContext& ctx); void ActivateXpad(Kernel::HLERequestContext& ctx); void GetXpadIDs(Kernel::HLERequestContext& ctx); + void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx); + void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx); void ActivateDebugPad(Kernel::HLERequestContext& ctx); void ActivateTouchScreen(Kernel::HLERequestContext& ctx); void ActivateMouse(Kernel::HLERequestContext& ctx); diff --git a/src/core/settings.h b/src/core/settings.h index 80f0d95a7e..9834f44bbf 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -152,6 +152,7 @@ struct Values { bool vibration_enabled; + bool motion_enabled; std::string motion_device; std::string touch_device; TouchscreenInput touchscreen; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index ea1a1cee63..062ec66b5d 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -12,6 +12,7 @@ #include "input_common/main.h" #include "input_common/motion_emu.h" #include "input_common/touch_from_button.h" +#include "input_common/udp/client.h" #include "input_common/udp/udp.h" #ifdef HAVE_SDL2 #include "input_common/sdl/sdl.h" @@ -40,7 +41,11 @@ struct InputSubsystem::Impl { sdl = SDL::Init(); #endif - udp = CemuhookUDP::Init(); + udp = std::make_shared(); + udpmotion = std::make_shared(udp); + Input::RegisterFactory("cemuhookudp", udpmotion); + udptouch = std::make_shared(udp); + Input::RegisterFactory("cemuhookudp", udptouch); } void Shutdown() { @@ -53,12 +58,17 @@ struct InputSubsystem::Impl { #ifdef HAVE_SDL2 sdl.reset(); #endif - udp.reset(); Input::UnregisterFactory("gcpad"); Input::UnregisterFactory("gcpad"); gcbuttons.reset(); gcanalog.reset(); + + Input::UnregisterFactory("cemuhookudp"); + Input::UnregisterFactory("cemuhookudp"); + + udpmotion.reset(); + udptouch.reset(); } [[nodiscard]] std::vector GetInputDevices() const { @@ -109,14 +119,28 @@ struct InputSubsystem::Impl { return {}; } + [[nodiscard]] MotionMapping GetMotionMappingForDevice( + const Common::ParamPackage& params) const { + if (!params.Has("class") || params.Get("class", "") == "any") { + return {}; + } + if (params.Get("class", "") == "cemuhookudp") { + // TODO return the correct motion device + return {}; + } + return {}; + } + std::shared_ptr keyboard; std::shared_ptr motion_emu; #ifdef HAVE_SDL2 std::unique_ptr sdl; #endif - std::unique_ptr udp; std::shared_ptr gcbuttons; std::shared_ptr gcanalog; + std::shared_ptr udpmotion; + std::shared_ptr udptouch; + std::shared_ptr udp; }; InputSubsystem::InputSubsystem() : impl{std::make_unique()} {} @@ -175,6 +199,22 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const { return impl->gcbuttons.get(); } +UDPMotionFactory* InputSubsystem::GetUDPMotions() { + return impl->udpmotion.get(); +} + +const UDPMotionFactory* InputSubsystem::GetUDPMotions() const { + return impl->udpmotion.get(); +} + +UDPTouchFactory* InputSubsystem::GetUDPTouch() { + return impl->udptouch.get(); +} + +const UDPTouchFactory* InputSubsystem::GetUDPTouch() const { + return impl->udptouch.get(); +} + void InputSubsystem::ReloadInputDevices() { if (!impl->udp) { return; diff --git a/src/input_common/main.h b/src/input_common/main.h index f3fbf696e3..dded3f1efb 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -21,10 +21,14 @@ namespace Settings::NativeButton { enum Values : int; } +namespace Settings::NativeMotion { +enum Values : int; +} + namespace InputCommon { namespace Polling { -enum class DeviceType { Button, AnalogPreferred }; +enum class DeviceType { Button, AnalogPreferred, Motion }; /** * A class that can be used to get inputs from an input device like controllers without having to @@ -50,6 +54,8 @@ public: class GCAnalogFactory; class GCButtonFactory; +class UDPMotionFactory; +class UDPTouchFactory; class Keyboard; class MotionEmu; @@ -59,6 +65,7 @@ class MotionEmu; */ using AnalogMapping = std::unordered_map; using ButtonMapping = std::unordered_map; +using MotionMapping = std::unordered_map; class InputSubsystem { public: @@ -103,6 +110,9 @@ public: /// Retrieves the button mappings for the given device. [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; + /// Retrieves the motion mappings for the given device. + [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; + /// Retrieves the underlying GameCube analog handler. [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); @@ -115,6 +125,18 @@ public: /// Retrieves the underlying GameCube button handler. [[nodiscard]] const GCButtonFactory* GetGCButtons() const; + /// Retrieves the underlying udp motion handler. + [[nodiscard]] UDPMotionFactory* GetUDPMotions(); + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const; + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] UDPTouchFactory* GetUDPTouch(); + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; + /// Reloads the input devices void ReloadInputDevices(); diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp index d4cdf76a3f..69fd3c1d2c 100644 --- a/src/input_common/motion_emu.cpp +++ b/src/input_common/motion_emu.cpp @@ -56,7 +56,7 @@ public: is_tilting = false; } - std::tuple, Common::Vec3> GetStatus() { + Input::MotionStatus GetStatus() { std::lock_guard guard{status_mutex}; return status; } @@ -76,7 +76,7 @@ private: Common::Event shutdown_event; - std::tuple, Common::Vec3> status; + Input::MotionStatus status; std::mutex status_mutex; // Note: always keep the thread declaration at the end so that other objects are initialized @@ -113,10 +113,19 @@ private: gravity = QuaternionRotate(inv_q, gravity); angular_rate = QuaternionRotate(inv_q, angular_rate); + // TODO: Calculate the correct rotation vector and orientation matrix + const auto matrix4x4 = q.ToMatrix(); + const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f); + const std::array orientation{ + Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), + Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), + Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]), + }; + // Update the sensor state { std::lock_guard guard{status_mutex}; - status = std::make_tuple(gravity, angular_rate); + status = std::make_tuple(gravity, angular_rate, rotation, orientation); } } } @@ -131,7 +140,7 @@ public: device = std::make_shared(update_millisecond, sensitivity); } - std::tuple, Common::Vec3> GetStatus() const override { + Input::MotionStatus GetStatus() const override { return device->GetStatus(); } diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp index 80c719cf45..b66c05856b 100644 --- a/src/input_common/settings.cpp +++ b/src/input_common/settings.cpp @@ -14,6 +14,13 @@ const std::array mapping = {{ }}; } +namespace NativeMotion { +const std::array mapping = {{ + "motionleft", + "motionright", +}}; +} + namespace NativeAnalog { const std::array mapping = {{ "lstick", diff --git a/src/input_common/settings.h b/src/input_common/settings.h index 2d258960b5..ab0b95cf1b 100644 --- a/src/input_common/settings.h +++ b/src/input_common/settings.h @@ -66,6 +66,21 @@ constexpr int NUM_STICKS_HID = NumAnalogs; extern const std::array mapping; } // namespace NativeAnalog +namespace NativeMotion { +enum Values : int { + MOTIONLEFT, + MOTIONRIGHT, + + NumMotions, +}; + +constexpr int MOTION_HID_BEGIN = MOTIONLEFT; +constexpr int MOTION_HID_END = NumMotions; +constexpr int NUM_MOTION_HID = NumMotions; + +extern const std::array mapping; +} // namespace NativeMotion + namespace NativeMouseButton { enum Values { Left, @@ -292,6 +307,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; using ButtonsRaw = std::array; using AnalogsRaw = std::array; +using MotionRaw = std::array; using MouseButtonsRaw = std::array; using KeyboardKeysRaw = std::array; using KeyboardModsRaw = std::array; @@ -314,6 +330,7 @@ struct PlayerInput { ControllerType controller_type; ButtonsRaw buttons; AnalogsRaw analogs; + MotionRaw motions; std::string lstick_mod; std::string rstick_mod; diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index 3f4eaf4483..2b6a68d4b4 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -2,14 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include #include #include #include #include #include #include "common/logging/log.h" +#include "core/settings.h" #include "input_common/udp/client.h" #include "input_common/udp/protocol.h" @@ -131,21 +130,59 @@ static void SocketLoop(Socket* socket) { socket->Loop(); } -Client::Client(std::shared_ptr status, const std::string& host, u16 port, - u8 pad_index, u32 client_id) - : status(std::move(status)) { - StartCommunication(host, port, pad_index, client_id); +Client::Client() { + LOG_INFO(Input, "Udp Initialization started"); + for (std::size_t client = 0; client < clients.size(); client++) { + u8 pad = client % 4; + StartCommunication(client, Settings::values.udp_input_address, + Settings::values.udp_input_port, pad, 24872); + // Set motion parameters + // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode + // Real HW values are unknown, 0.0001 is an approximate to Standard + clients[client].motion.SetGyroThreshold(0.0001f); + } } Client::~Client() { - socket->Stop(); - thread.join(); + Reset(); } +std::vector Client::GetInputDevices() const { + std::vector devices; + for (std::size_t client = 0; client < clients.size(); client++) { + if (!DeviceConnected(client)) { + continue; + } + std::string name = fmt::format("UDP Controller {}", client); + devices.emplace_back(Common::ParamPackage{ + {"class", "cemuhookudp"}, + {"display", std::move(name)}, + {"port", std::to_string(client)}, + }); + } + return devices; +} + +bool Client::DeviceConnected(std::size_t pad) const { + // Use last timestamp to detect if the socket has stopped sending data + const auto now = std::chrono::system_clock::now(); + u64 time_difference = + std::chrono::duration_cast(now - clients[pad].last_motion_update) + .count(); + return time_difference < 1000 && clients[pad].active == 1; +} + +void Client::ReloadUDPClient() { + for (std::size_t client = 0; client < clients.size(); client++) { + ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client); + } +} void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { - socket->Stop(); - thread.join(); - StartCommunication(host, port, pad_index, client_id); + // client number must be determined from host / port and pad index + std::size_t client = pad_index; + clients[client].socket->Stop(); + clients[client].thread.join(); + StartCommunication(client, host, port, pad_index, client_id); } void Client::OnVersion(Response::Version data) { @@ -157,23 +194,39 @@ void Client::OnPortInfo(Response::PortInfo data) { } void Client::OnPadData(Response::PadData data) { + // client number must be determined from host / port and pad index + std::size_t client = data.info.id; LOG_TRACE(Input, "PadData packet received"); - if (data.packet_counter <= packet_sequence) { + if (data.packet_counter == clients[client].packet_sequence) { LOG_WARNING( Input, "PadData packet dropped because its stale info. Current count: {} Packet count: {}", - packet_sequence, data.packet_counter); + clients[client].packet_sequence, data.packet_counter); return; } - packet_sequence = data.packet_counter; - // TODO: Check how the Switch handles motions and how the CemuhookUDP motion - // directions correspond to the ones of the Switch - Common::Vec3f accel = Common::MakeVec(data.accel.x, data.accel.y, data.accel.z); - Common::Vec3f gyro = Common::MakeVec(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); - { - std::lock_guard guard(status->update_mutex); + clients[client].active = data.info.is_pad_active; + clients[client].packet_sequence = data.packet_counter; + const auto now = std::chrono::system_clock::now(); + u64 time_difference = std::chrono::duration_cast( + now - clients[client].last_motion_update) + .count(); + clients[client].last_motion_update = now; + Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw}; + clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y}); + // Gyroscope values are not it the correct scale from better joy. + // Dividing by 312 allows us to make one full turn = 1 turn + // This must be a configurable valued called sensitivity + clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f); + clients[client].motion.UpdateRotation(time_difference); + clients[client].motion.UpdateOrientation(time_difference); + Common::Vec3f gyroscope = clients[client].motion.GetGyroscope(); + Common::Vec3f accelerometer = clients[client].motion.GetAcceleration(); + Common::Vec3f rotation = clients[client].motion.GetRotations(); + std::array orientation = clients[client].motion.GetOrientation(); - status->motion_status = {accel, gyro}; + { + std::lock_guard guard(clients[client].status.update_mutex); + clients[client].status.motion_status = {accelerometer, gyroscope, rotation, orientation}; // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates // between a simple "tap" and a hard press that causes the touch screen to click. @@ -182,11 +235,11 @@ void Client::OnPadData(Response::PadData data) { float x = 0; float y = 0; - if (is_active && status->touch_calibration) { - const u16 min_x = status->touch_calibration->min_x; - const u16 max_x = status->touch_calibration->max_x; - const u16 min_y = status->touch_calibration->min_y; - const u16 max_y = status->touch_calibration->max_y; + if (is_active && clients[client].status.touch_calibration) { + const u16 min_x = clients[client].status.touch_calibration->min_x; + const u16 max_x = clients[client].status.touch_calibration->max_x; + const u16 min_y = clients[client].status.touch_calibration->min_y; + const u16 max_y = clients[client].status.touch_calibration->max_y; x = (std::clamp(static_cast(data.touch_1.x), min_x, max_x) - min_x) / static_cast(max_x - min_x); @@ -194,17 +247,80 @@ void Client::OnPadData(Response::PadData data) { static_cast(max_y - min_y); } - status->touch_status = {x, y, is_active}; + clients[client].status.touch_status = {x, y, is_active}; + + if (configuring) { + UpdateYuzuSettings(client, accelerometer, gyroscope, is_active); + } } } -void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { +void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index, + u32 client_id) { SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, [this](Response::PortInfo info) { OnPortInfo(info); }, [this](Response::PadData data) { OnPadData(data); }}; LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); - socket = std::make_unique(host, port, pad_index, client_id, callback); - thread = std::thread{SocketLoop, this->socket.get()}; + clients[client].socket = std::make_unique(host, port, pad_index, client_id, callback); + clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; +} + +void Client::Reset() { + for (std::size_t client = 0; client < clients.size(); client++) { + clients[client].socket->Stop(); + clients[client].thread.join(); + } +} + +void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3& acc, + const Common::Vec3& gyro, bool touch) { + UDPPadStatus pad; + if (touch) { + pad.touch = PadTouch::Click; + pad_queue[client].Push(pad); + } + for (size_t i = 0; i < 3; ++i) { + if (gyro[i] > 6.0f || gyro[i] < -6.0f) { + pad.motion = static_cast(i); + pad.motion_value = gyro[i]; + pad_queue[client].Push(pad); + } + if (acc[i] > 2.0f || acc[i] < -2.0f) { + pad.motion = static_cast(i + 3); + pad.motion_value = acc[i]; + pad_queue[client].Push(pad); + } + } +} + +void Client::BeginConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = true; +} + +void Client::EndConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = false; +} + +DeviceStatus& Client::GetPadState(std::size_t pad) { + return clients[pad].status; +} + +const DeviceStatus& Client::GetPadState(std::size_t pad) const { + return clients[pad].status; +} + +std::array, 4>& Client::GetPadQueue() { + return pad_queue; +} + +const std::array, 4>& Client::GetPadQueue() const { + return pad_queue; } void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h index b8c6547554..523dc6a7a1 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/udp/client.h @@ -12,8 +12,12 @@ #include #include #include "common/common_types.h" +#include "common/param_package.h" #include "common/thread.h" +#include "common/threadsafe_queue.h" #include "common/vector_math.h" +#include "core/frontend/input.h" +#include "input_common/motion_input.h" namespace InputCommon::CemuhookUDP { @@ -28,9 +32,30 @@ struct PortInfo; struct Version; } // namespace Response +enum class PadMotion { + GyroX, + GyroY, + GyroZ, + AccX, + AccY, + AccZ, + Undefined, +}; + +enum class PadTouch { + Click, + Undefined, +}; + +struct UDPPadStatus { + PadTouch touch{PadTouch::Undefined}; + PadMotion motion{PadMotion::Undefined}; + f32 motion_value{0.0f}; +}; + struct DeviceStatus { std::mutex update_mutex; - std::tuple, Common::Vec3> motion_status; + Input::MotionStatus motion_status; std::tuple touch_status; // calibration data for scaling the device's touch area to 3ds @@ -45,22 +70,58 @@ struct DeviceStatus { class Client { public: - explicit Client(std::shared_ptr status, const std::string& host = DEFAULT_ADDR, - u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); + // Initialize the UDP client capture and read sequence + Client(); + + // Close and release the client ~Client(); + + // Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + std::vector GetInputDevices() const; + + bool DeviceConnected(std::size_t pad) const; + void ReloadUDPClient(); void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, u32 client_id = 24872); + std::array, 4>& GetPadQueue(); + const std::array, 4>& GetPadQueue() const; + + DeviceStatus& GetPadState(std::size_t pad); + const DeviceStatus& GetPadState(std::size_t pad) const; + private: + struct ClientData { + std::unique_ptr socket; + DeviceStatus status; + std::thread thread; + u64 packet_sequence = 0; + u8 active; + + // Realtime values + // motion is initalized with PID values for drift correction on joycons + InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f}; + std::chrono::time_point last_motion_update; + }; + + // For shutting down, clear all data, join all threads, release usb + void Reset(); + void OnVersion(Response::Version); void OnPortInfo(Response::PortInfo); void OnPadData(Response::PadData); - void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); + void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index, + u32 client_id); + void UpdateYuzuSettings(std::size_t client, const Common::Vec3& acc, + const Common::Vec3& gyro, bool touch); - std::unique_ptr socket; - std::shared_ptr status; - std::thread thread; - u64 packet_sequence = 0; + bool configuring = false; + + std::array clients; + std::array, 4> pad_queue; }; /// An async job allowing configuration of the touchpad calibration. diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp index 4b347e47e9..eba077a36c 100644 --- a/src/input_common/udp/udp.cpp +++ b/src/input_common/udp/udp.cpp @@ -1,105 +1,144 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include #include -#include -#include - -#include "common/param_package.h" -#include "core/frontend/input.h" -#include "core/settings.h" +#include +#include "common/assert.h" +#include "common/threadsafe_queue.h" #include "input_common/udp/client.h" #include "input_common/udp/udp.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class UDPTouchDevice final : public Input::TouchDevice { +class UDPMotion final : public Input::MotionDevice { public: - explicit UDPTouchDevice(std::shared_ptr status_) : status(std::move(status_)) {} - std::tuple GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->touch_status; + UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_) + : ip(ip_), port(port_), pad(pad_), client(client_) {} + + Input::MotionStatus GetStatus() const override { + return client->GetPadState(pad).motion_status; } private: - std::shared_ptr status; + const std::string ip; + const int port; + const int pad; + CemuhookUDP::Client* client; + mutable std::mutex mutex; }; -class UDPMotionDevice final : public Input::MotionDevice { -public: - explicit UDPMotionDevice(std::shared_ptr status_) : status(std::move(status_)) {} - std::tuple, Common::Vec3> GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->motion_status; - } +/// A motion device factory that creates motion devices from JC Adapter +UDPMotionFactory::UDPMotionFactory(std::shared_ptr client_) + : client(std::move(client_)) {} -private: - std::shared_ptr status; -}; +/** + * Creates motion device + * @param params contains parameters for creating the device: + * - "port": the nth jcpad on the adapter + */ +std::unique_ptr UDPMotionFactory::Create(const Common::ParamPackage& params) { + const std::string ip = params.Get("ip", "127.0.0.1"); + const int port = params.Get("port", 26760); + const int pad = params.Get("pad_index", 0); -class UDPTouchFactory final : public Input::Factory { -public: - explicit UDPTouchFactory(std::shared_ptr status_) : status(std::move(status_)) {} + return std::make_unique(ip, port, pad, client.get()); +} - std::unique_ptr Create(const Common::ParamPackage& params) override { - { - std::lock_guard guard(status->update_mutex); - status->touch_calibration = DeviceStatus::CalibrationData{}; - // These default values work well for DS4 but probably not other touch inputs - status->touch_calibration->min_x = params.Get("min_x", 100); - status->touch_calibration->min_y = params.Get("min_y", 50); - status->touch_calibration->max_x = params.Get("max_x", 1800); - status->touch_calibration->max_y = params.Get("max_y", 850); +void UDPMotionFactory::BeginConfiguration() { + polling = true; + client->BeginConfiguration(); +} + +void UDPMotionFactory::EndConfiguration() { + polling = false; + client->EndConfiguration(); +} + +Common::ParamPackage UDPMotionFactory::GetNextInput() { + Common::ParamPackage params; + CemuhookUDP::UDPPadStatus pad; + auto& queue = client->GetPadQueue(); + for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) { + while (queue[pad_number].Pop(pad)) { + if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { + continue; + } + params.Set("engine", "cemuhookudp"); + params.Set("ip", "127.0.0.1"); + params.Set("port", 26760); + params.Set("pad_index", static_cast(pad_number)); + params.Set("motion", static_cast(pad.motion)); + return params; } - return std::make_unique(status); } + return params; +} -private: - std::shared_ptr status; -}; - -class UDPMotionFactory final : public Input::Factory { +class UDPTouch final : public Input::TouchDevice { public: - explicit UDPMotionFactory(std::shared_ptr status_) : status(std::move(status_)) {} + UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_) + : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - std::unique_ptr Create(const Common::ParamPackage& params) override { - return std::make_unique(status); + std::tuple GetStatus() const override { + return client->GetPadState(pad).touch_status; } private: - std::shared_ptr status; + const std::string ip; + const int port; + const int pad; + CemuhookUDP::Client* client; + mutable std::mutex mutex; }; -State::State() { - auto status = std::make_shared(); - client = - std::make_unique(status, Settings::values.udp_input_address, - Settings::values.udp_input_port, Settings::values.udp_pad_index); +/// A motion device factory that creates motion devices from JC Adapter +UDPTouchFactory::UDPTouchFactory(std::shared_ptr client_) + : client(std::move(client_)) {} - motion_factory = std::make_shared(status); - touch_factory = std::make_shared(status); +/** + * Creates motion device + * @param params contains parameters for creating the device: + * - "port": the nth jcpad on the adapter + */ +std::unique_ptr UDPTouchFactory::Create(const Common::ParamPackage& params) { + const std::string ip = params.Get("ip", "127.0.0.1"); + const int port = params.Get("port", 26760); + const int pad = params.Get("pad_index", 0); - Input::RegisterFactory("cemuhookudp", motion_factory); - Input::RegisterFactory("cemuhookudp", touch_factory); + return std::make_unique(ip, port, pad, client.get()); } -State::~State() { - Input::UnregisterFactory("cemuhookudp"); - Input::UnregisterFactory("cemuhookudp"); +void UDPTouchFactory::BeginConfiguration() { + polling = true; + client->BeginConfiguration(); } -std::vector State::GetInputDevices() const { - // TODO support binding udp devices - return {}; +void UDPTouchFactory::EndConfiguration() { + polling = false; + client->EndConfiguration(); } -void State::ReloadUDPClient() { - client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, - Settings::values.udp_pad_index); +Common::ParamPackage UDPTouchFactory::GetNextInput() { + Common::ParamPackage params; + CemuhookUDP::UDPPadStatus pad; + auto& queue = client->GetPadQueue(); + for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) { + while (queue[pad_number].Pop(pad)) { + if (pad.touch == CemuhookUDP::PadTouch::Undefined) { + continue; + } + params.Set("engine", "cemuhookudp"); + params.Set("ip", "127.0.0.1"); + params.Set("port", 26760); + params.Set("pad_index", static_cast(pad_number)); + params.Set("touch", static_cast(pad.touch)); + return params; + } + } + return params; } -std::unique_ptr Init() { - return std::make_unique(); -} -} // namespace InputCommon::CemuhookUDP +} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h index 672a5c812e..ea3fd41751 100644 --- a/src/input_common/udp/udp.h +++ b/src/input_common/udp/udp.h @@ -1,32 +1,57 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include -#include -#include "common/param_package.h" +#include "core/frontend/input.h" +#include "input_common/udp/client.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class Client; -class UDPMotionFactory; -class UDPTouchFactory; - -class State { +/// A motion device factory that creates motion devices from udp clients +class UDPMotionFactory final : public Input::Factory { public: - State(); - ~State(); - void ReloadUDPClient(); - std::vector GetInputDevices() const; + explicit UDPMotionFactory(std::shared_ptr client_); + + std::unique_ptr Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } private: - std::unique_ptr client; - std::shared_ptr motion_factory; - std::shared_ptr touch_factory; + std::shared_ptr client; + bool polling = false; }; -std::unique_ptr Init(); +/// A touch device factory that creates touch devices from udp clients +class UDPTouchFactory final : public Input::Factory { +public: + explicit UDPTouchFactory(std::shared_ptr client_); -} // namespace InputCommon::CemuhookUDP + std::unique_ptr Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr client; + bool polling = false; +}; + +} // namespace InputCommon diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 2bc55a26af..d2913d6134 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -36,6 +36,11 @@ const std::array Config::default_button Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, }; +const std::array Config::default_motions = { + Qt::Key_7, + Qt::Key_8, +}; + const std::array, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ { Qt::Key_Up, @@ -284,6 +289,22 @@ void Config::ReadPlayerValues() { } } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = + InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; + + player_motions = qt_config + ->value(QStringLiteral("player_%1_").arg(p) + + QString::fromUtf8(Settings::NativeMotion::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_motions.empty()) { + player_motions = default_param; + } + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], @@ -424,6 +445,7 @@ void Config::ReadControlValues() { Settings::values.vibration_enabled = ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); + Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool(); Settings::values.use_docked_mode = ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); @@ -922,6 +944,14 @@ void Config::SavePlayerValues() { QString::fromStdString(player.buttons[i]), QString::fromStdString(default_param)); } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = + InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteSetting(QStringLiteral("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeMotion::mapping[i]), + QString::fromStdString(player.motions[i]), + QString::fromStdString(default_param)); + } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], @@ -1062,6 +1092,7 @@ void Config::SaveControlValues() { SaveMotionTouchValues(); WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); + WriteSetting(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true); WriteSetting(QStringLiteral("motion_device"), QString::fromStdString(Settings::values.motion_device), QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index ca0d29c6c7..5d8e45d782 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -23,6 +23,7 @@ public: void Save(); static const std::array default_buttons; + static const std::array default_motions; static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array default_stick_mod; static const std::array diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 7ea17a4db3..2725fcb2b2 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -146,6 +146,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, CallConfigureDialog(*this, input_subsystem); }); + connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] { + CallConfigureDialog(*this, input_subsystem); + }); + connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); @@ -172,6 +176,7 @@ void ConfigureInput::ApplyConfiguration() { OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); + Settings::values.motion_enabled = ui->motionGroup->isChecked(); } void ConfigureInput::changeEvent(QEvent* event) { @@ -191,6 +196,7 @@ void ConfigureInput::LoadConfiguration() { UpdateDockedState(Settings::values.players[8].connected); ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); + ui->motionGroup->setChecked(Settings::values.motion_enabled); } void ConfigureInput::LoadPlayerControllerIndices() { @@ -217,6 +223,7 @@ void ConfigureInput::RestoreDefaults() { ui->radioDocked->setChecked(true); ui->radioUndocked->setChecked(false); ui->vibrationGroup->setChecked(true); + ui->motionGroup->setChecked(true); } void ConfigureInput::UpdateDockedState(bool is_handheld) { diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index a535788370..9a61a6e159 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -18,6 +18,7 @@ #include "core/hle/service/sm/sm.h" #include "input_common/gcadapter/gc_poller.h" #include "input_common/main.h" +#include "input_common/udp/udp.h" #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" @@ -149,6 +150,14 @@ QString ButtonToText(const Common::ParamPackage& param) { return GetKeyName(param.Get("code", 0)); } + if (param.Get("engine", "") == "cemuhookudp") { + if (param.Has("pad_index")) { + const QString motion_str = QString::fromStdString(param.Get("pad_index", "")); + return QObject::tr("Motion %1").arg(motion_str); + } + return GetKeyName(param.Get("code", 0)); + } + if (param.Get("engine", "") == "sdl") { if (param.Has("hat")) { const QString hat_str = QString::fromStdString(param.Get("hat", "")); @@ -262,6 +271,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }, }}; + motion_map = { + ui->buttonMotionLeft, + ui->buttonMotionRight, + }; + analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; @@ -304,6 +318,32 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i Config::default_buttons[button_id]); } + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + auto* const button = motion_map[motion_id]; + if (button == nullptr) { + continue; + } + + button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button, &QPushButton::clicked, [=, this] { + HandleClick( + motion_map[motion_id], + [=, this](Common::ParamPackage params) { + motions_param[motion_id] = std::move(params); + }, + InputCommon::Polling::DeviceType::Motion); + }); + connect(button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + motions_param[motion_id].Clear(); + motion_map[motion_id]->setText(tr("[not set]")); + }); + context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location)); + }); + } + // Handle clicks for the modifier buttons as well. ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]); ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]); @@ -385,9 +425,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i UpdateControllerIcon(); UpdateControllerAvailableButtons(); + UpdateMotionButtons(); connect(ui->comboControllerType, qOverload(&QComboBox::currentIndexChanged), [this](int) { UpdateControllerIcon(); UpdateControllerAvailableButtons(); + UpdateMotionButtons(); }); connect(ui->comboDevices, qOverload(&QComboBox::currentIndexChanged), this, @@ -417,6 +459,13 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i return; } } + if (input_subsystem->GetUDPMotions()->IsPolling()) { + params = input_subsystem->GetUDPMotions()->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; + } + } for (auto& poller : device_pollers) { params = poller->GetNextInput(); if (params.Has("engine")) { @@ -448,6 +497,10 @@ void ConfigureInputPlayer::ApplyConfiguration() { return; } + auto& motions = player.motions; + std::transform(motions_param.begin(), motions_param.end(), motions.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + player.controller_type = static_cast(ui->comboControllerType->currentIndex()); player.connected = ui->groupConnectedController->isChecked(); @@ -501,6 +554,8 @@ void ConfigureInputPlayer::LoadConfiguration() { [](const std::string& str) { return Common::ParamPackage(str); }); std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); } UpdateUI(); @@ -544,6 +599,12 @@ void ConfigureInputPlayer::RestoreDefaults() { SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); } } + + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motions_param[motion_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; + } + UpdateUI(); UpdateInputDevices(); ui->comboControllerType->setCurrentIndex(0); @@ -573,6 +634,15 @@ void ConfigureInputPlayer::ClearAll() { } } + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + const auto* const button = motion_map[motion_id]; + if (button == nullptr || !button->isEnabled()) { + continue; + } + + motions_param[motion_id].Clear(); + } + UpdateUI(); UpdateInputDevices(); } @@ -582,6 +652,10 @@ void ConfigureInputPlayer::UpdateUI() { button_map[button]->setText(ButtonToText(buttons_param[button])); } + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id])); + } + ui->buttonLStickMod->setText(ButtonToText(lstick_mod)); ui->buttonRStickMod->setText(ButtonToText(rstick_mod)); @@ -659,7 +733,11 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() { void ConfigureInputPlayer::HandleClick( QPushButton* button, std::function new_input_setter, InputCommon::Polling::DeviceType type) { - button->setText(tr("[waiting]")); + if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { + button->setText(tr("Shake!")); + } else { + button->setText(tr("[waiting]")); + } button->setFocus(); // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a @@ -683,6 +761,10 @@ void ConfigureInputPlayer::HandleClick( input_subsystem->GetGCAnalogs()->BeginConfiguration(); } + if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetUDPMotions()->BeginConfiguration(); + } + timeout_timer->start(2500); // Cancel after 2.5 seconds poll_timer->start(50); // Check for new inputs every 50ms } @@ -700,6 +782,8 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, input_subsystem->GetGCButtons()->EndConfiguration(); input_subsystem->GetGCAnalogs()->EndConfiguration(); + input_subsystem->GetUDPMotions()->EndConfiguration(); + if (!abort) { (*input_setter)(params); } @@ -832,6 +916,37 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { } } +void ConfigureInputPlayer::UpdateMotionButtons() { + if (debug) { + // Motion isn't used with the debug controller, hide both groupboxes. + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->hide(); + return; + } + + // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller. + switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::Handheld: + // Show "Motion 1" and hide "Motion 2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->hide(); + break; + case Settings::ControllerType::RightJoycon: + // Show "Motion 2" and hide "Motion 1". + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->show(); + break; + case Settings::ControllerType::DualJoyconDetached: + default: + // Show both "Motion 1/2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->show(); + break; + } +} + void ConfigureInputPlayer::showEvent(QShowEvent* event) { if (bottom_row == nullptr) { return; diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index a25bc3bd90..b343f2c1d8 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -107,6 +107,9 @@ private: /// Hides and disables controller settings based on the current controller type. void UpdateControllerAvailableButtons(); + /// Shows or hides motion groupboxes based on the current controller type. + void UpdateMotionButtons(); + /// Gets the default controller mapping for this device and auto configures the input to match. void UpdateMappingWithDefaults(); @@ -128,11 +131,14 @@ private: std::array buttons_param; std::array analogs_param; + std::array motions_param; static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; /// Each button input is represented by a QPushButton. std::array button_map; + /// Each motion input is represented by a QPushButton. + std::array motion_map; /// Extra buttons for the modifiers. Common::ParamPackage lstick_mod; Common::ParamPackage rstick_mod; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index 9bc6818944..e03461d9d3 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -1983,6 +1983,9 @@ 3 + + 0 + @@ -1990,12 +1993,110 @@ - 40 - 0 + 20 + 20 + + + + Motion 1 + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 57 + 0 + + + + + 55 + 16777215 + + + + min-width: 55px; + + + Left + + + + + + + + + + Motion 2 + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 57 + 0 + + + + + 55 + 16777215 + + + + min-width: 55px; + + + Right + + + + + + @@ -2003,8 +2104,8 @@ - 40 - 0 + 20 + 20 diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index e9f1c65007..23448e747d 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -290,6 +290,8 @@ void Config::ReadValues() { Settings::values.vibration_enabled = sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true); + Settings::values.motion_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true); Settings::values.touchscreen.enabled = sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); Settings::values.touchscreen.device = diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp index aaf59129a6..bc273fb517 100644 --- a/src/yuzu_tester/config.cpp +++ b/src/yuzu_tester/config.cpp @@ -76,6 +76,7 @@ void Config::ReadValues() { } Settings::values.vibration_enabled = true; + Settings::values.motion_enabled = true; Settings::values.touchscreen.enabled = ""; Settings::values.touchscreen.device = ""; Settings::values.touchscreen.finger = 0;