diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 9988841e5e..61fbbcb1a0 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -104,7 +104,7 @@ main_window::~main_window() /* An init method is used so that RPCS3App can create the necessary connects before calling init (specifically the stylesheet connect). * Simplifies logic a bit. */ -bool main_window::Init(bool with_cli_boot) +bool main_window::Init([[maybe_unused]] bool with_cli_boot) { setAcceptDrops(true); diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 044b3e5dc0..15e95f0c20 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -22,6 +22,8 @@ #include "Input/product_info.h" #include "Input/keyboard_pad_handler.h" +#include + LOG_CHANNEL(cfg_log, "CFG"); inline std::string sstr(const QString& _in) { return _in.toStdString(); } @@ -206,6 +208,14 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti pad_settings_dialog::~pad_settings_dialog() { + if (m_input_thread) + { + m_input_thread_state = input_thread_state::pausing; + auto& thread = *m_input_thread; + thread = thread_state::aborting; + thread(); + } + m_gui_settings->SetValue(gui::pads_geometry, saveGeometry()); if (!Emu.IsStopped()) @@ -440,17 +450,27 @@ void pad_settings_dialog::InitButtons() } }; - // Use timer to get button input + // Use timer to display button input connect(&m_timer_input, &QTimer::timeout, this, [this, callback, fail_callback]() { - const std::vector buttons = + input_callback_data data; { - m_cfg_entries[button_ids::id_pad_l2].key, m_cfg_entries[button_ids::id_pad_r2].key, m_cfg_entries[button_ids::id_pad_lstick_left].key, - m_cfg_entries[button_ids::id_pad_lstick_right].key, m_cfg_entries[button_ids::id_pad_lstick_down].key, m_cfg_entries[button_ids::id_pad_lstick_up].key, - m_cfg_entries[button_ids::id_pad_rstick_left].key, m_cfg_entries[button_ids::id_pad_rstick_right].key, m_cfg_entries[button_ids::id_pad_rstick_down].key, - m_cfg_entries[button_ids::id_pad_rstick_up].key - }; - m_handler->get_next_button_press(m_device_name, callback, fail_callback, false, buttons); + std::lock_guard lock(m_input_mutex); + data = m_input_callback_data; + m_input_callback_data.has_new_data = false; + } + + if (data.has_new_data) + { + if (data.success) + { + callback(data.val, std::move(data.name), std::move(data.pad_name), data.battery_level, std::move(data.preview_values)); + } + else + { + fail_callback(data.pad_name); + } + } }); // Use timer to refresh pad connection status @@ -463,18 +483,73 @@ void pad_settings_dialog::InitButtons() cfg_log.fatal("Cannot convert itemData for index %d and itemText %s", i, sstr(ui->chooseDevice->itemText(i))); continue; } + const pad_device_info info = ui->chooseDevice->itemData(i).value(); + + std::lock_guard lock(m_handler_mutex); + m_handler->get_next_button_press(info.name, [this](u16, std::string, std::string pad_name, u32, pad_preview_values) { SwitchPadInfo(pad_name, true); }, [this](std::string pad_name) { SwitchPadInfo(pad_name, false); }, false); } }); + + // Use thread to get button input + m_input_thread = std::make_unique>>("Pad Settings Thread", [this]() + { + while (thread_ctrl::state() != thread_state::aborting) + { + thread_ctrl::wait_for(1000); + + if (m_input_thread_state != input_thread_state::active) + { + if (m_input_thread_state == input_thread_state::pausing) + { + m_input_thread_state = input_thread_state::paused; + } + + continue; + } + + std::lock_guard lock(m_handler_mutex); + + const std::vector buttons = + { + m_cfg_entries[button_ids::id_pad_l2].key, m_cfg_entries[button_ids::id_pad_r2].key, m_cfg_entries[button_ids::id_pad_lstick_left].key, + m_cfg_entries[button_ids::id_pad_lstick_right].key, m_cfg_entries[button_ids::id_pad_lstick_down].key, m_cfg_entries[button_ids::id_pad_lstick_up].key, + m_cfg_entries[button_ids::id_pad_rstick_left].key, m_cfg_entries[button_ids::id_pad_rstick_right].key, m_cfg_entries[button_ids::id_pad_rstick_down].key, + m_cfg_entries[button_ids::id_pad_rstick_up].key + }; + m_handler->get_next_button_press(m_device_name, + [this](u16 val, std::string name, std::string pad_name, u32 battery_level, pad_preview_values preview_values) + { + std::lock_guard lock(m_input_mutex); + m_input_callback_data.val = val; + m_input_callback_data.name = std::move(name); + m_input_callback_data.pad_name = std::move(pad_name); + m_input_callback_data.battery_level = battery_level; + m_input_callback_data.preview_values = std::move(preview_values); + m_input_callback_data.has_new_data = true; + m_input_callback_data.success = true; + }, + [this](std::string pad_name) + { + std::lock_guard lock(m_input_mutex); + m_input_callback_data.pad_name = std::move(pad_name); + m_input_callback_data.has_new_data = true; + m_input_callback_data.success = false; + }, + false, buttons); + } + }); } void pad_settings_dialog::SetPadData(u32 large_motor, u32 small_motor, bool led_battery_indicator) { ensure(m_handler); const auto& cfg = GetPlayerConfig(); + + std::lock_guard lock(m_handler_mutex); m_handler->SetPadData(m_device_name, GetPlayerIndex(), large_motor, small_motor, cfg.colorR, cfg.colorG, cfg.colorB, led_battery_indicator, cfg.led_battery_indicator_brightness); } @@ -1082,8 +1157,11 @@ void pad_settings_dialog::OnPadButtonClicked(int id) UpdateLabels(true); return; case button_ids::id_blacklist: + { + std::lock_guard lock(m_handler_mutex); m_handler->get_next_button_press(m_device_name, nullptr, nullptr, true); return; + } default: break; } @@ -1131,6 +1209,9 @@ void pad_settings_dialog::OnTabChanged(int index) void pad_settings_dialog::ChangeHandler() { + // Pause input thread. This means we don't have to lock the handler mutex here. + pause_input_thread(); + bool force_enable = false; // enable configs even with disconnected devices const u32 player = GetPlayerIndex(); const bool is_ldd_pad = GetIsLddPad(player); @@ -1351,6 +1432,7 @@ void pad_settings_dialog::ChangeHandler() // Re-enable input timer if (ui->chooseDevice->isEnabled() && ui->chooseDevice->currentIndex() >= 0) { + start_input_thread(); m_timer_input.start(1); m_timer_pad_refresh.start(1000); } @@ -1769,3 +1851,21 @@ void pad_settings_dialog::SubscribeTooltips() SubscribeTooltip(ui->gb_mouse_dz, tooltips.gamepad_settings.mouse_deadzones); SubscribeTooltip(ui->gb_mouse_movement, tooltips.gamepad_settings.mouse_movement); } + +void pad_settings_dialog::start_input_thread() +{ + m_input_thread_state = input_thread_state::active; +} + +void pad_settings_dialog::pause_input_thread() +{ + if (m_input_thread) + { + m_input_thread_state = input_thread_state::pausing; + + while (m_input_thread_state != input_thread_state::paused) + { + std::this_thread::sleep_for(1ms); + } + } +} diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index 6bcee815a8..da6404fc2e 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -5,8 +5,11 @@ #include #include +#include + #include "Emu/Io/pad_config.h" #include "Emu/GameInfo.h" +#include "Utilities/Thread.h" class gui_settings; class PadHandlerBase; @@ -145,6 +148,7 @@ private: // Pad Handlers std::shared_ptr m_handler; + std::mutex m_handler_mutex; std::string m_device_name; std::string m_profile; QTimer m_timer_pad_refresh; @@ -158,8 +162,27 @@ private: // Mouse Move QPoint m_last_pos; - // Input timer. Its Callback handles the input + // Input timer. Updates the GUI with input values QTimer m_timer_input; + std::mutex m_input_mutex; + struct input_callback_data + { + bool success = false; + bool has_new_data = false; + u16 val = 0; + std::string name; + std::string pad_name; + u32 battery_level = 0; + std::array preview_values{}; + } m_input_callback_data; + + // Input thread. Its Callback handles the input + std::unique_ptr>> m_input_thread; + enum class input_thread_state { paused, pausing, active }; + atomic_t m_input_thread_state{input_thread_state::paused}; + + void start_input_thread(); + void pause_input_thread(); void SaveExit(); void CancelExit();