diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index fb2ce2514c..25ac5af469 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -121,6 +121,13 @@ using ButtonDevice = InputDevice; */ using AnalogDevice = InputDevice>; +/** + * A vibration device is an input device that returns an unsigned byte as status. + * It represents whether the vibration device supports vibration or not. + * If the status returns 1, it supports vibration. Otherwise, it does not support vibration. + */ +using VibrationDevice = InputDevice; + /** * A motion status is an object that returns a tuple of accelerometer state vector, * gyroscope state vector, rotation state vector and orientation state matrix. diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index dc9954377f..27099de247 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -271,6 +271,10 @@ void Controller_NPad::OnLoadInputDevices() { std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks[i].begin(), Input::CreateDevice); + std::transform(players[i].vibrations.begin() + + Settings::NativeVibration::VIBRATION_HID_BEGIN, + players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END, + vibrations[i].begin(), Input::CreateDevice); std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END, motions[i].begin(), Input::CreateDevice); @@ -278,8 +282,10 @@ void Controller_NPad::OnLoadInputDevices() { } void Controller_NPad::OnRelease() { - for (std::size_t index = 0; index < connected_controllers.size(); ++index) { - VibrateControllerAtIndex(index, {}); + for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) { + for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) { + VibrateControllerAtIndex(npad_idx, device_idx); + } } } @@ -674,9 +680,9 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) } } -bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, +bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, const VibrationValue& vibration_value) { - if (!connected_controllers[npad_index].is_connected) { + if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) { return false; } @@ -686,10 +692,7 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, return false; } - using namespace Settings::NativeButton; - const auto& button_state = buttons[npad_index]; - - return button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( + return vibrations[npad_index][device_index]->SetRumblePlay( std::min(vibration_value.amp_low * player.vibration_strength / 100.0f, 1.0f), vibration_value.freq_low, std::min(vibration_value.amp_high * player.vibration_strength / 100.0f, 1.0f), @@ -717,6 +720,11 @@ void Controller_NPad::VibrateControllers(const std::vector& vibrat continue; } + if (vibration_device_handles[i].device_index == DeviceIndex::None) { + UNREACHABLE_MSG("DeviceIndex should never be None!"); + continue; + } + // Some games try to send mismatched parameters in the device handle, block these. if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft && (vibration_device_handles[i].npad_type == NpadType::JoyconRight || @@ -747,28 +755,8 @@ void Controller_NPad::VibrateControllers(const std::vector& vibrat continue; } - // TODO: Vibrate left/right vibration motors independently if possible. - if (VibrateControllerAtIndex(npad_index, vibration_values[i])) { - switch (connected_controllers[npad_index].type) { - case NPadControllerType::None: - UNREACHABLE(); - break; - case NPadControllerType::ProController: - case NPadControllerType::Handheld: - case NPadControllerType::JoyDual: - // Since we can't vibrate motors independently yet, we can reduce state changes by - // assigning all 3 device indices the current vibration value. - latest_vibration_values[npad_index][0] = vibration_values[i]; - latest_vibration_values[npad_index][1] = vibration_values[i]; - latest_vibration_values[npad_index][2] = vibration_values[i]; - break; - case NPadControllerType::JoyLeft: - case NPadControllerType::JoyRight: - case NPadControllerType::Pokeball: - default: - latest_vibration_values[npad_index][device_index] = vibration_values[i]; - break; - } + if (VibrateControllerAtIndex(npad_index, device_index, vibration_values[i])) { + latest_vibration_values[npad_index][device_index] = vibration_values[i]; } } } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 576ef15588..3ae9fb8e6e 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -148,7 +148,8 @@ public: void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode); - bool VibrateControllerAtIndex(std::size_t npad_index, const VibrationValue& vibration_value); + bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, + const VibrationValue& vibration_value = {}); void VibrateControllers(const std::vector& vibration_device_handles, const std::vector& vibration_values); @@ -399,18 +400,22 @@ private: using StickArray = std::array< std::array, Settings::NativeAnalog::NUM_STICKS_HID>, 10>; + using VibrationArray = std::array, + Settings::NativeVibration::NUM_VIBRATIONS_HID>, + 10>; using MotionArray = std::array< - std::array, Settings::NativeMotion::NUM_MOTION_HID>, + std::array, Settings::NativeMotion::NUM_MOTIONS_HID>, 10>; ButtonArray buttons; StickArray sticks; + VibrationArray vibrations; MotionArray motions; std::vector supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; // Each controller should have their own styleset changed event std::array styleset_changed_events; - std::array, 10> latest_vibration_values; + std::array, 10> latest_vibration_values{}; std::array connected_controllers{}; std::array unintended_home_button_input_protection{}; GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index e88f30d6ab..1d882a9775 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -998,6 +998,7 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { break; case Controller_NPad::DeviceIndex::None: default: + UNREACHABLE_MSG("DeviceIndex should never be None!"); vibration_device_info.position = VibrationDevicePosition::None; break; } diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index b912188b62..d80195c827 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -230,10 +230,8 @@ void Adapter::SendVibrations() { vibration_changed = false; } -bool Adapter::RumblePlay(std::size_t port, f32 amplitude) { - amplitude = std::clamp(amplitude, 0.0f, 1.0f); - const auto raw_amp = static_cast(amplitude * 0x8); - pads[port].rumble_amplitude = raw_amp; +bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { + pads[port].rumble_amplitude = amplitude; return rumble_enabled; } diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index d28dcfad35..f1256c9dad 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -77,8 +77,8 @@ public: Adapter(); ~Adapter(); - /// Request a vibration for a controlelr - bool RumblePlay(std::size_t port, f32 amplitude); + /// Request a vibration for a controller + bool RumblePlay(std::size_t port, u8 amplitude); /// Used for polling void BeginConfiguration(); diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 6bd6f57fc3..fe57c13a54 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -15,7 +15,7 @@ namespace InputCommon { class GCButton final : public Input::ButtonDevice { public: - explicit GCButton(u32 port_, s32 button_, GCAdapter::Adapter* adapter) + explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter) : port(port_), button(button_), gcadapter(adapter) {} ~GCButton() override; @@ -27,18 +27,10 @@ public: return false; } - bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override { - const float amplitude = amp_high + amp_low > 2.0f ? 1.0f : (amp_high + amp_low) * 0.5f; - const auto new_amp = - static_cast(pow(amplitude, 0.5f) * (3.0f - 2.0f * pow(amplitude, 0.15f))); - - return gcadapter->RumblePlay(port, new_amp); - } - private: const u32 port; const s32 button; - GCAdapter::Adapter* gcadapter; + const GCAdapter::Adapter* gcadapter; }; class GCAxisButton final : public Input::ButtonDevice { @@ -299,4 +291,42 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() { return params; } +class GCVibration final : public Input::VibrationDevice { +public: + explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter) + : port(port_), gcadapter(adapter) {} + + u8 GetStatus() const override { + return gcadapter->RumblePlay(port, 0); + } + + bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { + const auto mean_amplitude = (amp_low + amp_high) * 0.5f; + const auto processed_amplitude = static_cast( + pow(mean_amplitude, 0.5f) * (3.0f - 2.0f * pow(mean_amplitude, 0.15f)) * 0x8); + + return gcadapter->RumblePlay(port, processed_amplitude); + } + +private: + const u32 port; + GCAdapter::Adapter* gcadapter; +}; + +/// An vibration device factory that creates vibration devices from GC Adapter +GCVibrationFactory::GCVibrationFactory(std::shared_ptr adapter_) + : adapter(std::move(adapter_)) {} + +/** + * Creates a vibration device from a joystick + * @param params contains parameters for creating the device: + * - "port": the nth gcpad on the adapter + */ +std::unique_ptr GCVibrationFactory::Create( + const Common::ParamPackage& params) { + const auto port = static_cast(params.Get("port", 0)); + + return std::make_unique(port, adapter.get()); +} + } // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h index 0527f328f2..d1271e3ead 100644 --- a/src/input_common/gcadapter/gc_poller.h +++ b/src/input_common/gcadapter/gc_poller.h @@ -64,4 +64,15 @@ private: bool polling = false; }; +/// A vibration device factory creates vibration devices from GC Adapter +class GCVibrationFactory final : public Input::Factory { +public: + explicit GCVibrationFactory(std::shared_ptr adapter_); + + std::unique_ptr Create(const Common::ParamPackage& params) override; + +private: + std::shared_ptr adapter; +}; + } // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index b438482cca..e59ad4ff52 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -28,6 +28,8 @@ struct InputSubsystem::Impl { Input::RegisterFactory("gcpad", gcbuttons); gcanalog = std::make_shared(gcadapter); Input::RegisterFactory("gcpad", gcanalog); + gcvibration = std::make_shared(gcadapter); + Input::RegisterFactory("gcpad", gcvibration); keyboard = std::make_shared(); Input::RegisterFactory("keyboard", keyboard); @@ -64,9 +66,11 @@ struct InputSubsystem::Impl { #endif Input::UnregisterFactory("gcpad"); Input::UnregisterFactory("gcpad"); + Input::UnregisterFactory("gcpad"); gcbuttons.reset(); gcanalog.reset(); + gcvibration.reset(); Input::UnregisterFactory("cemuhookudp"); Input::UnregisterFactory("cemuhookudp"); @@ -142,6 +146,7 @@ struct InputSubsystem::Impl { #endif std::shared_ptr gcbuttons; std::shared_ptr gcanalog; + std::shared_ptr gcvibration; std::shared_ptr udpmotion; std::shared_ptr udptouch; std::shared_ptr udp; diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 18fb2ac5e2..a2a83cdc9c 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -85,16 +85,17 @@ public: using std::chrono::milliseconds; using std::chrono::steady_clock; - // Prevent vibrations less than 10ms apart from each other. - if (duration_cast(steady_clock::now() - last_vibration) < milliseconds(10)) { + // Block non-zero vibrations less than 10ms apart from each other. + if ((amp_low != 0 || amp_high != 0) && + duration_cast(steady_clock::now() - last_vibration) < milliseconds(10)) { return false; - }; + } last_vibration = steady_clock::now(); - if (sdl_controller != nullptr) { + if (sdl_controller) { return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0; - } else if (sdl_joystick != nullptr) { + } else if (sdl_joystick) { return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, 0) == 0; } @@ -321,14 +322,6 @@ public: return joystick->GetButton(button); } - bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { - const u16 processed_amp_low = - static_cast(pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f)) * 0xFFFF); - const u16 processed_amp_high = - static_cast(pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f)) * 0xFFFF); - return joystick->RumblePlay(processed_amp_low, processed_amp_high); - } - private: std::shared_ptr joystick; int button; @@ -412,6 +405,32 @@ private: const float range; }; +class SDLVibration final : public Input::VibrationDevice { +public: + explicit SDLVibration(std::shared_ptr joystick_) + : joystick(std::move(joystick_)) {} + + u8 GetStatus() const override { + joystick->RumblePlay(1, 1); + return joystick->RumblePlay(0, 0); + } + + bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override { + const auto process_amplitude = [](f32 amplitude) { + return static_cast(std::pow(amplitude, 0.5f) * + (3.0f - 2.0f * std::pow(amplitude, 0.15f)) * 0xFFFF); + }; + + const auto processed_amp_low = process_amplitude(amp_low); + const auto processed_amp_high = process_amplitude(amp_high); + + return joystick->RumblePlay(processed_amp_low, processed_amp_high); + } + +private: + std::shared_ptr joystick; +}; + class SDLDirectionMotion final : public Input::MotionDevice { public: explicit SDLDirectionMotion(std::shared_ptr joystick_, int hat_, Uint8 direction_) @@ -554,7 +573,7 @@ class SDLAnalogFactory final : public Input::Factory { public: explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} /** - * Creates analog device from joystick axes + * Creates an analog device from joystick axes * @param params contains parameters for creating the device: * - "guid": the guid of the joystick to bind * - "port": the nth joystick of the same type @@ -580,6 +599,26 @@ private: SDLState& state; }; +/// An vibration device factory that creates vibration devices from SDL joystick +class SDLVibrationFactory final : public Input::Factory { +public: + explicit SDLVibrationFactory(SDLState& state_) : state(state_) {} + /** + * Creates a vibration device from a joystick + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type + */ + std::unique_ptr Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + return std::make_unique(state.GetSDLJoystickByGUID(guid, port)); + } + +private: + SDLState& state; +}; + /// A motion device factory that creates motion devices from SDL joystick class SDLMotionFactory final : public Input::Factory { public: @@ -646,11 +685,13 @@ private: SDLState::SDLState() { using namespace Input; - analog_factory = std::make_shared(*this); button_factory = std::make_shared(*this); + analog_factory = std::make_shared(*this); + vibration_factory = std::make_shared(*this); motion_factory = std::make_shared(*this); - RegisterFactory("sdl", analog_factory); RegisterFactory("sdl", button_factory); + RegisterFactory("sdl", analog_factory); + RegisterFactory("sdl", vibration_factory); RegisterFactory("sdl", motion_factory); // If the frontend is going to manage the event loop, then we don't start one here @@ -687,6 +728,7 @@ SDLState::~SDLState() { using namespace Input; UnregisterFactory("sdl"); UnregisterFactory("sdl"); + UnregisterFactory("sdl"); UnregisterFactory("sdl"); CloseJoysticks(); diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index b9bb4dc562..08044b00d6 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -22,6 +22,7 @@ namespace InputCommon::SDL { class SDLAnalogFactory; class SDLButtonFactory; class SDLMotionFactory; +class SDLVibrationFactory; class SDLJoystick; class SDLState : public State { @@ -72,6 +73,7 @@ private: std::shared_ptr button_factory; std::shared_ptr analog_factory; + std::shared_ptr vibration_factory; std::shared_ptr motion_factory; bool start_thread = false; diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp index b66c05856b..557e7a9a0c 100644 --- a/src/input_common/settings.cpp +++ b/src/input_common/settings.cpp @@ -14,13 +14,6 @@ const std::array mapping = {{ }}; } -namespace NativeMotion { -const std::array mapping = {{ - "motionleft", - "motionright", -}}; -} - namespace NativeAnalog { const std::array mapping = {{ "lstick", @@ -28,6 +21,20 @@ const std::array mapping = {{ }}; } +namespace NativeVibration { +const std::array mapping = {{ + "left_vibration_device", + "right_vibration_device", +}}; +} + +namespace NativeMotion { +const std::array mapping = {{ + "motionleft", + "motionright", +}}; +} + namespace NativeMouseButton { const std::array mapping = {{ "left", diff --git a/src/input_common/settings.h b/src/input_common/settings.h index 2763ed9915..75486554b6 100644 --- a/src/input_common/settings.h +++ b/src/input_common/settings.h @@ -66,17 +66,32 @@ constexpr int NUM_STICKS_HID = NumAnalogs; extern const std::array mapping; } // namespace NativeAnalog +namespace NativeVibration { +enum Values : int { + LeftVibrationDevice, + RightVibrationDevice, + + NumVibrations, +}; + +constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice; +constexpr int VIBRATION_HID_END = NumVibrations; +constexpr int NUM_VIBRATIONS_HID = NumVibrations; + +extern const std::array mapping; +}; // namespace NativeVibration + namespace NativeMotion { enum Values : int { - MOTIONLEFT, - MOTIONRIGHT, + MotionLeft, + MotionRight, NumMotions, }; -constexpr int MOTION_HID_BEGIN = MOTIONLEFT; +constexpr int MOTION_HID_BEGIN = MotionLeft; constexpr int MOTION_HID_END = NumMotions; -constexpr int NUM_MOTION_HID = NumMotions; +constexpr int NUM_MOTIONS_HID = NumMotions; extern const std::array mapping; } // namespace NativeMotion @@ -305,9 +320,11 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; } // namespace NativeKeyboard -using ButtonsRaw = std::array; using AnalogsRaw = std::array; -using MotionRaw = std::array; +using ButtonsRaw = std::array; +using MotionsRaw = std::array; +using VibrationsRaw = std::array; + using MouseButtonsRaw = std::array; using KeyboardKeysRaw = std::array; using KeyboardModsRaw = std::array; @@ -330,7 +347,8 @@ struct PlayerInput { ControllerType controller_type; ButtonsRaw buttons; AnalogsRaw analogs; - MotionRaw motions; + VibrationsRaw vibrations; + MotionsRaw motions; bool vibration_enabled; int vibration_strength; diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index c5e671309a..cdcd3d28d1 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -478,6 +478,8 @@ void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) return; } + ConfigureVibration::SetVibrationDevices(player_index); + // Player 1 and Handheld auto& handheld = Settings::values.players.GetValue()[8]; // If Handheld is selected, copy all the settings from Player 1 to Handheld. diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 7f66f29aa9..6fa842cd5e 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -344,21 +344,6 @@ void Config::ReadPlayerValue(std::size_t player_index) { } } - 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("%1").arg(player_prefix) + - QString::fromUtf8(Settings::NativeMotion::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_motions.empty()) { - player_motions = default_param; - } - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], @@ -375,6 +360,33 @@ void Config::ReadPlayerValue(std::size_t player_index) { player_analogs = default_param; } } + + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + auto& player_vibrations = player.vibrations[i]; + + player_vibrations = + qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeVibration::mapping[i]), + QString{}) + .toString() + .toStdString(); + } + + 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("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeMotion::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_motions.empty()) { + player_motions = default_param; + } + } } void Config::ReadDebugValues() { @@ -1014,13 +1026,6 @@ void Config::SavePlayerValue(std::size_t player_index) { QString::fromStdString(player.buttons[i]), QString::fromStdString(default_param)); } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); - WriteSetting(QStringLiteral("%1").arg(player_prefix) + - QString::fromStdString(Settings::NativeMotion::mapping[i]), - QString::fromStdString(player.motions[i]), - QString::fromStdString(default_param)); - } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], @@ -1030,6 +1035,18 @@ void Config::SavePlayerValue(std::size_t player_index) { QString::fromStdString(player.analogs[i]), QString::fromStdString(default_param)); } + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeVibration::mapping[i]), + QString::fromStdString(player.vibrations[i]), QString{}); + } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeMotion::mapping[i]), + QString::fromStdString(player.motions[i]), + QString::fromStdString(default_param)); + } } void Config::SaveDebugValues() { diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 460ff08a4f..3e785c2243 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -22,6 +22,7 @@ #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" #include "yuzu/util/limitable_input_dialog.h" @@ -39,6 +40,10 @@ namespace { void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, bool connected) { + auto& player = Settings::values.players.GetValue()[npad_index]; + player.controller_type = controller_type; + player.connected = connected; + Core::System& system{Core::System::GetInstance()}; if (!system.IsPoweredOn()) { return; @@ -565,6 +570,8 @@ void ConfigureInputPlayer::ApplyConfiguration() { static_cast(ui->comboControllerType->currentIndex()); player.connected = ui->groupConnectedController->isChecked(); + ConfigureVibration::SetVibrationDevices(player_index); + // Player 2-8 if (player_index != 0) { UpdateController(player.controller_type, player_index, player.connected); diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index 1c68f28f32..714db5b802 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -2,6 +2,12 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include + +#include + +#include "common/param_package.h" #include "core/settings.h" #include "ui_configure_vibration.h" #include "yuzu/configuration/configure_vibration.h" @@ -53,6 +59,80 @@ void ConfigureVibration::ApplyConfiguration() { ui->checkBoxAccurateVibration->isChecked()); } +void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { + using namespace Settings::NativeButton; + static constexpr std::array, 2> buttons{{ + {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons + {A, B, X, Y, R, ZR}, // Right Buttons + }}; + + auto& player = Settings::values.players.GetValue()[player_index]; + + for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) { + std::unordered_map params_count; + + for (const auto button_index : buttons[device_idx]) { + const auto& player_button = player.buttons[button_index]; + + if (params_count.find(player_button) != params_count.end()) { + ++params_count[player_button]; + continue; + } + + params_count.insert_or_assign(player_button, 1); + } + + const auto it = std::max_element( + params_count.begin(), params_count.end(), + [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; }); + + auto& vibration_param_str = player.vibrations[device_idx]; + vibration_param_str.clear(); + + if (it->first.empty()) { + continue; + } + + const auto param = Common::ParamPackage(it->first); + + const auto engine = param.Get("engine", ""); + const auto guid = param.Get("guid", ""); + const auto port = param.Get("port", ""); + + if (engine.empty() || engine == "keyboard") { + continue; + } + + vibration_param_str += fmt::format("engine:{}", engine); + + if (!port.empty()) { + vibration_param_str += fmt::format(",port:{}", port); + } + if (!guid.empty()) { + vibration_param_str += fmt::format(",guid:{}", guid); + } + } + + if (player.vibrations[0] != player.vibrations[1]) { + return; + } + + if (!player.vibrations[0].empty() && + player.controller_type != Settings::ControllerType::RightJoycon) { + player.vibrations[1].clear(); + } else if (!player.vibrations[1].empty() && + player.controller_type == Settings::ControllerType::RightJoycon) { + player.vibrations[0].clear(); + } +} + +void ConfigureVibration::SetAllVibrationDevices() { + // Set vibration devices for all player indices including handheld + for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) { + SetVibrationDevices(player_idx); + } +} + void ConfigureVibration::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h index 37bbc26536..07411a86f8 100644 --- a/src/yuzu/configuration/configure_vibration.h +++ b/src/yuzu/configuration/configure_vibration.h @@ -24,6 +24,9 @@ public: void ApplyConfiguration(); + static void SetVibrationDevices(std::size_t player_index); + static void SetAllVibrationDevices(); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 54a46827fc..76a5c32f4e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -18,6 +18,7 @@ #include "applets/web_browser.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" +#include "configuration/configure_vibration.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/controller.h" @@ -1096,6 +1097,8 @@ void GMainWindow::BootGame(const QString& filename) { Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig); } + ConfigureVibration::SetAllVibrationDevices(); + Settings::LogSettings(); if (UISettings::values.select_user_on_boot) {