From 9aa08313e30318f9c0ac7b529a7c4ddee20ef2f2 Mon Sep 17 00:00:00 2001 From: gidan80 Date: Tue, 20 Nov 2018 16:35:06 +0100 Subject: [PATCH] Qt: Add custom pad configs Add a custom pad config for every game. --- rpcs3/Emu/IdManager.h | 14 +- rpcs3/Emu/Io/PadHandler.cpp | 19 ++- rpcs3/Emu/Io/PadHandler.h | 29 +++- rpcs3/Emu/System.cpp | 59 +++++--- rpcs3/Emu/System.h | 6 +- rpcs3/Icons/combo_config_bordered.png | Bin 0 -> 54151 bytes rpcs3/Icons/controllers_2.png | Bin 0 -> 14384 bytes rpcs3/pad_thread.cpp | 9 +- rpcs3/pad_thread.h | 7 +- rpcs3/resources.qrc | 2 + rpcs3/rpcs3_app.cpp | 8 +- rpcs3/rpcs3qt/game_list_frame.cpp | 196 +++++++++++++++++++++++--- rpcs3/rpcs3qt/game_list_frame.h | 18 ++- rpcs3/rpcs3qt/main_window.cpp | 18 +-- rpcs3/rpcs3qt/main_window.ui | 8 +- rpcs3/rpcs3qt/pad_settings_dialog.cpp | 40 +++--- rpcs3/rpcs3qt/pad_settings_dialog.h | 4 +- 17 files changed, 332 insertions(+), 105 deletions(-) create mode 100644 rpcs3/Icons/combo_config_bordered.png create mode 100644 rpcs3/Icons/controllers_2.png diff --git a/rpcs3/Emu/IdManager.h b/rpcs3/Emu/IdManager.h index 7e536b9265..94d2d3ffd5 100644 --- a/rpcs3/Emu/IdManager.h +++ b/rpcs3/Emu/IdManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Utilities/types.h" #include "Utilities/mutex.h" @@ -633,8 +633,8 @@ public: } // Emplace the object returned by provider() and return it if no object exists - template - static auto import(F&& provider) -> decltype(static_cast>(provider())) + template + static auto import(F&& provider, Args&&... args) -> decltype(static_cast>(provider(std::forward(args)...))) { std::shared_ptr ptr; { @@ -644,7 +644,7 @@ public: if (!cur) { - ptr = provider(); + ptr = provider(std::forward(args)...); if (ptr) { @@ -662,8 +662,8 @@ public: } // Emplace the object return by provider() (old object will be removed if it exists) - template - static auto import_always(F&& provider) -> decltype(static_cast>(provider())) + template + static auto import_always(F&& provider, Args&&... args) -> decltype(static_cast>(provider(std::forward(args)...))) { std::shared_ptr ptr; std::shared_ptr old; @@ -672,7 +672,7 @@ public: auto& cur = g_vec[get_type()]; - ptr = provider(); + ptr = provider(std::forward(args)...); if (ptr) { diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index 151aa54884..b29d1195e5 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "PadHandler.h" +#include "pad_thread.h" cfg_input g_cfg_input; @@ -268,13 +269,25 @@ bool PadHandlerBase::has_led() return b_has_led; } -std::string PadHandlerBase::get_config_dir(pad_handler type) +std::string PadHandlerBase::get_config_dir(pad_handler type, const std::string& title_id) { + if (!title_id.empty()) + { + return Emu.GetCustomInputConfigDir(title_id) + fmt::format("%s", type) + "/"; + } return fs::get_config_dir() + "/InputConfigs/" + fmt::format("%s", type) + "/"; } -std::string PadHandlerBase::get_config_filename(int i) +std::string PadHandlerBase::get_config_filename(int i, const std::string& title_id) { + if (!title_id.empty() && fs::is_file(Emu.GetCustomInputConfigPath(title_id))) + { + const std::string path = Emu.GetCustomInputConfigDir(title_id) + g_cfg_input.player[i]->handler.to_string() + "/" + g_cfg_input.player[i]->profile.to_string() + ".yml"; + if (fs::is_file(path)) + { + return path; + } + } return fs::get_config_dir() + "/InputConfigs/" + g_cfg_input.player[i]->handler.to_string() + "/" + g_cfg_input.player[i]->profile.to_string() + ".yml"; } @@ -286,7 +299,7 @@ void PadHandlerBase::init_configs() { if (g_cfg_input.player[i]->handler == m_type) { - init_config(&m_pad_configs[index], get_config_filename(i)); + init_config(&m_pad_configs[index], get_config_filename(i, pad::g_title_id)); index++; } } diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index 414f2b142f..a1d57aa8ca 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -7,6 +7,7 @@ #include "Utilities/Config.h" #include "Utilities/types.h" #include "Emu/System.h" +#include "Emu/GameInfo.h" // TODO: HLE info (constants, structs, etc.) should not be available here @@ -268,7 +269,7 @@ struct cfg_player final : cfg::node struct cfg_input final : cfg::node { - const std::string cfg_name = fs::get_config_dir() + "/config_input.yml"; + std::string cfg_name = fs::get_config_dir() + "/config_input.yml"; cfg_player player1{ this, "Player 1 Input", pad_handler::keyboard }; cfg_player player2{ this, "Player 2 Input", pad_handler::null }; @@ -280,18 +281,32 @@ struct cfg_input final : cfg::node cfg_player *player[7]{ &player1, &player2, &player3, &player4, &player5, &player6, &player7 }; // Thanks gcc! - bool load() + bool load(const std::string& title_id = "") { - if (fs::file cfg_file{ cfg_name, fs::read }) + cfg_name = Emu.GetCustomInputConfigPath(title_id); + + if (!fs::is_file(cfg_name)) + { + cfg_name = fs::get_config_dir() + "/config_input.yml"; + } + + if (fs::file cfg_file{cfg_name, fs::read}) { return from_string(cfg_file.to_string()); } - return false; } - void save() + void save(const std::string& title_id = "") { + if (title_id.empty()) + { + cfg_name = fs::get_config_dir() + "/config_input.yml"; + } + else + { + cfg_name = Emu.GetCustomInputConfigPath(title_id); + } fs::file(cfg_name, fs::rewrite).write(to_string()); } }; @@ -460,8 +475,8 @@ public: bool has_deadzones(); bool has_led(); - static std::string get_config_dir(pad_handler type); - static std::string get_config_filename(int i); + static std::string get_config_dir(pad_handler type, const std::string& title_id = ""); + static std::string get_config_filename(int i, const std::string& title_id = ""); virtual bool Init() { return true; } PadHandlerBase(pad_handler type = pad_handler::null); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index c09e35f49b..638e243540 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -39,7 +39,6 @@ #include "Utilities/GDBDebugServer.h" -#include "Utilities/sysinfo.h" #include "Utilities/JIT.h" #if defined(_WIN32) || defined(HAVE_VULKAN) @@ -303,31 +302,31 @@ void Emulator::Init() if (g_cfg.vfs.init_dirs) { - fs::create_path(dev_hdd0); - fs::create_path(dev_hdd1); - fs::create_path(dev_usb); - fs::create_dir(dev_hdd0 + "game/"); - fs::create_dir(dev_hdd0 + "game/TEST12345/"); - fs::create_dir(dev_hdd0 + "game/TEST12345/USRDIR/"); - fs::create_dir(dev_hdd0 + "game/.locks/"); - fs::create_dir(dev_hdd0 + "home/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/exdata/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/savedata/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/trophy/"); - fs::write_file(dev_hdd0 + "home/" + m_usr + "/localusername", fs::create + fs::excl + fs::write, "User"s); - fs::create_dir(dev_hdd0 + "disc/"); - fs::create_dir(dev_hdd0 + "savedata/"); - fs::create_dir(dev_hdd0 + "savedata/vmc/"); - fs::create_dir(dev_hdd1 + "cache/"); - fs::create_dir(dev_hdd1 + "game/"); + fs::create_path(dev_hdd0); + fs::create_path(dev_hdd1); + fs::create_path(dev_usb); + fs::create_dir(dev_hdd0 + "game/"); + fs::create_dir(dev_hdd0 + "game/TEST12345/"); + fs::create_dir(dev_hdd0 + "game/TEST12345/USRDIR/"); + fs::create_dir(dev_hdd0 + "game/.locks/"); + fs::create_dir(dev_hdd0 + "home/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/exdata/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/savedata/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/trophy/"); + fs::write_file(dev_hdd0 + "home/" + m_usr + "/localusername", fs::create + fs::excl + fs::write, "User"s); + fs::create_dir(dev_hdd0 + "disc/"); + fs::create_dir(dev_hdd0 + "savedata/"); + fs::create_dir(dev_hdd0 + "savedata/vmc/"); + fs::create_dir(dev_hdd1 + "cache/"); + fs::create_dir(dev_hdd1 + "game/"); } fs::create_path(fs::get_cache_dir() + "shaderlog/"); fs::create_path(fs::get_config_dir() + "captures/"); #ifdef WITH_GDB_DEBUGGER - LOG_SUCCESS(GENERAL, "GDB debug server will be started and listening on %d upon emulator boot", (int) g_cfg.misc.gdb_server_port); + LOG_SUCCESS(GENERAL, "GDB debug server will be started and listening on %d upon emulator boot", (int)g_cfg.misc.gdb_server_port); #endif // Initialize patch engine @@ -506,7 +505,7 @@ bool Emulator::BootRsxCapture(const std::string& path) GetCallbacks().on_ready(); auto gsrender = fxm::import(Emu.GetCallbacks().get_gs_render); - auto padhandler = fxm::import(Emu.GetCallbacks().get_pad_handler); + auto padhandler = fxm::import(Emu.GetCallbacks().get_pad_handler, ""); if (gsrender.get() == nullptr || padhandler.get() == nullptr) return false; @@ -727,6 +726,22 @@ std::string Emulator::GetCustomConfigPath(const std::string& title_id, bool get_ return path; } +std::string Emulator::GetCustomInputConfigDir(const std::string& title_id) +{ + // Notice: the extra folder for each title id may be removed at a later stage + // Warning: make sure to change any function that removes this directory as well +#ifdef _WIN32 + return fs::get_config_dir() + "config/custom_input_configs/" + title_id + "/"; +#else + return fs::get_config_dir() + "custom_input_configs/" + title_id + "/"; +#endif +} + +std::string Emulator::GetCustomInputConfigPath(const std::string& title_id) +{ + return GetCustomInputConfigDir(title_id) + "/config_input_" + title_id + ".yml"; +} + void Emulator::SetForceBoot(bool force_boot) { m_force_boot = force_boot; @@ -1343,7 +1358,7 @@ void Emulator::Load(bool add_only, bool force_global_config) } fxm::import(Emu.GetCallbacks().get_gs_render); // TODO: must be created in appropriate sys_rsx syscall - fxm::import(Emu.GetCallbacks().get_pad_handler); + fxm::import(Emu.GetCallbacks().get_pad_handler, m_title_id); network_thread_init(); } else if (ppu_prx.open(elf_file) == elf_error::ok) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 99d1916014..40a5d2236d 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -189,12 +189,12 @@ struct EmuCallbacks std::function on_stop; std::function on_ready; std::function exit; - std::function reset_pads; + std::function reset_pads; std::function enable_pads; std::function handle_taskbar_progress; // (type, value) type: 0 for reset, 1 for increment, 2 for set_limit std::function()> get_kb_handler; std::function()> get_mouse_handler; - std::function()> get_pad_handler; + std::function(const std::string&)> get_pad_handler; std::function()> get_gs_frame; std::function()> get_gs_render; std::function()> get_audio; @@ -326,6 +326,8 @@ public: static std::string GetCustomConfigDir(); static std::string GetCustomConfigPath(const std::string& title_id, bool get_deprecated_path = false); + static std::string GetCustomInputConfigDir(const std::string& title_id); + static std::string GetCustomInputConfigPath(const std::string& title_id); void SetForceBoot(bool force_boot); diff --git a/rpcs3/Icons/combo_config_bordered.png b/rpcs3/Icons/combo_config_bordered.png new file mode 100644 index 0000000000000000000000000000000000000000..16a5e106ccd73cf40f4626db7b113dfdc91e0839 GIT binary patch literal 54151 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

u{kp4g%YmzE|Ady4s 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 literal 0 HcmV?d00001 diff --git a/rpcs3/Icons/controllers_2.png b/rpcs3/Icons/controllers_2.png new file mode 100644 index 0000000000000000000000000000000000000000..b26813ca35ae50bc7ebb513a4b40d7cb8b82f3ba GIT binary patch literal 14384 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 literal 0 HcmV?d00001 diff --git a/rpcs3/pad_thread.cpp b/rpcs3/pad_thread.cpp index cb4f81aab6..d3c22bc7a9 100644 --- a/rpcs3/pad_thread.cpp +++ b/rpcs3/pad_thread.cpp @@ -14,6 +14,7 @@ namespace pad { atomic_t g_current = nullptr; std::recursive_mutex g_pad_mutex; + std::string g_title_id; } struct pad_setting @@ -23,8 +24,9 @@ struct pad_setting u32 device_type; }; -pad_thread::pad_thread(void *_curthread, void *_curwindow) : curthread(_curthread), curwindow(_curwindow) +pad_thread::pad_thread(void *_curthread, void *_curwindow, const std::string& title_id) : curthread(_curthread), curwindow(_curwindow) { + pad::g_title_id = title_id; Init(); thread = std::make_shared(&pad_thread::ThreadFunc, this); @@ -65,7 +67,7 @@ void pad_thread::Init() handlers.clear(); - g_cfg_input.load(); + g_cfg_input.load(pad::g_title_id); std::shared_ptr keyptr; @@ -142,8 +144,9 @@ void pad_thread::SetRumble(const u32 pad, u8 largeMotor, bool smallMotor) } } -void pad_thread::Reset() +void pad_thread::Reset(const std::string& title_id) { + pad::g_title_id = title_id; reset = active.load(); } diff --git a/rpcs3/pad_thread.h b/rpcs3/pad_thread.h index e918366bd4..499555eaa0 100644 --- a/rpcs3/pad_thread.h +++ b/rpcs3/pad_thread.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -16,14 +16,14 @@ struct PadInfo class pad_thread { public: - pad_thread(void *_curthread, void *_curwindow); // void * instead of QThread * and QWindow * because of include in emucore + pad_thread(void* _curthread, void* _curwindow, const std::string& title_id = ""); // void * instead of QThread * and QWindow * because of include in emucore ~pad_thread(); PadInfo& GetInfo() { return m_info; } auto& GetPads() { return m_pads; } void SetRumble(const u32 pad, u8 largeMotor, bool smallMotor); void Init(); - void Reset(); + void Reset(const std::string& title_id = ""); void SetEnabled(bool enabled); void SetIntercepted(bool intercepted); @@ -50,6 +50,7 @@ namespace pad { extern atomic_t g_current; extern std::recursive_mutex g_pad_mutex; + extern std::string g_title_id; static inline class pad_thread* get_current_handler() { diff --git a/rpcs3/resources.qrc b/rpcs3/resources.qrc index 3574e9ded1..14a3a0d619 100644 --- a/rpcs3/resources.qrc +++ b/rpcs3/resources.qrc @@ -18,5 +18,7 @@ Icons/open.png Icons/custom_config.png Icons/custom_config_2.png + Icons/controllers_2.png + Icons/combo_config_bordered.png diff --git a/rpcs3/rpcs3_app.cpp b/rpcs3/rpcs3_app.cpp index 2a7abe4e5e..1c3445a07b 100644 --- a/rpcs3/rpcs3_app.cpp +++ b/rpcs3/rpcs3_app.cpp @@ -142,9 +142,9 @@ void rpcs3_app::InitializeCallbacks() RequestCallAfter(std::move(func)); }; - callbacks.reset_pads = [this]() + callbacks.reset_pads = [this](const std::string& title_id = "") { - pad::get_current_handler()->Reset(); + pad::get_current_handler()->Reset(title_id); }; callbacks.enable_pads = [this](bool enable) { @@ -183,9 +183,9 @@ void rpcs3_app::InitializeCallbacks() } }; - callbacks.get_pad_handler = [this]() -> std::shared_ptr + callbacks.get_pad_handler = [this](const std::string& title_id) -> std::shared_ptr { - return std::make_shared(thread(), gameWindow); + return std::make_shared(thread(), gameWindow, title_id); }; callbacks.get_gs_frame = [this]() -> std::unique_ptr diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 13aaa8a056..0fe493c2ac 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -1,6 +1,7 @@ #include "game_list_frame.h" #include "qt_utils.h" #include "settings_dialog.h" +#include "pad_settings_dialog.h" #include "table_item_delegate.h" #include "custom_table_widget_item.h" #include "input_dialog.h" @@ -114,6 +115,10 @@ game_list_frame::game_list_frame(std::shared_ptr guiSettings, std: QMenu* configure = new QMenu(this); configure->addActions(m_columnActs); configure->exec(mapToGlobal(pos)); + + QMenu* pad_configure = new QMenu(this); + pad_configure->addActions(m_columnActs); + pad_configure->exec(mapToGlobal(pos)); }); connect(m_xgrid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); @@ -471,12 +476,14 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter) } const auto compat = m_game_compat->GetCompatibility(game.serial); + const bool hasCustomConfig = fs::is_file(Emu.GetCustomConfigPath(game.serial)) || fs::is_file(Emu.GetCustomConfigPath(game.serial, true)); + const bool hasCustomPadConfig = fs::is_file(Emu.GetCustomInputConfigPath(game.serial)); const QColor color = getGridCompatibilityColor(compat.color); - const QPixmap pxmap = PaintedPixmap(img, hasCustomConfig, color); + const QPixmap pxmap = PaintedPixmap(img, hasCustomConfig, hasCustomPadConfig, color); - m_game_data.push_back(game_info(new gui_game_info{ game, compat, img, pxmap, hasCustomConfig })); + m_game_data.push_back(game_info(new gui_game_info{game, compat, img, pxmap, hasCustomConfig, hasCustomPadConfig})); } catch (const std::exception& e) { @@ -695,6 +702,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) } myMenu.addAction(boot); QAction* configure = myMenu.addAction(tr("&Configure")); + QAction* pad_configure = myMenu.addAction(tr("&Configure pads")); QAction* createPPUCache = myMenu.addAction(tr("&Create PPU Cache")); myMenu.addSeparator(); QAction* renameTitle = myMenu.addAction(tr("&Rename In Game List")); @@ -709,9 +717,20 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) QAction* remove_custom_config = remove_menu->addAction(tr("&Remove Custom Configuration")); connect(remove_custom_config, &QAction::triggered, [=]() { - if (RemoveCustomConfiguration(currGame.serial, true)) + if (RemoveCustomConfiguration(currGame.serial, gameinfo, true)) { - ShowCustomConfigIcon(item, false); + ShowCustomConfigIcon(item, config::type::emu); + } + }); + } + if (gameinfo->hasCustomPadConfig) + { + QAction* remove_custom_pad_config = remove_menu->addAction(tr("&Remove Custom Pad Configuration")); + connect(remove_custom_pad_config, &QAction::triggered, [=]() + { + if (RemoveCustomPadConfiguration(currGame.serial, gameinfo, true)) + { + ShowCustomConfigIcon(item, config::type::pad); } }); } @@ -775,7 +794,33 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) settings_dialog dlg(m_gui_settings, m_emu_settings, 0, this, &currGame); if (dlg.exec() == QDialog::Accepted && !gameinfo->hasCustomConfig) { - ShowCustomConfigIcon(item, true); + gameinfo->hasCustomConfig = true; + ShowCustomConfigIcon(item, config::type::emu); + } + }); + connect(pad_configure, &QAction::triggered, [=] + { + if (!Emu.IsStopped()) + { + Emu.GetCallbacks().enable_pads(false); + } + pad_settings_dialog dlg(this, &currGame); + connect(&dlg, &QDialog::finished, [this](int/* result*/) + { + if (Emu.IsStopped()) + { + return; + } + Emu.GetCallbacks().reset_pads(Emu.GetTitleID()); + }); + if (dlg.exec() == QDialog::Accepted && !gameinfo->hasCustomPadConfig) + { + gameinfo->hasCustomPadConfig = true; + ShowCustomConfigIcon(item, config::type::pad); + } + if (!Emu.IsStopped()) + { + Emu.GetCallbacks().enable_pads(true); } }); connect(hide_serial, &QAction::triggered, [=](bool checked) @@ -801,7 +846,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) } QMessageBox* mb = new QMessageBox(QMessageBox::Question, tr("Confirm %1 Removal").arg(qstr(currGame.category)), tr("Permanently remove %0 from drive?\nPath: %1").arg(name).arg(qstr(currGame.path)), QMessageBox::Yes | QMessageBox::No, this); - mb->setCheckBox(new QCheckBox(tr("Remove caches and custom config"))); + mb->setCheckBox(new QCheckBox(tr("Remove caches and custom configs"))); mb->deleteLater(); if (mb->exec() == QMessageBox::Yes) { @@ -811,6 +856,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) RemovePPUCache(cache_base_dir); RemoveSPUCache(cache_base_dir); RemoveCustomConfiguration(currGame.serial); + RemoveCustomPadConfiguration(currGame.serial); } fs::remove_all(currGame.path); m_game_data.erase(std::remove(m_game_data.begin(), m_game_data.end(), gameinfo), m_game_data.end()); @@ -933,7 +979,7 @@ bool game_list_frame::CreatePPUCache(const std::string& path) return success; } -bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, bool is_interactive) +bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, game_info game, bool is_interactive) { const std::string config_path_new = Emu.GetCustomConfigPath(title_id); const std::string config_path_old = Emu.GetCustomConfigPath(title_id, true); @@ -954,6 +1000,10 @@ bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, boo } if (fs::remove_file(path)) { + if (game) + { + game->hasCustomConfig = false; + } LOG_SUCCESS(GENERAL, "Removed configuration file: %s", path); } else @@ -971,6 +1021,41 @@ bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, boo return result; } +bool game_list_frame::RemoveCustomPadConfiguration(const std::string& title_id, game_info game, bool is_interactive) +{ + const std::string config_dir = Emu.GetCustomInputConfigDir(title_id); + + if (!fs::is_dir(config_dir)) + return true; + + if (is_interactive && QMessageBox::question(this, tr("Confirm Removal"), (!Emu.IsStopped() && Emu.GetTitleID() == title_id) + ? tr("Remove custom pad configuration?\nYour configuration will revert to the global pad settings.") + : tr("Remove custom pad configuration?")) != QMessageBox::Yes) + return true; + + if (QDir(qstr(config_dir)).removeRecursively()) + { + if (game) + { + game->hasCustomPadConfig = false; + } + if (!Emu.IsStopped() && Emu.GetTitleID() == title_id) + { + Emu.GetCallbacks().enable_pads(false); + Emu.GetCallbacks().reset_pads(title_id); + Emu.GetCallbacks().enable_pads(true); + } + LOG_NOTICE(GENERAL, "Removed pad configuration directory: %s", config_dir); + return true; + } + else if (is_interactive) + { + QMessageBox::warning(this, tr("Warning!"), tr("Failed to completely remove pad configuration directory!")); + LOG_FATAL(GENERAL, "Failed to completely remove pad configuration directory: %s\nError: %s", config_dir, fs::g_tls_error); + } + return false; +} + bool game_list_frame::RemoveShadersCache(const std::string& base_dir, bool is_interactive) { if (!fs::is_dir(base_dir)) @@ -1260,6 +1345,52 @@ void game_list_frame::BatchRemoveCustomConfigurations() Refresh(true); } +void game_list_frame::BatchRemoveCustomPadConfigurations() +{ + std::set serials; + for (const auto& game : m_game_data) + { + if (game->hasCustomPadConfig && !serials.count(game->info.serial)) + { + serials.emplace(game->info.serial); + } + } + const u32 total = serials.size(); + + if (total == 0) + { + QMessageBox::information(this, tr("Custom Pad Configuration Batch Removal"), tr("No files found"), QMessageBox::Ok); + return; + } + + progress_dialog* pdlg = new progress_dialog(tr("Removing all custom pad configurations"), tr("Cancel"), 0, total, this); + pdlg->setWindowTitle(tr("Custom Pad Configuration Batch Removal")); + pdlg->setAutoClose(false); + pdlg->setAutoReset(false); + pdlg->show(); + + u32 removed = 0; + for (const auto& serial : serials) + { + if (pdlg->wasCanceled()) + { + LOG_NOTICE(GENERAL, "Custom Pad Configuration Batch Removal was canceled. %d/%d custom pad configurations cleared", removed, total); + break; + } + QApplication::processEvents(); + + if (RemoveCustomPadConfiguration(serial)) + { + pdlg->SetValue(++removed); + } + } + + pdlg->setLabelText(tr("%0/%1 custom pad configurations cleared").arg(removed).arg(total)); + pdlg->setCancelButtonText(tr("OK")); + QApplication::beep(); + Refresh(true); +} + void game_list_frame::BatchRemoveShaderCaches() { std::set serials; @@ -1302,7 +1433,7 @@ void game_list_frame::BatchRemoveShaderCaches() QApplication::beep(); } -QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon, const QColor& compatibility_color) +QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon, bool paint_pad_config_icon, const QColor& compatibility_color) { const int device_pixel_ratio = devicePixelRatio(); const QSize original_size = img.size(); @@ -1318,11 +1449,26 @@ QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon painter.drawImage(QPoint(0, 0), img); } - if (paint_config_icon && !m_isListLayout) + if (!m_isListLayout && (paint_config_icon || paint_pad_config_icon)) { const int width = original_size.width() * 0.2; const QPoint origin = QPoint(original_size.width() - width, 0); - QImage custom_config_icon(":/Icons/custom_config_2.png"); + QString icon_path; + + if (paint_config_icon && paint_pad_config_icon) + { + icon_path = ":/Icons/combo_config_bordered.png"; + } + else if (paint_config_icon) + { + icon_path = ":/Icons/custom_config_2.png"; + } + else if (paint_pad_config_icon) + { + icon_path = ":/Icons/controllers_2.png"; + } + + QImage custom_config_icon(icon_path); custom_config_icon.setDevicePixelRatio(device_pixel_ratio); painter.drawImage(origin, custom_config_icon.scaled(QSize(width, width) * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation)); } @@ -1342,7 +1488,7 @@ QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon return QPixmap::fromImage(image.scaled(m_Icon_Size * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation)); } -void game_list_frame::ShowCustomConfigIcon(QTableWidgetItem* item, bool enabled) +void game_list_frame::ShowCustomConfigIcon(QTableWidgetItem* item, config::type type) { auto game = GetGameInfoFromItem(item); if (game == nullptr) @@ -1350,24 +1496,22 @@ void game_list_frame::ShowCustomConfigIcon(QTableWidgetItem* item, bool enabled) return; } - game->hasCustomConfig = enabled; - const QColor color = getGridCompatibilityColor(game->compat.color); - game->pxmap = PaintedPixmap(game->icon, enabled, color); - if (!m_isListLayout) { + const QColor color = getGridCompatibilityColor(game->compat.color); + game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); int r = m_xgrid->currentItem()->row(), c = m_xgrid->currentItem()->column(); m_xgrid->addItem(game->pxmap, qstr(game->info.name).simplified(), r, c); m_xgrid->item(r, c)->setData(gui::game_role, QVariant::fromValue(game)); } - else if (enabled) - { + else if (game->hasCustomConfig && game->hasCustomPadConfig) + m_gameList->item(item->row(), gui::column_name)->setIcon(QIcon(":/Icons/combo_config_bordered.png")); + else if (game->hasCustomConfig) m_gameList->item(item->row(), gui::column_name)->setIcon(QIcon(":/Icons/custom_config.png")); - } + else if (game->hasCustomPadConfig) + m_gameList->item(item->row(), gui::column_name)->setIcon(QIcon(":/Icons/controllers.png")); else - { m_gameList->setItem(item->row(), gui::column_name, new custom_table_widget_item(game->info.name)); - } } void game_list_frame::ResizeIcons(const int& sliderPos) @@ -1395,7 +1539,7 @@ void game_list_frame::RepaintIcons(const bool& fromSettings) for (auto& game : m_game_data) { QColor color = getGridCompatibilityColor(game->compat.color); - game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, color); + game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); } Refresh(); @@ -1560,10 +1704,18 @@ int game_list_frame::PopulateGameList() // Title custom_table_widget_item* title_item = new custom_table_widget_item(title); - if (game->hasCustomConfig) + if (game->hasCustomConfig && game->hasCustomPadConfig) + { + title_item->setIcon(QIcon(":/Icons/combo_config_bordered.png")); + } + else if (game->hasCustomConfig) { title_item->setIcon(QIcon(":/Icons/custom_config.png")); } + else if (game->hasCustomPadConfig) + { + title_item->setIcon(QIcon(":/Icons/controllers.png")); + } // Serial custom_table_widget_item* serial_item = new custom_table_widget_item(game->info.serial); diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 6d415eb5dc..f130f1130f 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -31,6 +31,15 @@ enum Category Others, }; +namespace config +{ + enum class type + { + emu, + pad + }; +} + namespace category // (see PARAM.SFO in psdevwiki.com) TODO: Disc Categories { // PS3 bootable @@ -171,6 +180,7 @@ struct gui_game_info QImage icon; QPixmap pxmap; bool hasCustomConfig; + bool hasCustomPadConfig; }; typedef std::shared_ptr game_info; @@ -215,6 +225,7 @@ public Q_SLOTS: void BatchRemovePPUCaches(); void BatchRemoveSPUCaches(); void BatchRemoveCustomConfigurations(); + void BatchRemoveCustomPadConfigurations(); void BatchRemoveShaderCaches(); void SetListMode(const bool& isList); void SetSearchText(const QString& text); @@ -234,9 +245,9 @@ protected: void resizeEvent(QResizeEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; private: - QPixmap PaintedPixmap(const QImage& img, bool paint_config_icon = false, const QColor& color = QColor()); + QPixmap PaintedPixmap(const QImage& img, bool paint_config_icon = false, bool paint_pad_config_icon = false, const QColor& color = QColor()); QColor getGridCompatibilityColor(const QString& string); - void ShowCustomConfigIcon(QTableWidgetItem* item, bool enabled); + void ShowCustomConfigIcon(QTableWidgetItem* item, config::type type); void PopulateGameGrid(int maxCols, const QSize& image_size, const QColor& image_color); bool IsEntryVisible(const game_info& game); void SortGameList(); @@ -244,7 +255,8 @@ private: int PopulateGameList(); bool SearchMatchesApp(const std::string& name, const std::string& serial); - bool RemoveCustomConfiguration(const std::string& base_dir, bool is_interactive = false); + bool RemoveCustomConfiguration(const std::string& title_id, game_info game = nullptr, bool is_interactive = false); + bool RemoveCustomPadConfiguration(const std::string& title_id, game_info game = nullptr, bool is_interactive = false); bool RemoveShadersCache(const std::string& base_dir, bool is_interactive = false); bool RemovePPUCache(const std::string& base_dir, bool is_interactive = false); bool RemoveSPUCache(const std::string& base_dir, bool is_interactive = false); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 6a1f628fc8..bbb57e8766 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1247,6 +1247,7 @@ void main_window::CreateConnects() connect(ui->batchRemoveSPUCachesAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveSPUCaches); connect(ui->batchRemoveShaderCachesAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveShaderCaches); connect(ui->batchRemoveCustomConfigurationsAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveCustomConfigurations); + connect(ui->batchRemoveCustomPadConfigurationsAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveCustomPadConfigurations); connect(ui->sysPauseAct, &QAction::triggered, this, &main_window::OnPlayOrPause); connect(ui->sysStopAct, &QAction::triggered, [=]() { Emu.Stop(); }); @@ -1282,20 +1283,19 @@ void main_window::CreateConnects() auto openPadSettings = [this] { - auto resetPadHandlers = [this](int/* result*/) - { - if (Emu.IsStopped()) - { - return; - } - Emu.GetCallbacks().reset_pads(); - }; if (!Emu.IsStopped()) { Emu.GetCallbacks().enable_pads(false); } pad_settings_dialog dlg(this); - connect(&dlg, &QDialog::finished, resetPadHandlers); + connect(&dlg, &QDialog::finished, [this](int/* result*/) + { + if (Emu.IsStopped()) + { + return; + } + Emu.GetCallbacks().reset_pads(Emu.GetTitleID()); + }); dlg.exec(); if (!Emu.IsStopped()) { diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index c3e6967705..4075cdba42 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -141,7 +141,7 @@ 0 0 1058 - 21 + 22 @@ -172,6 +172,7 @@ + @@ -995,6 +996,11 @@ Remove Shader Caches + + + Remove Custom Pad Configurations + + diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index edd7599914..df19f5092d 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -29,19 +29,13 @@ constexpr auto qstr = QString::fromStdString; inline bool CreateConfigFile(const QString& dir, const QString& name) { - QString input_dir = qstr(fs::get_config_dir()) + "/InputConfigs/"; - if (!QDir().mkdir(input_dir) && !QDir().exists(input_dir)) - { - LOG_ERROR(GENERAL, "Failed to create dir %s", sstr(input_dir)); - return false; - } - if (!QDir().mkdir(dir) && !QDir().exists(dir)) + if (!QDir().mkpath(dir)) { LOG_ERROR(GENERAL, "Failed to create dir %s", sstr(dir)); return false; } - QString filename = dir + name + ".yml"; + const QString filename = dir + name + ".yml"; QFile new_file(filename); if (!new_file.open(QIODevice::WriteOnly)) @@ -54,16 +48,25 @@ inline bool CreateConfigFile(const QString& dir, const QString& name) return true; }; -pad_settings_dialog::pad_settings_dialog(QWidget *parent) +pad_settings_dialog::pad_settings_dialog(QWidget *parent, const GameInfo *game) : QDialog(parent), ui(new Ui::pad_settings_dialog) { ui->setupUi(this); - setWindowTitle(tr("Gamepads Settings")); - // load input config g_cfg_input.from_default(); - g_cfg_input.load(); + + if (game) + { + m_title_id = game->serial; + g_cfg_input.load(game->serial); + setWindowTitle(tr("Gamepads Settings: [%0] %1").arg(qstr(game->serial)).arg(qstr(game->name).simplified())); + } + else + { + g_cfg_input.load(); + setWindowTitle(tr("Gamepads Settings")); + } // Create tab widget for 7 players m_tabs = new QTabWidget; @@ -153,7 +156,7 @@ pad_settings_dialog::pad_settings_dialog(QWidget *parent) QMessageBox::warning(this, tr("Error"), tr("Please choose a non-existing name")); continue; } - if (CreateConfigFile(qstr(PadHandlerBase::get_config_dir(g_cfg_input.player[i]->handler)), friendlyName)) + if (CreateConfigFile(qstr(PadHandlerBase::get_config_dir(g_cfg_input.player[i]->handler, m_title_id)), friendlyName)) { ui->chooseProfile->addItem(friendlyName); ui->chooseProfile->setCurrentText(friendlyName); @@ -839,6 +842,7 @@ void pad_settings_dialog::OnPadButtonClicked(int id) void pad_settings_dialog::OnTabChanged(int index) { + // TODO: Do not save yet! But keep all profile changes until the dialog was saved. // Save old profile SaveProfile(); @@ -898,7 +902,7 @@ void pad_settings_dialog::ChangeInputType() if (!g_cfg_input.player[player]->handler.from_string(handler)) { // Something went wrong - LOG_ERROR(GENERAL, "Failed to convert input string:%s", handler); + LOG_ERROR(GENERAL, "Failed to convert input string: %s", handler); return; } @@ -963,7 +967,7 @@ void pad_settings_dialog::ChangeInputType() } } - QString profile_dir = qstr(PadHandlerBase::get_config_dir(m_handler->m_type)); + QString profile_dir = qstr(PadHandlerBase::get_config_dir(m_handler->m_type, m_title_id)); QStringList profiles = gui::utils::get_dir_entries(QDir(profile_dir), QStringList() << "*.yml"); if (profiles.isEmpty()) @@ -1021,7 +1025,7 @@ void pad_settings_dialog::ChangeProfile() } // Change handler - const std::string cfg_name = PadHandlerBase::get_config_dir(m_handler->m_type) + m_profile + ".yml"; + const std::string cfg_name = PadHandlerBase::get_config_dir(m_handler->m_type, m_title_id) + m_profile + ".yml"; // Adjust to the different pad handlers switch (m_handler->m_type) @@ -1145,7 +1149,7 @@ void pad_settings_dialog::SaveExit() g_cfg_input.player[i]->profile.from_default(); } - g_cfg_input.save(); + g_cfg_input.save(m_title_id); QDialog::accept(); } diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index e14b00ad37..0541b7589d 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -8,6 +8,7 @@ #include #include "Emu/Io/PadHandler.h" +#include "Emu/GameInfo.h" namespace Ui { @@ -85,7 +86,7 @@ class pad_settings_dialog : public QDialog const QString Disconnected_suffix = tr(" (disconnected)"); public: - explicit pad_settings_dialog(QWidget *parent = nullptr); + explicit pad_settings_dialog(QWidget *parent = nullptr, const GameInfo *game = nullptr); ~pad_settings_dialog(); private Q_SLOTS: @@ -100,6 +101,7 @@ private Q_SLOTS: private: Ui::pad_settings_dialog *ui; + std::string m_title_id; // TabWidget QTabWidget* m_tabs;