mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	Merge pull request #4594 from german77/MotionHID
hid/configuration: Implement motion controls to HID
This commit is contained in:
		
						commit
						3f6d83b27c
					
				| @ -119,11 +119,11 @@ using ButtonDevice = InputDevice<bool>; | ||||
| using AnalogDevice = InputDevice<std::tuple<float, float>>; | ||||
| 
 | ||||
| /**
 | ||||
|  * 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<std::tuple<float, float>>; | ||||
|  * 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<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>; | ||||
| using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>, | ||||
|                                 std::array<Common::Vec3f, 3>>; | ||||
| 
 | ||||
| /**
 | ||||
|  * A motion device is an input device that returns a motion status object | ||||
|  */ | ||||
| using MotionDevice = InputDevice<MotionStatus>; | ||||
| 
 | ||||
| /**
 | ||||
|  * A touch device is an input device that returns a tuple of two floats and a bool. The floats are | ||||
|  | ||||
| @ -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<Input::AnalogDevice>); | ||||
|         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<Input::MotionDevice>); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -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<std::size_t>(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<u32>(i); | ||||
| 
 | ||||
|         const std::array<SixAxisGeneric*, 6> 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<MotionDevice, 2> 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); | ||||
|  | ||||
| @ -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<Common::Vec3f, 3> orientation{}; | ||||
|         s64_le always_one{1}; | ||||
|     }; | ||||
|     static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size"); | ||||
| 
 | ||||
|     struct SixAxisGeneric { | ||||
|         CommonHeader common{}; | ||||
|         std::array<SixAxisStates, 17> 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<Common::Vec3f, 3> 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<NPadEntry, 10> shared_memory_entries{}; | ||||
|     std::array< | ||||
|     using ButtonArray = std::array< | ||||
|         std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>, | ||||
|         10> | ||||
|         buttons; | ||||
|     std::array< | ||||
|         10>; | ||||
|     using StickArray = std::array< | ||||
|         std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, | ||||
|         10> | ||||
|         sticks; | ||||
|         10>; | ||||
|     using MotionArray = std::array< | ||||
|         std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTION_HID>, | ||||
|         10>; | ||||
|     ButtonArray buttons; | ||||
|     StickArray sticks; | ||||
|     MotionArray motions; | ||||
|     std::vector<u32> 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<ControllerHolder, 10> 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<ControllerPad, 10> npad_pad_states{}; | ||||
|     bool is_in_lr_assignment_mode{false}; | ||||
|     Core::System& system; | ||||
|  | ||||
| @ -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<u32>()}; | ||||
|     const auto applet_resource_user_id{rp.Pop<u64>()}; | ||||
|     applet_resource->GetController<Controller_NPad>(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<u32>()}; | ||||
|     const auto applet_resource_user_id{rp.Pop<u64>()}; | ||||
|     applet_resource->GetController<Controller_NPad>(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<u64>()}; | ||||
| @ -484,13 +509,13 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { | ||||
|     const auto handle{rp.Pop<u32>()}; | ||||
|     const auto applet_resource_user_id{rp.Pop<u64>()}; | ||||
| 
 | ||||
|     LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, | ||||
|     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<Controller_NPad>(HidController::NPad) | ||||
|                 .IsSixAxisSensorAtRest()); | ||||
| } | ||||
| 
 | ||||
| void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -152,6 +152,7 @@ struct Values { | ||||
| 
 | ||||
|     bool vibration_enabled; | ||||
| 
 | ||||
|     bool motion_enabled; | ||||
|     std::string motion_device; | ||||
|     std::string touch_device; | ||||
|     TouchscreenInput touchscreen; | ||||
|  | ||||
| @ -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<InputCommon::CemuhookUDP::Client>(); | ||||
|         udpmotion = std::make_shared<UDPMotionFactory>(udp); | ||||
|         Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion); | ||||
|         udptouch = std::make_shared<UDPTouchFactory>(udp); | ||||
|         Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch); | ||||
|     } | ||||
| 
 | ||||
|     void Shutdown() { | ||||
| @ -53,12 +58,17 @@ struct InputSubsystem::Impl { | ||||
| #ifdef HAVE_SDL2 | ||||
|         sdl.reset(); | ||||
| #endif | ||||
|         udp.reset(); | ||||
|         Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); | ||||
|         Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); | ||||
| 
 | ||||
|         gcbuttons.reset(); | ||||
|         gcanalog.reset(); | ||||
| 
 | ||||
|         Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); | ||||
|         Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); | ||||
| 
 | ||||
|         udpmotion.reset(); | ||||
|         udptouch.reset(); | ||||
|     } | ||||
| 
 | ||||
|     [[nodiscard]] std::vector<Common::ParamPackage> 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> keyboard; | ||||
|     std::shared_ptr<MotionEmu> motion_emu; | ||||
| #ifdef HAVE_SDL2 | ||||
|     std::unique_ptr<SDL::State> sdl; | ||||
| #endif | ||||
|     std::unique_ptr<CemuhookUDP::State> udp; | ||||
|     std::shared_ptr<GCButtonFactory> gcbuttons; | ||||
|     std::shared_ptr<GCAnalogFactory> gcanalog; | ||||
|     std::shared_ptr<UDPMotionFactory> udpmotion; | ||||
|     std::shared_ptr<UDPTouchFactory> udptouch; | ||||
|     std::shared_ptr<CemuhookUDP::Client> udp; | ||||
| }; | ||||
| 
 | ||||
| InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} | ||||
| @ -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; | ||||
|  | ||||
| @ -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<Settings::NativeAnalog::Values, Common::ParamPackage>; | ||||
| using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; | ||||
| using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>; | ||||
| 
 | ||||
| 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(); | ||||
| 
 | ||||
|  | ||||
| @ -56,7 +56,7 @@ public: | ||||
|         is_tilting = false; | ||||
|     } | ||||
| 
 | ||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> 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<float>, Common::Vec3<float>> 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<MotionEmuDevice>(update_millisecond, sensitivity); | ||||
|     } | ||||
| 
 | ||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { | ||||
|     Input::MotionStatus GetStatus() const override { | ||||
|         return device->GetStatus(); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -14,6 +14,13 @@ const std::array<const char*, NumButtons> mapping = {{ | ||||
| }}; | ||||
| } | ||||
| 
 | ||||
| namespace NativeMotion { | ||||
| const std::array<const char*, NumMotions> mapping = {{ | ||||
|     "motionleft", | ||||
|     "motionright", | ||||
| }}; | ||||
| } | ||||
| 
 | ||||
| namespace NativeAnalog { | ||||
| const std::array<const char*, NumAnalogs> mapping = {{ | ||||
|     "lstick", | ||||
|  | ||||
| @ -66,6 +66,21 @@ constexpr int NUM_STICKS_HID = NumAnalogs; | ||||
| extern const std::array<const char*, NumAnalogs> 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<const char*, NumMotions> mapping; | ||||
| } // namespace NativeMotion
 | ||||
| 
 | ||||
| namespace NativeMouseButton { | ||||
| enum Values { | ||||
|     Left, | ||||
| @ -292,6 +307,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; | ||||
| 
 | ||||
| using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; | ||||
| using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; | ||||
| using MotionRaw = std::array<std::string, NativeMotion::NumMotions>; | ||||
| using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; | ||||
| using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; | ||||
| using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; | ||||
| @ -314,6 +330,7 @@ struct PlayerInput { | ||||
|     ControllerType controller_type; | ||||
|     ButtonsRaw buttons; | ||||
|     AnalogsRaw analogs; | ||||
|     MotionRaw motions; | ||||
|     std::string lstick_mod; | ||||
|     std::string rstick_mod; | ||||
| 
 | ||||
|  | ||||
| @ -2,14 +2,13 @@ | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <chrono> | ||||
| #include <cstring> | ||||
| #include <functional> | ||||
| #include <thread> | ||||
| #include <boost/asio.hpp> | ||||
| #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<DeviceStatus> 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<Common::ParamPackage> Client::GetInputDevices() const { | ||||
|     std::vector<Common::ParamPackage> 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<std::chrono::milliseconds>(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<float>(data.accel.x, data.accel.y, data.accel.z); | ||||
|     Common::Vec3f gyro = Common::MakeVec<float>(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<std::chrono::microseconds>( | ||||
|                               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<Common::Vec3f, 3> 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<u16>(data.touch_1.x), min_x, max_x) - min_x) / | ||||
|                 static_cast<float>(max_x - min_x); | ||||
| @ -194,17 +247,80 @@ void Client::OnPadData(Response::PadData data) { | ||||
|                 static_cast<float>(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<Socket>(host, port, pad_index, client_id, callback); | ||||
|     thread = std::thread{SocketLoop, this->socket.get()}; | ||||
|     clients[client].socket = std::make_unique<Socket>(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<float>& acc, | ||||
|                                 const Common::Vec3<float>& 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<PadMotion>(i); | ||||
|             pad.motion_value = gyro[i]; | ||||
|             pad_queue[client].Push(pad); | ||||
|         } | ||||
|         if (acc[i] > 2.0f || acc[i] < -2.0f) { | ||||
|             pad.motion = static_cast<PadMotion>(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<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() { | ||||
|     return pad_queue; | ||||
| } | ||||
| 
 | ||||
| const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const { | ||||
|     return pad_queue; | ||||
| } | ||||
| 
 | ||||
| void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, | ||||
|  | ||||
| @ -12,8 +12,12 @@ | ||||
| #include <thread> | ||||
| #include <tuple> | ||||
| #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<float>, Common::Vec3<float>> motion_status; | ||||
|     Input::MotionStatus motion_status; | ||||
|     std::tuple<float, float, bool> 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<DeviceStatus> 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<Common::ParamPackage> 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<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue(); | ||||
|     const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const; | ||||
| 
 | ||||
|     DeviceStatus& GetPadState(std::size_t pad); | ||||
|     const DeviceStatus& GetPadState(std::size_t pad) const; | ||||
| 
 | ||||
| private: | ||||
|     struct ClientData { | ||||
|         std::unique_ptr<Socket> 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<std::chrono::system_clock> 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<float>& acc, | ||||
|                             const Common::Vec3<float>& gyro, bool touch); | ||||
| 
 | ||||
|     std::unique_ptr<Socket> socket; | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
|     std::thread thread; | ||||
|     u64 packet_sequence = 0; | ||||
|     bool configuring = false; | ||||
| 
 | ||||
|     std::array<ClientData, 4> clients; | ||||
|     std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue; | ||||
| }; | ||||
| 
 | ||||
| /// An async job allowing configuration of the touchpad calibration.
 | ||||
|  | ||||
| @ -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 <atomic> | ||||
| #include <list> | ||||
| #include <mutex> | ||||
| #include <optional> | ||||
| #include <tuple> | ||||
| 
 | ||||
| #include "common/param_package.h" | ||||
| #include "core/frontend/input.h" | ||||
| #include "core/settings.h" | ||||
| #include <utility> | ||||
| #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<DeviceStatus> status_) : status(std::move(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: | ||||
|     const std::string ip; | ||||
|     const int port; | ||||
|     const int pad; | ||||
|     CemuhookUDP::Client* client; | ||||
|     mutable std::mutex mutex; | ||||
| }; | ||||
| 
 | ||||
| /// A motion device factory that creates motion devices from JC Adapter
 | ||||
| UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_) | ||||
|     : client(std::move(client_)) {} | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates motion device | ||||
|  * @param params contains parameters for creating the device: | ||||
|  *     - "port": the nth jcpad on the adapter | ||||
|  */ | ||||
| std::unique_ptr<Input::MotionDevice> 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); | ||||
| 
 | ||||
|     return std::make_unique<UDPMotion>(ip, port, pad, client.get()); | ||||
| } | ||||
| 
 | ||||
| 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<int>(pad_number)); | ||||
|             params.Set("motion", static_cast<u16>(pad.motion)); | ||||
|             return params; | ||||
|         } | ||||
|     } | ||||
|     return params; | ||||
| } | ||||
| 
 | ||||
| class UDPTouch final : public Input::TouchDevice { | ||||
| public: | ||||
|     UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_) | ||||
|         : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} | ||||
| 
 | ||||
|     std::tuple<float, float, bool> GetStatus() const override { | ||||
|         std::lock_guard guard(status->update_mutex); | ||||
|         return status->touch_status; | ||||
|         return client->GetPadState(pad).touch_status; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> 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<DeviceStatus> status_) : status(std::move(status_)) {} | ||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> 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
 | ||||
| UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_) | ||||
|     : client(std::move(client_)) {} | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates motion device | ||||
|  * @param params contains parameters for creating the device: | ||||
|  *     - "port": the nth jcpad on the adapter | ||||
|  */ | ||||
| std::unique_ptr<Input::TouchDevice> 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); | ||||
| 
 | ||||
|     return std::make_unique<UDPTouch>(ip, port, pad, client.get()); | ||||
| } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
| }; | ||||
| 
 | ||||
| class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { | ||||
| public: | ||||
|     explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} | ||||
| 
 | ||||
|     std::unique_ptr<Input::TouchDevice> 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); | ||||
|         } | ||||
|         return std::make_unique<UDPTouchDevice>(status); | ||||
| void UDPTouchFactory::BeginConfiguration() { | ||||
|     polling = true; | ||||
|     client->BeginConfiguration(); | ||||
| } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
| }; | ||||
| 
 | ||||
| class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { | ||||
| public: | ||||
|     explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} | ||||
| 
 | ||||
|     std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { | ||||
|         return std::make_unique<UDPMotionDevice>(status); | ||||
| void UDPTouchFactory::EndConfiguration() { | ||||
|     polling = false; | ||||
|     client->EndConfiguration(); | ||||
| } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
| }; | ||||
| 
 | ||||
| State::State() { | ||||
|     auto status = std::make_shared<DeviceStatus>(); | ||||
|     client = | ||||
|         std::make_unique<Client>(status, Settings::values.udp_input_address, | ||||
|                                  Settings::values.udp_input_port, Settings::values.udp_pad_index); | ||||
| 
 | ||||
|     motion_factory = std::make_shared<UDPMotionFactory>(status); | ||||
|     touch_factory = std::make_shared<UDPTouchFactory>(status); | ||||
| 
 | ||||
|     Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory); | ||||
|     Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory); | ||||
| 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<int>(pad_number)); | ||||
|             params.Set("touch", static_cast<u16>(pad.touch)); | ||||
|             return params; | ||||
|         } | ||||
|     } | ||||
|     return params; | ||||
| } | ||||
| 
 | ||||
| State::~State() { | ||||
|     Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); | ||||
|     Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); | ||||
| } | ||||
| 
 | ||||
| std::vector<Common::ParamPackage> State::GetInputDevices() const { | ||||
|     // TODO support binding udp devices
 | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| void State::ReloadUDPClient() { | ||||
|     client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, | ||||
|                          Settings::values.udp_pad_index); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<State> Init() { | ||||
|     return std::make_unique<State>(); | ||||
| } | ||||
| } // namespace InputCommon::CemuhookUDP
 | ||||
| } // namespace InputCommon
 | ||||
|  | ||||
| @ -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 <memory> | ||||
| #include <vector> | ||||
| #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<Input::MotionDevice> { | ||||
| public: | ||||
|     State(); | ||||
|     ~State(); | ||||
|     void ReloadUDPClient(); | ||||
|     std::vector<Common::ParamPackage> GetInputDevices() const; | ||||
|     explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_); | ||||
| 
 | ||||
|     std::unique_ptr<Input::MotionDevice> 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> client; | ||||
|     std::shared_ptr<UDPMotionFactory> motion_factory; | ||||
|     std::shared_ptr<UDPTouchFactory> touch_factory; | ||||
|     std::shared_ptr<CemuhookUDP::Client> client; | ||||
|     bool polling = false; | ||||
| }; | ||||
| 
 | ||||
| std::unique_ptr<State> Init(); | ||||
| /// A touch device factory that creates touch devices from udp clients
 | ||||
| class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { | ||||
| public: | ||||
|     explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_); | ||||
| 
 | ||||
| } // namespace InputCommon::CemuhookUDP
 | ||||
|     std::unique_ptr<Input::TouchDevice> 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<CemuhookUDP::Client> client; | ||||
|     bool polling = false; | ||||
| }; | ||||
| 
 | ||||
| } // namespace InputCommon
 | ||||
|  | ||||
| @ -36,6 +36,11 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button | ||||
|     Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, | ||||
| }; | ||||
| 
 | ||||
| const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = { | ||||
|     Qt::Key_7, | ||||
|     Qt::Key_8, | ||||
| }; | ||||
| 
 | ||||
| const std::array<std::array<int, 4>, 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")); | ||||
|  | ||||
| @ -23,6 +23,7 @@ public: | ||||
|     void Save(); | ||||
| 
 | ||||
|     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; | ||||
|     static const std::array<int, Settings::NativeMotion::NumMotions> default_motions; | ||||
|     static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; | ||||
|     static const std::array<int, 2> default_stick_mod; | ||||
|     static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> | ||||
|  | ||||
| @ -146,6 +146,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, | ||||
|                 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); | ||||
|             }); | ||||
| 
 | ||||
|     connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] { | ||||
|         CallConfigureDialog<ConfigureMotionTouch>(*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) { | ||||
|  | ||||
| @ -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<int>(&QComboBox::currentIndexChanged), [this](int) { | ||||
|         UpdateControllerIcon(); | ||||
|         UpdateControllerAvailableButtons(); | ||||
|         UpdateMotionButtons(); | ||||
|     }); | ||||
| 
 | ||||
|     connect(ui->comboDevices, qOverload<int>(&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<Settings::ControllerType>(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<void(const Common::ParamPackage&)> new_input_setter, | ||||
|     InputCommon::Polling::DeviceType type) { | ||||
|     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; | ||||
|  | ||||
| @ -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<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; | ||||
|     std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; | ||||
|     std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param; | ||||
| 
 | ||||
|     static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; | ||||
| 
 | ||||
|     /// Each button input is represented by a QPushButton.
 | ||||
|     std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; | ||||
|     /// Each motion input is represented by a QPushButton.
 | ||||
|     std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map; | ||||
|     /// Extra buttons for the modifiers.
 | ||||
|     Common::ParamPackage lstick_mod; | ||||
|     Common::ParamPackage rstick_mod; | ||||
|  | ||||
| @ -1983,6 +1983,9 @@ | ||||
|              <property name="spacing"> | ||||
|               <number>3</number> | ||||
|              </property> | ||||
|              <property name="topMargin"> | ||||
|               <number>0</number> | ||||
|              </property> | ||||
|              <item> | ||||
|               <spacer name="horizontalSpacerMiscButtons1"> | ||||
|                <property name="orientation"> | ||||
| @ -1990,12 +1993,110 @@ | ||||
|                </property> | ||||
|                <property name="sizeHint" stdset="0"> | ||||
|                 <size> | ||||
|                  <width>40</width> | ||||
|                  <height>0</height> | ||||
|                  <width>20</width> | ||||
|                  <height>20</height> | ||||
|                 </size> | ||||
|                </property> | ||||
|               </spacer> | ||||
|              </item> | ||||
|              <item> | ||||
|               <widget class="QGroupBox" name="buttonMotionLeftGroup"> | ||||
|                <property name="title"> | ||||
|                 <string>Motion 1</string> | ||||
|                </property> | ||||
|                <property name="alignment"> | ||||
|                 <set>Qt::AlignCenter</set> | ||||
|                </property> | ||||
|                <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2"> | ||||
|                 <property name="spacing"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="leftMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="topMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="rightMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="bottomMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <item> | ||||
|                  <widget class="QPushButton" name="buttonMotionLeft"> | ||||
|                   <property name="minimumSize"> | ||||
|                    <size> | ||||
|                     <width>57</width> | ||||
|                     <height>0</height> | ||||
|                    </size> | ||||
|                   </property> | ||||
|                   <property name="maximumSize"> | ||||
|                    <size> | ||||
|                     <width>55</width> | ||||
|                     <height>16777215</height> | ||||
|                    </size> | ||||
|                   </property> | ||||
|                   <property name="styleSheet"> | ||||
|                    <string notr="true">min-width: 55px;</string> | ||||
|                   </property> | ||||
|                   <property name="text"> | ||||
|                    <string>Left</string> | ||||
|                   </property> | ||||
|                  </widget> | ||||
|                 </item> | ||||
|                </layout> | ||||
|               </widget> | ||||
|              </item> | ||||
|              <item> | ||||
|               <widget class="QGroupBox" name="buttonMotionRightGroup"> | ||||
|                <property name="title"> | ||||
|                 <string>Motion 2</string> | ||||
|                </property> | ||||
|                <property name="alignment"> | ||||
|                 <set>Qt::AlignCenter</set> | ||||
|                </property> | ||||
|                <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2"> | ||||
|                 <property name="spacing"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="leftMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="topMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="rightMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <property name="bottomMargin"> | ||||
|                  <number>3</number> | ||||
|                 </property> | ||||
|                 <item> | ||||
|                  <widget class="QPushButton" name="buttonMotionRight"> | ||||
|                   <property name="minimumSize"> | ||||
|                    <size> | ||||
|                     <width>57</width> | ||||
|                     <height>0</height> | ||||
|                    </size> | ||||
|                   </property> | ||||
|                   <property name="maximumSize"> | ||||
|                    <size> | ||||
|                     <width>55</width> | ||||
|                     <height>16777215</height> | ||||
|                    </size> | ||||
|                   </property> | ||||
|                   <property name="styleSheet"> | ||||
|                    <string notr="true">min-width: 55px;</string> | ||||
|                   </property> | ||||
|                   <property name="text"> | ||||
|                    <string>Right</string> | ||||
|                   </property> | ||||
|                  </widget> | ||||
|                 </item> | ||||
|                </layout> | ||||
|               </widget> | ||||
|              </item> | ||||
|              <item> | ||||
|               <spacer name="horizontalSpacerMiscButtons4"> | ||||
|                <property name="orientation"> | ||||
| @ -2003,8 +2104,8 @@ | ||||
|                </property> | ||||
|                <property name="sizeHint" stdset="0"> | ||||
|                 <size> | ||||
|                  <width>40</width> | ||||
|                  <height>0</height> | ||||
|                  <width>20</width> | ||||
|                  <height>20</height> | ||||
|                 </size> | ||||
|                </property> | ||||
|               </spacer> | ||||
|  | ||||
| @ -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 = | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei