diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index 09ddb5959d..c6ddb0c2bd 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -34,6 +34,7 @@ #include "pcsx2/Host.h" #include "pcsx2/INISettingsInterface.h" #include "pcsx2/ImGui/FullscreenUI.h" +#include "pcsx2/ImGui/ImGuiFullscreen.h" #include "pcsx2/ImGui/ImGuiManager.h" #include "pcsx2/Input/InputManager.h" #include "pcsx2/MTGS.h" @@ -272,7 +273,7 @@ void Host::BeginPresentFrame() const bool idle_frame = s_total_frames && (last_draws == s_total_internal_draws && last_uploads == s_total_uploads); - if(!idle_frame) + if (!idle_frame) s_total_drawn_frames++; s_total_frames++; @@ -399,6 +400,17 @@ void Host::OnCreateMemoryCardOpenRequested() // noop } +bool Host::ShouldPreferHostFileSelector() +{ + return false; +} + +void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback, + FileSelectorFilters filters, std::string_view initial_directory) +{ + callback(std::string()); +} + std::optional InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str) { return std::nullopt; @@ -538,7 +550,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks", true); - if(str.find("af") != std::string::npos) + if (str.find("af") != std::string::npos) s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_AutoFlush", true); if (str.find("cpufb") != std::string::npos) s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_CPU_FB_Conversion", true); @@ -834,7 +846,7 @@ int wmain(int argc, wchar_t** argv) u8_args.reserve(static_cast(argc)); for (int i = 0; i < argc; i++) u8_args.push_back(StringUtil::WideStringToUTF8String(argv[i])); - + std::vector u8_argptrs; u8_argptrs.reserve(u8_args.size()); for (int i = 0; i < argc; i++) diff --git a/pcsx2-gsrunner/pcsx2-gsrunner.vcxproj b/pcsx2-gsrunner/pcsx2-gsrunner.vcxproj index 1b394cf409..aeecae8547 100644 --- a/pcsx2-gsrunner/pcsx2-gsrunner.vcxproj +++ b/pcsx2-gsrunner/pcsx2-gsrunner.vcxproj @@ -36,6 +36,7 @@ %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\fmt\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\imgui\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include; %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\simpleini\include %(AdditionalIncludeDirectories);$(SolutionDir)pcsx2 diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index aabf05e072..a9089abd65 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -24,6 +24,7 @@ #include "pcsx2/Host.h" #include "pcsx2/INISettingsInterface.h" #include "pcsx2/ImGui/FullscreenUI.h" +#include "pcsx2/ImGui/ImGuiFullscreen.h" #include "pcsx2/ImGui/ImGuiManager.h" #include "pcsx2/ImGui/ImGuiOverlays.h" #include "pcsx2/Input/InputManager.h" @@ -46,6 +47,7 @@ #include #include #include +#include #include #include @@ -1152,6 +1154,59 @@ void Host::OnCreateMemoryCardOpenRequested() emit g_emu_thread->onCreateMemoryCardOpenRequested(); } +bool Host::ShouldPreferHostFileSelector() +{ +#ifdef __linux__ + // If running inside a flatpak, we want to use native selectors/portals. + return (std::getenv("container") != nullptr); +#else + return false; +#endif +} + +void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback, + FileSelectorFilters filters, std::string_view initial_directory) +{ + const bool from_cpu_thread = g_emu_thread->isOnEmuThread(); + + QString filters_str; + if (!filters.empty()) + { + filters_str.append(QStringLiteral("All File Types (%1)") + .arg(QString::fromStdString(StringUtil::JoinString(filters.begin(), filters.end(), " ")))); + for (const std::string& filter : filters) + { + filters_str.append( + QStringLiteral(";;%1 Files (%2)") + .arg( + QtUtils::StringViewToQString(std::string_view(filter).substr(filter.starts_with("*.") ? 2 : 0)).toUpper()) + .arg(QString::fromStdString(filter))); + } + } + + QtHost::RunOnUIThread([title = QtUtils::StringViewToQString(title), select_directory, callback = std::move(callback), + filters_str = std::move(filters_str), + initial_directory = QtUtils::StringViewToQString(initial_directory), + from_cpu_thread]() mutable { + auto lock = g_main_window->pauseAndLockVM(); + + QString path; + + if (select_directory) + path = QFileDialog::getExistingDirectory(lock.getDialogParent(), title, initial_directory); + else + path = QFileDialog::getOpenFileName(lock.getDialogParent(), title, initial_directory, filters_str); + + if (!path.isEmpty()) + path = QDir::toNativeSeparators(path); + + if (from_cpu_thread) + Host::RunOnCPUThread([callback = std::move(callback), path = path.toStdString()]() { callback(path); }); + else + callback(path.toStdString()); + }); +} + void Host::PumpMessagesOnCPUThread() { g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents); @@ -1255,9 +1310,9 @@ bool QtHost::InitializeConfig() QMessageBox::critical( nullptr, QStringLiteral("PCSX2"), QStringLiteral("Failed to create data directory at path\n\n%1\n\n" - "The error was: %2\n" - "Please ensure this directory is writable. You can also try portable mode " - "by creating portable.txt in the same directory you installed PCSX2 into.") + "The error was: %2\n" + "Please ensure this directory is writable. You can also try portable mode " + "by creating portable.txt in the same directory you installed PCSX2 into.") .arg(QString::fromStdString(EmuFolders::DataRoot)) .arg(QString::fromStdString(error.GetDescription()))); return false; diff --git a/pcsx2/ImGui/ImGuiFullscreen.cpp b/pcsx2/ImGui/ImGuiFullscreen.cpp index 77f3fbb853..37fea51c4d 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.cpp +++ b/pcsx2/ImGui/ImGuiFullscreen.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #define IMGUI_DEFINE_MATH_OPERATORS @@ -9,6 +9,7 @@ #include "GS/Renderers/Common/GSTexture.h" #include "ImGui/ImGuiAnimated.h" #include "ImGui/ImGuiFullscreen.h" +#include "ImGui/ImGuiManager.h" #include "common/Assertions.h" #include "common/Console.h" @@ -1871,7 +1872,7 @@ void ImGuiFullscreen::PopulateFileSelectorItems() { if (s_file_selector_filters.empty() || std::none_of(s_file_selector_filters.begin(), s_file_selector_filters.end(), - [&fd](const std::string& filter) { return StringUtil::WildcardMatch(fd.FileName.c_str(), filter.c_str()); })) + [&fd](const std::string& filter) { return StringUtil::WildcardMatch(fd.FileName.c_str(), filter.c_str(), false); })) { continue; } @@ -1900,6 +1901,16 @@ bool ImGuiFullscreen::IsFileSelectorOpen() void ImGuiFullscreen::OpenFileSelector(std::string_view title, bool select_directory, FileSelectorCallback callback, FileSelectorFilters filters, std::string initial_directory) { + if (initial_directory.empty() || !FileSystem::DirectoryExists(initial_directory.c_str())) + initial_directory = FileSystem::GetWorkingDirectory(); + + if (Host::ShouldPreferHostFileSelector()) + { + Host::OpenHostFileSelectorAsync(ImGuiManager::StripIconCharacters(title), select_directory, std::move(callback), + std::move(filters), initial_directory); + return; + } + if (s_file_selector_open) CloseFileSelector(); @@ -1909,8 +1920,6 @@ void ImGuiFullscreen::OpenFileSelector(std::string_view title, bool select_direc s_file_selector_callback = std::move(callback); s_file_selector_filters = std::move(filters); - if (initial_directory.empty() || !FileSystem::DirectoryExists(initial_directory.c_str())) - initial_directory = FileSystem::GetWorkingDirectory(); SetFileSelectorDirectory(std::move(initial_directory)); QueueResetFocus(); } diff --git a/pcsx2/ImGui/ImGuiFullscreen.h b/pcsx2/ImGui/ImGuiFullscreen.h index 7c7ec85f71..9461481534 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.h +++ b/pcsx2/ImGui/ImGuiFullscreen.h @@ -264,3 +264,17 @@ namespace ImGuiFullscreen void GetFileSelectorHelpText(SmallStringBase& dest); void GetInputDialogHelpText(SmallStringBase& dest); } // namespace ImGuiFullscreen + +// Host UI triggers from Big Picture mode. +namespace Host +{ + /// Returns true if native file dialogs should be preferred over Big Picture. + bool ShouldPreferHostFileSelector(); + + /// Opens a file selector dialog. + using FileSelectorCallback = std::function; + using FileSelectorFilters = std::vector; + void OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback, + FileSelectorFilters filters = FileSelectorFilters(), + std::string_view initial_directory = std::string_view()); +} // namespace Host diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index 0c4f6d90ef..c4fb41b1a7 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -1061,3 +1061,25 @@ void ImGuiManager::SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y sc.pos.first = pos_x; sc.pos.second = pos_y; } + +std::string ImGuiManager::StripIconCharacters(std::string_view str) +{ + std::string result; + result.reserve(str.length()); + + for (size_t offset = 0; offset < str.length();) + { + char32_t utf; + offset += StringUtil::DecodeUTF8(str, offset, &utf); + + // icon if outside BMP/SMP/TIP, or inside private use area + if (utf > 0x32FFF || (utf >= 0xE000 && utf <= 0xF8FF)) + continue; + + StringUtil::EncodeAndAppendUTF8(result, utf); + } + + StringUtil::StripWhitespace(&result); + + return result; +} diff --git a/pcsx2/ImGui/ImGuiManager.h b/pcsx2/ImGui/ImGuiManager.h index 52d2ef9a89..8d1683256b 100644 --- a/pcsx2/ImGui/ImGuiManager.h +++ b/pcsx2/ImGui/ImGuiManager.h @@ -103,6 +103,9 @@ namespace ImGuiManager /// Sets the position of a software cursor, used when we have relative coordinates such as controllers. void SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y); + + /// Strips icon characters from a string. + std::string StripIconCharacters(std::string_view str); } // namespace ImGuiManager namespace Host diff --git a/tests/ctest/core/StubHost.cpp b/tests/ctest/core/StubHost.cpp index edc2b9f2b6..677b4d8684 100644 --- a/tests/ctest/core/StubHost.cpp +++ b/tests/ctest/core/StubHost.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include "pcsx2/Achievements.h" @@ -6,6 +6,8 @@ #include "pcsx2/GameList.h" #include "pcsx2/Host.h" #include "pcsx2/ImGui/FullscreenUI.h" +#include "pcsx2/ImGui/ImGuiAnimated.h" +#include "pcsx2/ImGui/ImGuiFullscreen.h" #include "pcsx2/ImGui/ImGuiManager.h" #include "pcsx2/Input/InputManager.h" #include "pcsx2/VMManager.h" @@ -213,6 +215,17 @@ void Host::OnCreateMemoryCardOpenRequested() { } +bool Host::ShouldPreferHostFileSelector() +{ + return false; +} + +void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback, + FileSelectorFilters filters, std::string_view initial_directory) +{ + callback(std::string()); +} + std::optional InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str) { return std::nullopt;