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>>; | using AnalogDevice = InputDevice<std::tuple<float, float>>; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * A motion device is an input device that returns a tuple of accelerometer state vector and |  * A motion status is an object that returns a tuple of accelerometer state vector, | ||||||
|  * gyroscope state vector. |  * gyroscope state vector, rotation state vector and orientation state matrix. | ||||||
|  * |  * | ||||||
|  * For both vectors: |  * 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. |  *   y+ is normal to the touch screen, pointing outward. | ||||||
|  *   z+ is the same direction as UP on D-pad. |  *   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: |  * For gyroscope state vector: | ||||||
|  *   Orientation is determined by right-hand rule. |  *   Orientation is determined by right-hand rule. | ||||||
|  *   Units: deg/sec |  *   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 |  * 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, |         std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, | ||||||
|                        players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, |                        players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, | ||||||
|                        sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>); |                        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; |     auto& rstick_entry = npad_pad_states[controller_idx].r_stick; | ||||||
|     const auto& button_state = buttons[controller_idx]; |     const auto& button_state = buttons[controller_idx]; | ||||||
|     const auto& analog_state = sticks[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] = |     const auto [stick_l_x_f, stick_l_y_f] = | ||||||
|         analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); |         analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); | ||||||
|     const auto [stick_r_x_f, stick_r_y_f] = |     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; |             continue; | ||||||
|         } |         } | ||||||
|         const u32 npad_index = static_cast<u32>(i); |         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); |         RequestPadStateUpdate(npad_index); | ||||||
|         auto& pad_state = npad_pad_states[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.raw = 0; | ||||||
|         libnx_entry.connection_status.IsConnected.Assign(1); |         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) { |         switch (controller_type) { | ||||||
|         case NPadControllerType::None: |         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; |             main_controller.pad.r_stick = pad_state.r_stick; | ||||||
| 
 | 
 | ||||||
|             libnx_entry.connection_status.IsWired.Assign(1); |             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; |             break; | ||||||
|         case NPadControllerType::Handheld: |         case NPadControllerType::Handheld: | ||||||
|             handheld_entry.connection_status.raw = 0; |             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.IsRightJoyConnected.Assign(1); | ||||||
|             libnx_entry.connection_status.IsLeftJoyWired.Assign(1); |             libnx_entry.connection_status.IsLeftJoyWired.Assign(1); | ||||||
|             libnx_entry.connection_status.IsRightJoyWired.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; |             break; | ||||||
|         case NPadControllerType::JoyDual: |         case NPadControllerType::JoyDual: | ||||||
|             dual_entry.connection_status.raw = 0; |             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.IsLeftJoyConnected.Assign(1); | ||||||
|             libnx_entry.connection_status.IsRightJoyConnected.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; |             break; | ||||||
|         case NPadControllerType::JoyLeft: |         case NPadControllerType::JoyLeft: | ||||||
|             left_entry.connection_status.raw = 0; |             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; |             left_entry.pad.r_stick = pad_state.r_stick; | ||||||
| 
 | 
 | ||||||
|             libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); |             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; |             break; | ||||||
|         case NPadControllerType::JoyRight: |         case NPadControllerType::JoyRight: | ||||||
|             right_entry.connection_status.raw = 0; |             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; |             right_entry.pad.r_stick = pad_state.r_stick; | ||||||
| 
 | 
 | ||||||
|             libnx_entry.connection_status.IsRightJoyConnected.Assign(1); |             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; |             break; | ||||||
|         case NPadControllerType::Pokeball: |         case NPadControllerType::Pokeball: | ||||||
|             pokeball_entry.connection_status.raw = 0; |             pokeball_entry.connection_status.raw = 0; | ||||||
| @ -582,6 +680,14 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo | |||||||
|     return gyroscope_zero_drift_mode; |     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) { | 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_1 = NPadIdToIndex(npad_id_1); | ||||||
|     const auto npad_index_2 = NPadIdToIndex(npad_id_2); |     const auto npad_index_2 = NPadIdToIndex(npad_id_2); | ||||||
|  | |||||||
| @ -130,6 +130,8 @@ public: | |||||||
| 
 | 
 | ||||||
|     void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); |     void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); | ||||||
|     GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; |     GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; | ||||||
|  |     bool IsSixAxisSensorAtRest() const; | ||||||
|  |     void SetSixAxisEnabled(bool six_axis_status); | ||||||
|     LedPattern GetLedPattern(u32 npad_id); |     LedPattern GetLedPattern(u32 npad_id); | ||||||
|     void SetVibrationEnabled(bool can_vibrate); |     void SetVibrationEnabled(bool can_vibrate); | ||||||
|     bool IsVibrationEnabled() const; |     bool IsVibrationEnabled() const; | ||||||
| @ -252,6 +254,24 @@ private: | |||||||
|     }; |     }; | ||||||
|     static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size"); |     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 { |     enum class ColorReadError : u32_le { | ||||||
|         ReadOk = 0, |         ReadOk = 0, | ||||||
|         ColorDoesntExist = 1, |         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 { |     struct NPadEntry { | ||||||
|         NPadType joy_styles; |         NPadType joy_styles; | ||||||
|         NPadAssignments pad_assignment; |         NPadAssignments pad_assignment; | ||||||
| @ -300,9 +327,12 @@ private: | |||||||
|         NPadGeneric pokeball_states; |         NPadGeneric pokeball_states; | ||||||
|         NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
 |         NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
 | ||||||
|                            // relying on this for the time being
 |                            // relying on this for the time being
 | ||||||
|         INSERT_PADDING_BYTES( |         SixAxisGeneric sixaxis_full; | ||||||
|             0x708 * |         SixAxisGeneric sixaxis_handheld; | ||||||
|             6); // TODO(ogniK): SixAxis states, require more information before implementation
 |         SixAxisGeneric sixaxis_dual_left; | ||||||
|  |         SixAxisGeneric sixaxis_dual_right; | ||||||
|  |         SixAxisGeneric sixaxis_left; | ||||||
|  |         SixAxisGeneric sixaxis_right; | ||||||
|         NPadDevice device_type; |         NPadDevice device_type; | ||||||
|         NPadProperties properties; |         NPadProperties properties; | ||||||
|         INSERT_PADDING_WORDS(1); |         INSERT_PADDING_WORDS(1); | ||||||
| @ -325,14 +355,18 @@ private: | |||||||
| 
 | 
 | ||||||
|     NPadType style{}; |     NPadType style{}; | ||||||
|     std::array<NPadEntry, 10> shared_memory_entries{}; |     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>, |         std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>, | ||||||
|         10> |         10>; | ||||||
|         buttons; |     using StickArray = std::array< | ||||||
|     std::array< |  | ||||||
|         std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, |         std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, | ||||||
|         10> |         10>; | ||||||
|         sticks; |     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{}; |     std::vector<u32> supported_npad_id_types{}; | ||||||
|     NpadHoldType hold_type{NpadHoldType::Vertical}; |     NpadHoldType hold_type{NpadHoldType::Vertical}; | ||||||
|     // Each controller should have their own styleset changed event
 |     // Each controller should have their own styleset changed event
 | ||||||
| @ -341,6 +375,8 @@ private: | |||||||
|     std::array<ControllerHolder, 10> connected_controllers{}; |     std::array<ControllerHolder, 10> connected_controllers{}; | ||||||
|     GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; |     GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; | ||||||
|     bool can_controllers_vibrate{true}; |     bool can_controllers_vibrate{true}; | ||||||
|  |     bool sixaxis_sensors_enabled{true}; | ||||||
|  |     bool sixaxis_at_rest{true}; | ||||||
|     std::array<ControllerPad, 10> npad_pad_states{}; |     std::array<ControllerPad, 10> npad_pad_states{}; | ||||||
|     bool is_in_lr_assignment_mode{false}; |     bool is_in_lr_assignment_mode{false}; | ||||||
|     Core::System& system; |     Core::System& system; | ||||||
|  | |||||||
| @ -164,8 +164,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { | |||||||
|         {56, nullptr, "ActivateJoyXpad"}, |         {56, nullptr, "ActivateJoyXpad"}, | ||||||
|         {58, nullptr, "GetJoyXpadLifoHandle"}, |         {58, nullptr, "GetJoyXpadLifoHandle"}, | ||||||
|         {59, nullptr, "GetJoyXpadIds"}, |         {59, nullptr, "GetJoyXpadIds"}, | ||||||
|         {60, nullptr, "ActivateSixAxisSensor"}, |         {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"}, | ||||||
|         {61, nullptr, "DeactivateSixAxisSensor"}, |         {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"}, | ||||||
|         {62, nullptr, "GetSixAxisSensorLifoHandle"}, |         {62, nullptr, "GetSixAxisSensorLifoHandle"}, | ||||||
|         {63, nullptr, "ActivateJoySixAxisSensor"}, |         {63, nullptr, "ActivateJoySixAxisSensor"}, | ||||||
|         {64, nullptr, "DeactivateJoySixAxisSensor"}, |         {64, nullptr, "DeactivateJoySixAxisSensor"}, | ||||||
| @ -329,6 +329,31 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { | |||||||
|     rb.Push(0); |     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) { | void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestParser rp{ctx}; |     IPC::RequestParser rp{ctx}; | ||||||
|     const auto applet_resource_user_id{rp.Pop<u64>()}; |     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 handle{rp.Pop<u32>()}; | ||||||
|     const auto applet_resource_user_id{rp.Pop<u64>()}; |     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); |               applet_resource_user_id); | ||||||
| 
 | 
 | ||||||
|     IPC::ResponseBuilder rb{ctx, 3}; |     IPC::ResponseBuilder rb{ctx, 3}; | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
|     // TODO (Hexagon12): Properly implement reading gyroscope values from controllers.
 |     rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) | ||||||
|     rb.Push(true); |                 .IsSixAxisSensorAtRest()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { | void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { | ||||||
|  | |||||||
| @ -86,6 +86,8 @@ private: | |||||||
|     void CreateAppletResource(Kernel::HLERequestContext& ctx); |     void CreateAppletResource(Kernel::HLERequestContext& ctx); | ||||||
|     void ActivateXpad(Kernel::HLERequestContext& ctx); |     void ActivateXpad(Kernel::HLERequestContext& ctx); | ||||||
|     void GetXpadIDs(Kernel::HLERequestContext& ctx); |     void GetXpadIDs(Kernel::HLERequestContext& ctx); | ||||||
|  |     void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx); | ||||||
|  |     void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx); | ||||||
|     void ActivateDebugPad(Kernel::HLERequestContext& ctx); |     void ActivateDebugPad(Kernel::HLERequestContext& ctx); | ||||||
|     void ActivateTouchScreen(Kernel::HLERequestContext& ctx); |     void ActivateTouchScreen(Kernel::HLERequestContext& ctx); | ||||||
|     void ActivateMouse(Kernel::HLERequestContext& ctx); |     void ActivateMouse(Kernel::HLERequestContext& ctx); | ||||||
|  | |||||||
| @ -152,6 +152,7 @@ struct Values { | |||||||
| 
 | 
 | ||||||
|     bool vibration_enabled; |     bool vibration_enabled; | ||||||
| 
 | 
 | ||||||
|  |     bool motion_enabled; | ||||||
|     std::string motion_device; |     std::string motion_device; | ||||||
|     std::string touch_device; |     std::string touch_device; | ||||||
|     TouchscreenInput touchscreen; |     TouchscreenInput touchscreen; | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ | |||||||
| #include "input_common/main.h" | #include "input_common/main.h" | ||||||
| #include "input_common/motion_emu.h" | #include "input_common/motion_emu.h" | ||||||
| #include "input_common/touch_from_button.h" | #include "input_common/touch_from_button.h" | ||||||
|  | #include "input_common/udp/client.h" | ||||||
| #include "input_common/udp/udp.h" | #include "input_common/udp/udp.h" | ||||||
| #ifdef HAVE_SDL2 | #ifdef HAVE_SDL2 | ||||||
| #include "input_common/sdl/sdl.h" | #include "input_common/sdl/sdl.h" | ||||||
| @ -40,7 +41,11 @@ struct InputSubsystem::Impl { | |||||||
|         sdl = SDL::Init(); |         sdl = SDL::Init(); | ||||||
| #endif | #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() { |     void Shutdown() { | ||||||
| @ -53,12 +58,17 @@ struct InputSubsystem::Impl { | |||||||
| #ifdef HAVE_SDL2 | #ifdef HAVE_SDL2 | ||||||
|         sdl.reset(); |         sdl.reset(); | ||||||
| #endif | #endif | ||||||
|         udp.reset(); |  | ||||||
|         Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); |         Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); | ||||||
|         Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); |         Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); | ||||||
| 
 | 
 | ||||||
|         gcbuttons.reset(); |         gcbuttons.reset(); | ||||||
|         gcanalog.reset(); |         gcanalog.reset(); | ||||||
|  | 
 | ||||||
|  |         Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); | ||||||
|  |         Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); | ||||||
|  | 
 | ||||||
|  |         udpmotion.reset(); | ||||||
|  |         udptouch.reset(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { |     [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { | ||||||
| @ -109,14 +119,28 @@ struct InputSubsystem::Impl { | |||||||
|         return {}; |         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<Keyboard> keyboard; | ||||||
|     std::shared_ptr<MotionEmu> motion_emu; |     std::shared_ptr<MotionEmu> motion_emu; | ||||||
| #ifdef HAVE_SDL2 | #ifdef HAVE_SDL2 | ||||||
|     std::unique_ptr<SDL::State> sdl; |     std::unique_ptr<SDL::State> sdl; | ||||||
| #endif | #endif | ||||||
|     std::unique_ptr<CemuhookUDP::State> udp; |  | ||||||
|     std::shared_ptr<GCButtonFactory> gcbuttons; |     std::shared_ptr<GCButtonFactory> gcbuttons; | ||||||
|     std::shared_ptr<GCAnalogFactory> gcanalog; |     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>()} {} | InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} | ||||||
| @ -175,6 +199,22 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const { | |||||||
|     return impl->gcbuttons.get(); |     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() { | void InputSubsystem::ReloadInputDevices() { | ||||||
|     if (!impl->udp) { |     if (!impl->udp) { | ||||||
|         return; |         return; | ||||||
|  | |||||||
| @ -21,10 +21,14 @@ namespace Settings::NativeButton { | |||||||
| enum Values : int; | enum Values : int; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | namespace Settings::NativeMotion { | ||||||
|  | enum Values : int; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| namespace InputCommon { | namespace InputCommon { | ||||||
| namespace Polling { | 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 |  * 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 GCAnalogFactory; | ||||||
| class GCButtonFactory; | class GCButtonFactory; | ||||||
|  | class UDPMotionFactory; | ||||||
|  | class UDPTouchFactory; | ||||||
| class Keyboard; | class Keyboard; | ||||||
| class MotionEmu; | class MotionEmu; | ||||||
| 
 | 
 | ||||||
| @ -59,6 +65,7 @@ class MotionEmu; | |||||||
|  */ |  */ | ||||||
| using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; | using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; | ||||||
| using ButtonMapping = std::unordered_map<Settings::NativeButton::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 { | class InputSubsystem { | ||||||
| public: | public: | ||||||
| @ -103,6 +110,9 @@ public: | |||||||
|     /// Retrieves the button mappings for the given device.
 |     /// Retrieves the button mappings for the given device.
 | ||||||
|     [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; |     [[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.
 |     /// Retrieves the underlying GameCube analog handler.
 | ||||||
|     [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); |     [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); | ||||||
| 
 | 
 | ||||||
| @ -115,6 +125,18 @@ public: | |||||||
|     /// Retrieves the underlying GameCube button handler.
 |     /// Retrieves the underlying GameCube button handler.
 | ||||||
|     [[nodiscard]] const GCButtonFactory* GetGCButtons() const; |     [[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
 |     /// Reloads the input devices
 | ||||||
|     void ReloadInputDevices(); |     void ReloadInputDevices(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -56,7 +56,7 @@ public: | |||||||
|         is_tilting = false; |         is_tilting = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { |     Input::MotionStatus GetStatus() { | ||||||
|         std::lock_guard guard{status_mutex}; |         std::lock_guard guard{status_mutex}; | ||||||
|         return status; |         return status; | ||||||
|     } |     } | ||||||
| @ -76,7 +76,7 @@ private: | |||||||
| 
 | 
 | ||||||
|     Common::Event shutdown_event; |     Common::Event shutdown_event; | ||||||
| 
 | 
 | ||||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; |     Input::MotionStatus status; | ||||||
|     std::mutex status_mutex; |     std::mutex status_mutex; | ||||||
| 
 | 
 | ||||||
|     // Note: always keep the thread declaration at the end so that other objects are initialized
 |     // 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); |             gravity = QuaternionRotate(inv_q, gravity); | ||||||
|             angular_rate = QuaternionRotate(inv_q, angular_rate); |             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
 |             // Update the sensor state
 | ||||||
|             { |             { | ||||||
|                 std::lock_guard guard{status_mutex}; |                 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); |         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(); |         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 { | namespace NativeAnalog { | ||||||
| const std::array<const char*, NumAnalogs> mapping = {{ | const std::array<const char*, NumAnalogs> mapping = {{ | ||||||
|     "lstick", |     "lstick", | ||||||
|  | |||||||
| @ -66,6 +66,21 @@ constexpr int NUM_STICKS_HID = NumAnalogs; | |||||||
| extern const std::array<const char*, NumAnalogs> mapping; | extern const std::array<const char*, NumAnalogs> mapping; | ||||||
| } // namespace NativeAnalog
 | } // 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 { | namespace NativeMouseButton { | ||||||
| enum Values { | enum Values { | ||||||
|     Left, |     Left, | ||||||
| @ -292,6 +307,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; | |||||||
| 
 | 
 | ||||||
| using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; | using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; | ||||||
| using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; | 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 MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; | ||||||
| using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; | using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; | ||||||
| using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; | using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; | ||||||
| @ -314,6 +330,7 @@ struct PlayerInput { | |||||||
|     ControllerType controller_type; |     ControllerType controller_type; | ||||||
|     ButtonsRaw buttons; |     ButtonsRaw buttons; | ||||||
|     AnalogsRaw analogs; |     AnalogsRaw analogs; | ||||||
|  |     MotionRaw motions; | ||||||
|     std::string lstick_mod; |     std::string lstick_mod; | ||||||
|     std::string rstick_mod; |     std::string rstick_mod; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,14 +2,13 @@ | |||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <algorithm> |  | ||||||
| #include <array> |  | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <cstring> | #include <cstring> | ||||||
| #include <functional> | #include <functional> | ||||||
| #include <thread> | #include <thread> | ||||||
| #include <boost/asio.hpp> | #include <boost/asio.hpp> | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | #include "core/settings.h" | ||||||
| #include "input_common/udp/client.h" | #include "input_common/udp/client.h" | ||||||
| #include "input_common/udp/protocol.h" | #include "input_common/udp/protocol.h" | ||||||
| 
 | 
 | ||||||
| @ -131,21 +130,59 @@ static void SocketLoop(Socket* socket) { | |||||||
|     socket->Loop(); |     socket->Loop(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, | Client::Client() { | ||||||
|                u8 pad_index, u32 client_id) |     LOG_INFO(Input, "Udp Initialization started"); | ||||||
|     : status(std::move(status)) { |     for (std::size_t client = 0; client < clients.size(); client++) { | ||||||
|     StartCommunication(host, port, pad_index, client_id); |         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() { | Client::~Client() { | ||||||
|     socket->Stop(); |     Reset(); | ||||||
|     thread.join(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 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) { | void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { | ||||||
|     socket->Stop(); |     // client number must be determined from host / port and pad index
 | ||||||
|     thread.join(); |     std::size_t client = pad_index; | ||||||
|     StartCommunication(host, port, pad_index, client_id); |     clients[client].socket->Stop(); | ||||||
|  |     clients[client].thread.join(); | ||||||
|  |     StartCommunication(client, host, port, pad_index, client_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Client::OnVersion(Response::Version data) { | void Client::OnVersion(Response::Version data) { | ||||||
| @ -157,23 +194,39 @@ void Client::OnPortInfo(Response::PortInfo data) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Client::OnPadData(Response::PadData 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"); |     LOG_TRACE(Input, "PadData packet received"); | ||||||
|     if (data.packet_counter <= packet_sequence) { |     if (data.packet_counter == clients[client].packet_sequence) { | ||||||
|         LOG_WARNING( |         LOG_WARNING( | ||||||
|             Input, |             Input, | ||||||
|             "PadData packet dropped because its stale info. Current count: {} Packet count: {}", |             "PadData packet dropped because its stale info. Current count: {} Packet count: {}", | ||||||
|             packet_sequence, data.packet_counter); |             clients[client].packet_sequence, data.packet_counter); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     packet_sequence = data.packet_counter; |     clients[client].active = data.info.is_pad_active; | ||||||
|     // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
 |     clients[client].packet_sequence = data.packet_counter; | ||||||
|     // directions correspond to the ones of the Switch
 |     const auto now = std::chrono::system_clock::now(); | ||||||
|     Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); |     u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>( | ||||||
|     Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); |                               now - clients[client].last_motion_update) | ||||||
|     { |                               .count(); | ||||||
|         std::lock_guard guard(status->update_mutex); |     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
 |         // 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.
 |         // 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 x = 0; | ||||||
|         float y = 0; |         float y = 0; | ||||||
| 
 | 
 | ||||||
|         if (is_active && status->touch_calibration) { |         if (is_active && clients[client].status.touch_calibration) { | ||||||
|             const u16 min_x = status->touch_calibration->min_x; |             const u16 min_x = clients[client].status.touch_calibration->min_x; | ||||||
|             const u16 max_x = status->touch_calibration->max_x; |             const u16 max_x = clients[client].status.touch_calibration->max_x; | ||||||
|             const u16 min_y = status->touch_calibration->min_y; |             const u16 min_y = clients[client].status.touch_calibration->min_y; | ||||||
|             const u16 max_y = status->touch_calibration->max_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) / |             x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / | ||||||
|                 static_cast<float>(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); |                 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); }, |     SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, | ||||||
|                             [this](Response::PortInfo info) { OnPortInfo(info); }, |                             [this](Response::PortInfo info) { OnPortInfo(info); }, | ||||||
|                             [this](Response::PadData data) { OnPadData(data); }}; |                             [this](Response::PadData data) { OnPadData(data); }}; | ||||||
|     LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); |     LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); | ||||||
|     socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); |     clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); | ||||||
|     thread = std::thread{SocketLoop, this->socket.get()}; |     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, | void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, | ||||||
|  | |||||||
| @ -12,8 +12,12 @@ | |||||||
| #include <thread> | #include <thread> | ||||||
| #include <tuple> | #include <tuple> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
|  | #include "common/param_package.h" | ||||||
| #include "common/thread.h" | #include "common/thread.h" | ||||||
|  | #include "common/threadsafe_queue.h" | ||||||
| #include "common/vector_math.h" | #include "common/vector_math.h" | ||||||
|  | #include "core/frontend/input.h" | ||||||
|  | #include "input_common/motion_input.h" | ||||||
| 
 | 
 | ||||||
| namespace InputCommon::CemuhookUDP { | namespace InputCommon::CemuhookUDP { | ||||||
| 
 | 
 | ||||||
| @ -28,9 +32,30 @@ struct PortInfo; | |||||||
| struct Version; | struct Version; | ||||||
| } // namespace Response
 | } // 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 { | struct DeviceStatus { | ||||||
|     std::mutex update_mutex; |     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; |     std::tuple<float, float, bool> touch_status; | ||||||
| 
 | 
 | ||||||
|     // calibration data for scaling the device's touch area to 3ds
 |     // calibration data for scaling the device's touch area to 3ds
 | ||||||
| @ -45,22 +70,58 @@ struct DeviceStatus { | |||||||
| 
 | 
 | ||||||
| class Client { | class Client { | ||||||
| public: | public: | ||||||
|     explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, |     // Initialize the UDP client capture and read sequence
 | ||||||
|                     u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); |     Client(); | ||||||
|  | 
 | ||||||
|  |     // Close and release the client
 | ||||||
|     ~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, |     void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, | ||||||
|                       u32 client_id = 24872); |                       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: | 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 OnVersion(Response::Version); | ||||||
|     void OnPortInfo(Response::PortInfo); |     void OnPortInfo(Response::PortInfo); | ||||||
|     void OnPadData(Response::PadData); |     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; |     bool configuring = false; | ||||||
|     std::shared_ptr<DeviceStatus> status; | 
 | ||||||
|     std::thread thread; |     std::array<ClientData, 4> clients; | ||||||
|     u64 packet_sequence = 0; |     std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// An async job allowing configuration of the touchpad calibration.
 | /// 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
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #include <atomic> | ||||||
|  | #include <list> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <optional> | #include <utility> | ||||||
| #include <tuple> | #include "common/assert.h" | ||||||
| 
 | #include "common/threadsafe_queue.h" | ||||||
| #include "common/param_package.h" |  | ||||||
| #include "core/frontend/input.h" |  | ||||||
| #include "core/settings.h" |  | ||||||
| #include "input_common/udp/client.h" | #include "input_common/udp/client.h" | ||||||
| #include "input_common/udp/udp.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: | public: | ||||||
|     explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} |     UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_) | ||||||
|     std::tuple<float, float, bool> GetStatus() const override { |         : ip(ip_), port(port_), pad(pad_), client(client_) {} | ||||||
|         std::lock_guard guard(status->update_mutex); | 
 | ||||||
|         return status->touch_status; |     Input::MotionStatus GetStatus() const override { | ||||||
|  |         return client->GetPadState(pad).motion_status; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | 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 { | /// A motion device factory that creates motion devices from JC Adapter
 | ||||||
| public: | UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_) | ||||||
|     explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} |     : client(std::move(client_)) {} | ||||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { |  | ||||||
|         std::lock_guard guard(status->update_mutex); |  | ||||||
|         return status->motion_status; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
| private: | /**
 | ||||||
|     std::shared_ptr<DeviceStatus> status; |  * 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); | ||||||
| 
 | 
 | ||||||
| class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { |     return std::make_unique<UDPMotion>(ip, port, pad, client.get()); | ||||||
| public: | } | ||||||
|     explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} |  | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { | void UDPMotionFactory::BeginConfiguration() { | ||||||
|         { |     polling = true; | ||||||
|             std::lock_guard guard(status->update_mutex); |     client->BeginConfiguration(); | ||||||
|             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); | void UDPMotionFactory::EndConfiguration() { | ||||||
|             status->touch_calibration->min_y = params.Get("min_y", 50); |     polling = false; | ||||||
|             status->touch_calibration->max_x = params.Get("max_x", 1800); |     client->EndConfiguration(); | ||||||
|             status->touch_calibration->max_y = params.Get("max_y", 850); | } | ||||||
|  | 
 | ||||||
|  | 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 std::make_unique<UDPTouchDevice>(status); |  | ||||||
|     } |     } | ||||||
|  |     return params; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| private: | class UDPTouch final : public Input::TouchDevice { | ||||||
|     std::shared_ptr<DeviceStatus> status; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { |  | ||||||
| public: | public: | ||||||
|     explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> 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<Input::MotionDevice> Create(const Common::ParamPackage& params) override { |     std::tuple<float, float, bool> GetStatus() const override { | ||||||
|         return std::make_unique<UDPMotionDevice>(status); |         return client->GetPadState(pad).touch_status; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::shared_ptr<DeviceStatus> status; |     const std::string ip; | ||||||
|  |     const int port; | ||||||
|  |     const int pad; | ||||||
|  |     CemuhookUDP::Client* client; | ||||||
|  |     mutable std::mutex mutex; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| State::State() { | /// A motion device factory that creates motion devices from JC Adapter
 | ||||||
|     auto status = std::make_shared<DeviceStatus>(); | UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_) | ||||||
|     client = |     : client(std::move(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); |  * 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); | ||||||
| 
 | 
 | ||||||
|     Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory); |     return std::make_unique<UDPTouch>(ip, port, pad, client.get()); | ||||||
|     Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| State::~State() { | void UDPTouchFactory::BeginConfiguration() { | ||||||
|     Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); |     polling = true; | ||||||
|     Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); |     client->BeginConfiguration(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<Common::ParamPackage> State::GetInputDevices() const { | void UDPTouchFactory::EndConfiguration() { | ||||||
|     // TODO support binding udp devices
 |     polling = false; | ||||||
|     return {}; |     client->EndConfiguration(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void State::ReloadUDPClient() { | Common::ParamPackage UDPTouchFactory::GetNextInput() { | ||||||
|     client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, |     Common::ParamPackage params; | ||||||
|                          Settings::values.udp_pad_index); |     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; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<State> Init() { | } // namespace InputCommon
 | ||||||
|     return std::make_unique<State>(); |  | ||||||
| } |  | ||||||
| } // namespace InputCommon::CemuhookUDP
 |  | ||||||
|  | |||||||
| @ -1,32 +1,57 @@ | |||||||
| // Copyright 2018 Citra Emulator Project
 | // Copyright 2020 yuzu Emulator Project
 | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include "core/frontend/input.h" | ||||||
| #include "common/param_package.h" | #include "input_common/udp/client.h" | ||||||
| 
 | 
 | ||||||
| namespace InputCommon::CemuhookUDP { | namespace InputCommon { | ||||||
| 
 | 
 | ||||||
| class Client; | /// A motion device factory that creates motion devices from udp clients
 | ||||||
| class UDPMotionFactory; | class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { | ||||||
| class UDPTouchFactory; |  | ||||||
| 
 |  | ||||||
| class State { |  | ||||||
| public: | public: | ||||||
|     State(); |     explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_); | ||||||
|     ~State(); | 
 | ||||||
|     void ReloadUDPClient(); |     std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; | ||||||
|     std::vector<Common::ParamPackage> GetInputDevices() const; | 
 | ||||||
|  |     Common::ParamPackage GetNextInput(); | ||||||
|  | 
 | ||||||
|  |     /// For device input configuration/polling
 | ||||||
|  |     void BeginConfiguration(); | ||||||
|  |     void EndConfiguration(); | ||||||
|  | 
 | ||||||
|  |     bool IsPolling() const { | ||||||
|  |         return polling; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::unique_ptr<Client> client; |     std::shared_ptr<CemuhookUDP::Client> client; | ||||||
|     std::shared_ptr<UDPMotionFactory> motion_factory; |     bool polling = false; | ||||||
|     std::shared_ptr<UDPTouchFactory> touch_factory; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 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, |     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{{ | const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ | ||||||
|     { |     { | ||||||
|         Qt::Key_Up, |         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) { |         for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { | ||||||
|             const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( |             const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( | ||||||
|                 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], |                 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], | ||||||
| @ -424,6 +445,7 @@ void Config::ReadControlValues() { | |||||||
| 
 | 
 | ||||||
|     Settings::values.vibration_enabled = |     Settings::values.vibration_enabled = | ||||||
|         ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); |         ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); | ||||||
|  |     Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool(); | ||||||
|     Settings::values.use_docked_mode = |     Settings::values.use_docked_mode = | ||||||
|         ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); |         ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); | ||||||
| 
 | 
 | ||||||
| @ -922,6 +944,14 @@ void Config::SavePlayerValues() { | |||||||
|                          QString::fromStdString(player.buttons[i]), |                          QString::fromStdString(player.buttons[i]), | ||||||
|                          QString::fromStdString(default_param)); |                          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) { |         for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { | ||||||
|             const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( |             const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( | ||||||
|                 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], |                 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], | ||||||
| @ -1062,6 +1092,7 @@ void Config::SaveControlValues() { | |||||||
|     SaveMotionTouchValues(); |     SaveMotionTouchValues(); | ||||||
| 
 | 
 | ||||||
|     WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); |     WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); | ||||||
|  |     WriteSetting(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true); | ||||||
|     WriteSetting(QStringLiteral("motion_device"), |     WriteSetting(QStringLiteral("motion_device"), | ||||||
|                  QString::fromStdString(Settings::values.motion_device), |                  QString::fromStdString(Settings::values.motion_device), | ||||||
|                  QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); |                  QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ public: | |||||||
|     void Save(); |     void Save(); | ||||||
| 
 | 
 | ||||||
|     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; |     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<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; | ||||||
|     static const std::array<int, 2> default_stick_mod; |     static const std::array<int, 2> default_stick_mod; | ||||||
|     static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> |     static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> | ||||||
|  | |||||||
| @ -146,6 +146,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, | |||||||
|                 CallConfigureDialog<ConfigureMotionTouch>(*this, 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->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); | ||||||
|     connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); |     connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); | ||||||
| 
 | 
 | ||||||
| @ -172,6 +176,7 @@ void ConfigureInput::ApplyConfiguration() { | |||||||
|     OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); |     OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); | ||||||
| 
 | 
 | ||||||
|     Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); |     Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); | ||||||
|  |     Settings::values.motion_enabled = ui->motionGroup->isChecked(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConfigureInput::changeEvent(QEvent* event) { | void ConfigureInput::changeEvent(QEvent* event) { | ||||||
| @ -191,6 +196,7 @@ void ConfigureInput::LoadConfiguration() { | |||||||
|     UpdateDockedState(Settings::values.players[8].connected); |     UpdateDockedState(Settings::values.players[8].connected); | ||||||
| 
 | 
 | ||||||
|     ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); |     ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); | ||||||
|  |     ui->motionGroup->setChecked(Settings::values.motion_enabled); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConfigureInput::LoadPlayerControllerIndices() { | void ConfigureInput::LoadPlayerControllerIndices() { | ||||||
| @ -217,6 +223,7 @@ void ConfigureInput::RestoreDefaults() { | |||||||
|     ui->radioDocked->setChecked(true); |     ui->radioDocked->setChecked(true); | ||||||
|     ui->radioUndocked->setChecked(false); |     ui->radioUndocked->setChecked(false); | ||||||
|     ui->vibrationGroup->setChecked(true); |     ui->vibrationGroup->setChecked(true); | ||||||
|  |     ui->motionGroup->setChecked(true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConfigureInput::UpdateDockedState(bool is_handheld) { | void ConfigureInput::UpdateDockedState(bool is_handheld) { | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ | |||||||
| #include "core/hle/service/sm/sm.h" | #include "core/hle/service/sm/sm.h" | ||||||
| #include "input_common/gcadapter/gc_poller.h" | #include "input_common/gcadapter/gc_poller.h" | ||||||
| #include "input_common/main.h" | #include "input_common/main.h" | ||||||
|  | #include "input_common/udp/udp.h" | ||||||
| #include "ui_configure_input_player.h" | #include "ui_configure_input_player.h" | ||||||
| #include "yuzu/configuration/config.h" | #include "yuzu/configuration/config.h" | ||||||
| #include "yuzu/configuration/configure_input_player.h" | #include "yuzu/configuration/configure_input_player.h" | ||||||
| @ -149,6 +150,14 @@ QString ButtonToText(const Common::ParamPackage& param) { | |||||||
|         return GetKeyName(param.Get("code", 0)); |         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.Get("engine", "") == "sdl") { | ||||||
|         if (param.Has("hat")) { |         if (param.Has("hat")) { | ||||||
|             const QString hat_str = QString::fromStdString(param.Get("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_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; | ||||||
|     analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; |     analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; | ||||||
|     analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; |     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]); |                              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.
 |     // Handle clicks for the modifier buttons as well.
 | ||||||
|     ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]); |     ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]); | ||||||
|     ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]); |     ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]); | ||||||
| @ -385,9 +425,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||||||
| 
 | 
 | ||||||
|     UpdateControllerIcon(); |     UpdateControllerIcon(); | ||||||
|     UpdateControllerAvailableButtons(); |     UpdateControllerAvailableButtons(); | ||||||
|  |     UpdateMotionButtons(); | ||||||
|     connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { |     connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { | ||||||
|         UpdateControllerIcon(); |         UpdateControllerIcon(); | ||||||
|         UpdateControllerAvailableButtons(); |         UpdateControllerAvailableButtons(); | ||||||
|  |         UpdateMotionButtons(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, |     connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, | ||||||
| @ -417,6 +459,13 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         if (input_subsystem->GetUDPMotions()->IsPolling()) { | ||||||
|  |             params = input_subsystem->GetUDPMotions()->GetNextInput(); | ||||||
|  |             if (params.Has("engine")) { | ||||||
|  |                 SetPollingResult(params, false); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         for (auto& poller : device_pollers) { |         for (auto& poller : device_pollers) { | ||||||
|             params = poller->GetNextInput(); |             params = poller->GetNextInput(); | ||||||
|             if (params.Has("engine")) { |             if (params.Has("engine")) { | ||||||
| @ -448,6 +497,10 @@ void ConfigureInputPlayer::ApplyConfiguration() { | |||||||
|         return; |         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 = |     player.controller_type = | ||||||
|         static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); |         static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); | ||||||
|     player.connected = ui->groupConnectedController->isChecked(); |     player.connected = ui->groupConnectedController->isChecked(); | ||||||
| @ -501,6 +554,8 @@ void ConfigureInputPlayer::LoadConfiguration() { | |||||||
|                        [](const std::string& str) { return Common::ParamPackage(str); }); |                        [](const std::string& str) { return Common::ParamPackage(str); }); | ||||||
|         std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), |         std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), | ||||||
|                        [](const std::string& str) { return Common::ParamPackage(str); }); |                        [](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(); |     UpdateUI(); | ||||||
| @ -544,6 +599,12 @@ void ConfigureInputPlayer::RestoreDefaults() { | |||||||
|             SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); |             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(); |     UpdateUI(); | ||||||
|     UpdateInputDevices(); |     UpdateInputDevices(); | ||||||
|     ui->comboControllerType->setCurrentIndex(0); |     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(); |     UpdateUI(); | ||||||
|     UpdateInputDevices(); |     UpdateInputDevices(); | ||||||
| } | } | ||||||
| @ -582,6 +652,10 @@ void ConfigureInputPlayer::UpdateUI() { | |||||||
|         button_map[button]->setText(ButtonToText(buttons_param[button])); |         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->buttonLStickMod->setText(ButtonToText(lstick_mod)); | ||||||
|     ui->buttonRStickMod->setText(ButtonToText(rstick_mod)); |     ui->buttonRStickMod->setText(ButtonToText(rstick_mod)); | ||||||
| 
 | 
 | ||||||
| @ -659,7 +733,11 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() { | |||||||
| void ConfigureInputPlayer::HandleClick( | void ConfigureInputPlayer::HandleClick( | ||||||
|     QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, |     QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, | ||||||
|     InputCommon::Polling::DeviceType type) { |     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(); |     button->setFocus(); | ||||||
| 
 | 
 | ||||||
|     // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
 |     // 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(); |         input_subsystem->GetGCAnalogs()->BeginConfiguration(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (type == InputCommon::Polling::DeviceType::Motion) { | ||||||
|  |         input_subsystem->GetUDPMotions()->BeginConfiguration(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     timeout_timer->start(2500); // Cancel after 2.5 seconds
 |     timeout_timer->start(2500); // Cancel after 2.5 seconds
 | ||||||
|     poll_timer->start(50);      // Check for new inputs every 50ms
 |     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->GetGCButtons()->EndConfiguration(); | ||||||
|     input_subsystem->GetGCAnalogs()->EndConfiguration(); |     input_subsystem->GetGCAnalogs()->EndConfiguration(); | ||||||
| 
 | 
 | ||||||
|  |     input_subsystem->GetUDPMotions()->EndConfiguration(); | ||||||
|  | 
 | ||||||
|     if (!abort) { |     if (!abort) { | ||||||
|         (*input_setter)(params); |         (*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) { | void ConfigureInputPlayer::showEvent(QShowEvent* event) { | ||||||
|     if (bottom_row == nullptr) { |     if (bottom_row == nullptr) { | ||||||
|         return; |         return; | ||||||
|  | |||||||
| @ -107,6 +107,9 @@ private: | |||||||
|     /// Hides and disables controller settings based on the current controller type.
 |     /// Hides and disables controller settings based on the current controller type.
 | ||||||
|     void UpdateControllerAvailableButtons(); |     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.
 |     /// Gets the default controller mapping for this device and auto configures the input to match.
 | ||||||
|     void UpdateMappingWithDefaults(); |     void UpdateMappingWithDefaults(); | ||||||
| 
 | 
 | ||||||
| @ -128,11 +131,14 @@ private: | |||||||
| 
 | 
 | ||||||
|     std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; |     std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; | ||||||
|     std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_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; |     static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; | ||||||
| 
 | 
 | ||||||
|     /// Each button input is represented by a QPushButton.
 |     /// Each button input is represented by a QPushButton.
 | ||||||
|     std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; |     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.
 |     /// Extra buttons for the modifiers.
 | ||||||
|     Common::ParamPackage lstick_mod; |     Common::ParamPackage lstick_mod; | ||||||
|     Common::ParamPackage rstick_mod; |     Common::ParamPackage rstick_mod; | ||||||
|  | |||||||
| @ -1983,6 +1983,9 @@ | |||||||
|              <property name="spacing"> |              <property name="spacing"> | ||||||
|               <number>3</number> |               <number>3</number> | ||||||
|              </property> |              </property> | ||||||
|  |              <property name="topMargin"> | ||||||
|  |               <number>0</number> | ||||||
|  |              </property> | ||||||
|              <item> |              <item> | ||||||
|               <spacer name="horizontalSpacerMiscButtons1"> |               <spacer name="horizontalSpacerMiscButtons1"> | ||||||
|                <property name="orientation"> |                <property name="orientation"> | ||||||
| @ -1990,12 +1993,110 @@ | |||||||
|                </property> |                </property> | ||||||
|                <property name="sizeHint" stdset="0"> |                <property name="sizeHint" stdset="0"> | ||||||
|                 <size> |                 <size> | ||||||
|                  <width>40</width> |                  <width>20</width> | ||||||
|                  <height>0</height> |                  <height>20</height> | ||||||
|                 </size> |                 </size> | ||||||
|                </property> |                </property> | ||||||
|               </spacer> |               </spacer> | ||||||
|              </item> |              </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> |              <item> | ||||||
|               <spacer name="horizontalSpacerMiscButtons4"> |               <spacer name="horizontalSpacerMiscButtons4"> | ||||||
|                <property name="orientation"> |                <property name="orientation"> | ||||||
| @ -2003,8 +2104,8 @@ | |||||||
|                </property> |                </property> | ||||||
|                <property name="sizeHint" stdset="0"> |                <property name="sizeHint" stdset="0"> | ||||||
|                 <size> |                 <size> | ||||||
|                  <width>40</width> |                  <width>20</width> | ||||||
|                  <height>0</height> |                  <height>20</height> | ||||||
|                 </size> |                 </size> | ||||||
|                </property> |                </property> | ||||||
|               </spacer> |               </spacer> | ||||||
|  | |||||||
| @ -290,6 +290,8 @@ void Config::ReadValues() { | |||||||
| 
 | 
 | ||||||
|     Settings::values.vibration_enabled = |     Settings::values.vibration_enabled = | ||||||
|         sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true); |         sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true); | ||||||
|  |     Settings::values.motion_enabled = | ||||||
|  |         sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true); | ||||||
|     Settings::values.touchscreen.enabled = |     Settings::values.touchscreen.enabled = | ||||||
|         sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); |         sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); | ||||||
|     Settings::values.touchscreen.device = |     Settings::values.touchscreen.device = | ||||||
|  | |||||||
| @ -76,6 +76,7 @@ void Config::ReadValues() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Settings::values.vibration_enabled = true; |     Settings::values.vibration_enabled = true; | ||||||
|  |     Settings::values.motion_enabled = true; | ||||||
|     Settings::values.touchscreen.enabled = ""; |     Settings::values.touchscreen.enabled = ""; | ||||||
|     Settings::values.touchscreen.device = ""; |     Settings::values.touchscreen.device = ""; | ||||||
|     Settings::values.touchscreen.finger = 0; |     Settings::values.touchscreen.finger = 0; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei