diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index dad4bc2681..1b0db770a9 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -65,6 +65,7 @@ #include "DolphinQt/QtUtils/DoubleClickEventFilter.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/NonAutodismissibleMenu.h" +#include "DolphinQt/QtUtils/QtUtils.h" #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" #include "DolphinQt/WiiUpdate.h" @@ -722,19 +723,7 @@ void GameList::OpenContainingFolder() if (!game) return; - // Remove everything after the last separator in the game's path, resulting in the parent - // directory path with a trailing separator. Keeping the trailing separator prevents Windows from - // mistakenly opening a .bat or .exe file in the grandparent folder when that file has the same - // base name as the folder (See https://bugs.dolphin-emu.org/issues/12411). - std::string parent_directory_path; - SplitPath(game->GetFilePath(), &parent_directory_path, nullptr, nullptr); - if (parent_directory_path.empty()) - { - return; - } - - const QUrl url = QUrl::fromLocalFile(QString::fromStdString(parent_directory_path)); - QDesktopServices::openUrl(url); + QtUtils::ShowFileInFolder(game->GetFilePath()); } void GameList::OpenWiiSaveFolder() @@ -766,45 +755,44 @@ void GameList::OpenGCSaveFolder() for (Slot slot : ExpansionInterface::MEMCARD_SLOTS) { - QUrl url; const ExpansionInterface::EXIDeviceType current_exi_device = Config::Get(Config::GetInfoForEXIDevice(slot)); switch (current_exi_device) { case ExpansionInterface::EXIDeviceType::MemoryCardFolder: { - std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot)); - QDir dir(QString::fromStdString(override_path.empty() ? - Config::GetGCIFolderPath(slot, game->GetRegion()) : - override_path)); + namespace fs = std::filesystem; - if (!dir.entryList({QStringLiteral("%1-%2-*.gci") - .arg(QString::fromStdString(game->GetMakerID())) - .arg(QString::fromStdString(game->GetGameID().substr(0, 4)))}) - .empty()) + std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot)); + const auto dir = + override_path.empty() ? Config::GetGCIFolderPath(slot, game->GetRegion()) : override_path; + + const auto gci_filename_prefix = + fs::path{fmt::format("{}-{}-", game->GetMakerID(), game->GetGameID().substr(0, 4))} + .native(); + const auto gci_extension = fs::path{".gci"}.native(); + + for (const auto& entry : fs::directory_iterator(dir)) { - url = QUrl::fromLocalFile(dir.absolutePath()); + if (entry.path().filename().native().starts_with(gci_filename_prefix) && + entry.path().extension() == gci_extension) + { + QtUtils::ShowFileInFolder(entry.path().generic_string()); + found = true; + break; + } } break; } case ExpansionInterface::EXIDeviceType::MemoryCard: { - const std::string memcard_path = Config::GetMemcardPath(slot, game->GetRegion()); - - std::string memcard_dir; - - SplitPath(memcard_path, &memcard_dir, nullptr, nullptr); - url = QUrl::fromLocalFile(QString::fromStdString(memcard_dir)); + QtUtils::ShowFileInFolder(Config::GetMemcardPath(slot, game->GetRegion())); + found = true; break; } default: break; } - - found |= !url.isEmpty(); - - if (!url.isEmpty()) - QDesktopServices::openUrl(url); } if (!found) diff --git a/Source/Core/DolphinQt/QtUtils/QtUtils.cpp b/Source/Core/DolphinQt/QtUtils/QtUtils.cpp index 6c64acfeb7..1283b17687 100644 --- a/Source/Core/DolphinQt/QtUtils/QtUtils.cpp +++ b/Source/Core/DolphinQt/QtUtils/QtUtils.cpp @@ -4,10 +4,43 @@ #include "DolphinQt/QtUtils/QtUtils.h" #include +#include +#include #include #include +#include #include +#if defined(QT_DBUS_LIB) +#include +#include +#endif + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +namespace +{ + +void ShowFolderOfFile(std::string_view file_path) +{ + // Remove everything after the last separator in the game's path, resulting in the parent + // directory path with a trailing separator. Keeping the trailing separator prevents Windows from + // mistakenly opening a .bat or .exe file in the grandparent folder when that file has the same + // base name as the folder (See https://bugs.dolphin-emu.org/issues/12411). + std::string parent_directory_path; + SplitPath(file_path, &parent_directory_path, nullptr, nullptr); + if (parent_directory_path.empty()) + { + return; + } + + const QUrl url = QUrl::fromLocalFile(QString::fromStdString(parent_directory_path)); + QDesktopServices::openUrl(url); +} + +} // namespace + namespace QtUtils { @@ -68,4 +101,54 @@ void CenterOnParentWindow(QWidget* const widget) widget->setGeometry(QRect(pos, size)); } +void ShowFileInFolder(std::string_view file_path) +{ +#if defined(QT_DBUS_LIB) + QDBusInterface dbus{QString::fromLatin1("org.freedesktop.DBus"), + QString::fromLatin1("/org/freedesktop/DBus"), + QString::fromLatin1("org.freedesktop.DBus")}; + + dbus.call(QString::fromLatin1("StartServiceByName"), + QString::fromLatin1("org.freedesktop.FileManager1"), 0u); + + QDBusInterface iface{QString::fromLatin1("org.freedesktop.FileManager1"), + QString::fromLatin1("/org/freedesktop/FileManager1"), + QString::fromLatin1("org.freedesktop.FileManager1")}; + if (iface.isValid()) + { + QStringList urls; + urls << QUrl::fromLocalFile(QString::fromUtf8(file_path)).toString(); + + const QDBusReply reply = iface.call(QString::fromLatin1("ShowItems"), urls, QString{}); + if (reply.isValid()) + return; + + const auto& err = reply.error(); + WARN_LOG_FMT(COMMON, "DBus call failed: {} - {}", err.name().toStdString(), + err.message().toStdString()); + } + else + { + WARN_LOG_FMT(COMMON, "Invalid DBus interface: {}", iface.lastError().message().toStdString()); + } + +#elif defined(_WIN32) + if (QProcess{}.startDetached(QString::fromLatin1("explorer.exe"), + {QString::fromLatin1("/select"), QString::fromLatin1(","), + QDir::toNativeSeparators(QString::fromUtf8(file_path))})) + { + return; + } +#elif defined(__APPLE__) + if (QProcess{}.startDetached(QString::fromLatin1("open"), + {QString::fromLatin1("-R"), QString::fromUtf8(file_path)})) + { + return; + } +#endif + + // Fall back on failure or unsupported platforms. + ShowFolderOfFile(file_path); +} + } // namespace QtUtils diff --git a/Source/Core/DolphinQt/QtUtils/QtUtils.h b/Source/Core/DolphinQt/QtUtils/QtUtils.h index 812808cf9f..f6ba8036ca 100644 --- a/Source/Core/DolphinQt/QtUtils/QtUtils.h +++ b/Source/Core/DolphinQt/QtUtils/QtUtils.h @@ -3,6 +3,8 @@ #pragma once +#include + #include class QDateTimeEdit; @@ -37,4 +39,7 @@ public: QSize sizeHint() const override { return Widget::minimumSizeHint(); } }; +// Opens the folder of the given file (and also selects the file on supported platforms). +void ShowFileInFolder(std::string_view file_path); + } // namespace QtUtils