From cfb4317706b74a1d3561817e0ea5f78714446c92 Mon Sep 17 00:00:00 2001 From: Fabian Thomys Date: Tue, 1 Jul 2025 08:07:48 +0200 Subject: [PATCH] Qt: Implement option for organizing screenshots in folders by game name --- pcsx2-qt/MainWindow.cpp | 37 ++++++++++++++++++++++ pcsx2-qt/MainWindow.h | 1 + pcsx2-qt/Settings/FolderSettingsWidget.cpp | 8 +++++ pcsx2-qt/Settings/FolderSettingsWidget.ui | 19 +++++++---- pcsx2/Config.h | 3 +- pcsx2/GS/Renderers/Common/GSRenderer.cpp | 25 ++++++++++++++- pcsx2/Pcsx2Config.cpp | 1 + 7 files changed, 86 insertions(+), 8 deletions(-) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 2069b02b77..38a1f345af 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -34,10 +34,12 @@ #include "pcsx2/Recording/InputRecording.h" #include "pcsx2/Recording/InputRecordingControls.h" #include "pcsx2/SIO/Sio.h" +#include "pcsx2/GS/GSExtra.h" #include "common/Assertions.h" #include "common/CocoaTools.h" #include "common/FileSystem.h" +#include "common/Path.h" #include #include @@ -1454,6 +1456,8 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) connect(menu.addAction(tr("Check Wiki Page")), &QAction::triggered, [this, entry]() { goToWikiPage(entry); }); } + action = menu.addAction(tr("Open Screenshots Folder")); + connect(action, &QAction::triggered, [this, entry]() { openScreenshotsFolderForGame(entry); }); menu.addSeparator(); if (!s_vm_valid) @@ -2905,6 +2909,39 @@ void MainWindow::goToWikiPage(const GameList::Entry* entry) QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", entry->serial).c_str()); } +void MainWindow::openScreenshotsFolderForGame(const GameList::Entry* entry) +{ + if (!entry || entry->title.empty()) + return; + + // if disabled open the snapshots folder + if (!EmuConfig.GS.OrganizeScreenshotsByGame) + { + QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::Snapshots))); + return; + } + + std::string game_name = entry->title; + Path::SanitizeFileName(&game_name); + if (game_name.length() > 219) + { + game_name.resize(219); + } + const std::string game_dir = Path::Combine(EmuFolders::Snapshots, game_name); + + if (!FileSystem::DirectoryExists(game_dir.c_str())) + { + if (!FileSystem::CreateDirectoryPath(game_dir.c_str(), false)) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to create screenshots directory '%1'.").arg(QString::fromStdString(game_dir))); + return; + } + } + + const QFileInfo fi(QString::fromStdString(game_dir)); + QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absoluteFilePath())); +} + std::optional MainWindow::promptForResumeState(const QString& save_state_path) { if (save_state_path.isEmpty()) diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 0c05e2ec5f..5c210f20be 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -274,6 +274,7 @@ private: void setGameListEntryCoverImage(const GameList::Entry* entry); void clearGameListEntryPlayTime(const GameList::Entry* entry); void goToWikiPage(const GameList::Entry* entry); + void openScreenshotsFolderForGame(const GameList::Entry* entry); std::optional promptForResumeState(const QString& save_state_path); void loadSaveStateSlot(s32 slot, bool load_backup = false); diff --git a/pcsx2-qt/Settings/FolderSettingsWidget.cpp b/pcsx2-qt/Settings/FolderSettingsWidget.cpp index c4ddfcf12d..2d8107e009 100644 --- a/pcsx2-qt/Settings/FolderSettingsWidget.cpp +++ b/pcsx2-qt/Settings/FolderSettingsWidget.cpp @@ -2,8 +2,10 @@ // SPDX-License-Identifier: GPL-3.0+ #include +#include #include "FolderSettingsWidget.h" +#include "pcsx2/GS/GS.h" #include "SettingWidgetBinder.h" #include "SettingsWindow.h" @@ -18,8 +20,14 @@ FolderSettingsWidget::FolderSettingsWidget(SettingsWindow* dialog, QWidget* pare SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.cheats, m_ui.cheatsBrowse, m_ui.cheatsOpen, m_ui.cheatsReset, "Folders", "Cheats", Path::Combine(EmuFolders::DataRoot, "cheats")); SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.covers, m_ui.coversBrowse, m_ui.coversOpen, m_ui.coversReset, "Folders", "Covers", Path::Combine(EmuFolders::DataRoot, "covers")); SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.snapshots, m_ui.snapshotsBrowse, m_ui.snapshotsOpen, m_ui.snapshotsReset, "Folders", "Snapshots", Path::Combine(EmuFolders::DataRoot, "snaps")); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.organizeScreenshotsByGame, "EmuCore/GS", "OrganizeScreenshotsByGame", false); + connect(m_ui.organizeScreenshotsByGame, &QCheckBox::checkStateChanged, this, [](int state) { + GSConfig.OrganizeScreenshotsByGame = (state == Qt::Checked); + }); SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset, "Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "sstates")); SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.videoDumpingDirectory, m_ui.videoDumpingDirectoryBrowse, m_ui.videoDumpingDirectoryOpen, m_ui.videoDumpingDirectoryReset, "Folders", "Videos", Path::Combine(EmuFolders::DataRoot, "videos")); + dialog->registerWidgetHelp(m_ui.organizeScreenshotsByGame, tr("Organize Screenshots by Game"), tr("Unchecked"), + tr("When enabled, screenshots will be saved in a folder with the game's name, instead of all being saved in the Snapshots folder")); } FolderSettingsWidget::~FolderSettingsWidget() = default; diff --git a/pcsx2-qt/Settings/FolderSettingsWidget.ui b/pcsx2-qt/Settings/FolderSettingsWidget.ui index c4d1ec60cb..f1617f7b9e 100644 --- a/pcsx2-qt/Settings/FolderSettingsWidget.ui +++ b/pcsx2-qt/Settings/FolderSettingsWidget.ui @@ -175,12 +175,19 @@ - - Used for screenshots and saving GS dumps. - - - - + + Used for screenshots and saving GS dumps. + + + + + + + Save Screenshots in Game-Specific Folders + + + + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index ab06a06ee0..1e939ce31a 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -781,7 +781,8 @@ struct Pcsx2Config EnableVideoCaptureParameters : 1, VideoCaptureAutoResolution : 1, EnableAudioCapture : 1, - EnableAudioCaptureParameters : 1; + EnableAudioCaptureParameters : 1, + OrganizeScreenshotsByGame : 1; }; }; diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index a71f256df8..55bcf38889 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -876,7 +876,30 @@ static std::string GSGetBaseFilename() std::string GSGetBaseSnapshotFilename() { // prepend snapshots directory - return Path::Combine(EmuFolders::Snapshots, GSGetBaseFilename()); + std::string base_path = EmuFolders::Snapshots; + + // If organize by game is enabled, create a game-specific folder + if (GSConfig.OrganizeScreenshotsByGame) + { + std::string game_name = VMManager::GetTitle(true); + if (!game_name.empty()) + { + Path::SanitizeFileName(&game_name); + if (game_name.length() > 219) + { + game_name.resize(219); + } + const std::string game_dir = Path::Combine(base_path, game_name); + if (!FileSystem::DirectoryExists(game_dir.c_str())) + { + FileSystem::CreateDirectoryPath(game_dir.c_str(), false); + } + + base_path = game_dir; + } + } + + return Path::Combine(base_path, GSGetBaseFilename()); } std::string GSGetBaseVideoFilename() diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index d311515843..296c62da25 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -920,6 +920,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapIntEnumEx(ScreenshotSize, "ScreenshotSize"); SettingsWrapIntEnumEx(ScreenshotFormat, "ScreenshotFormat"); SettingsWrapEntry(ScreenshotQuality); + SettingsWrapBitBool(OrganizeScreenshotsByGame); SettingsWrapEntry(StretchY); SettingsWrapEntryEx(Crop[0], "CropLeft"); SettingsWrapEntryEx(Crop[1], "CropTop");