diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 97c1d2a1a..144dd0a8a 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -928,8 +928,8 @@ void FullscreenUI::DestroyResources() ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters() { - return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.mds", "*.psexe", - "*.ps-exe", "*.exe", "*.psf", "*.minipsf", "*.m3u", "*.pbp", "*.PBP"}; + return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.mds", + "*.psexe", "*.ps-exe", "*.exe", "*.psf", "*.minipsf", "*.m3u", "*.pbp"}; } void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional fast_boot) diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index d22dd74ee..7a3928b9c 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "qthost.h" @@ -36,6 +36,7 @@ #include "util/audio_stream.h" #include "util/http_downloader.h" +#include "util/imgui_fullscreen.h" #include "util/imgui_manager.h" #include "util/ini_settings_interface.h" #include "util/input_manager.h" @@ -1566,6 +1567,60 @@ void Host::OnCoverDownloaderOpenRequested() emit g_emu_thread->onCoverDownloaderOpenRequested(); } +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 /* = FileSelectorFilters() */, + std::string_view initial_directory /* = std::string_view() */) +{ + const bool from_cpu_thread = g_emu_thread->isOnThread(); + + 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->pauseAndLockSystem(); + + 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 EmuThread::doBackgroundControllerPoll() { System::Internal::IdlePollUpdate(); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index b9e82e800..220313254 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -12,6 +12,7 @@ #include "scmversion/scmversion.h" #include "util/gpu_device.h" +#include "util/imgui_fullscreen.h" #include "util/imgui_manager.h" #include "util/input_manager.h" #include "util/platform_misc.h" @@ -387,6 +388,18 @@ void Host::OnCoverDownloaderOpenRequested() // noop } +bool Host::ShouldPreferHostFileSelector() +{ + return false; +} + +void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback, + FileSelectorFilters filters /* = FileSelectorFilters() */, + std::string_view initial_directory /* = std::string_view() */) +{ + callback(std::string()); +} + std::optional InputManager::ConvertHostKeyboardStringToCode(std::string_view str) { return std::nullopt; diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index d1d54a463..df82a8014 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -7,6 +7,7 @@ #include "gpu_device.h" #include "image.h" #include "imgui_animated.h" +#include "imgui_manager.h" #include "common/assert.h" #include "common/easing.h" @@ -2072,7 +2073,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()); + return StringUtil::WildcardMatch(fd.FileName.c_str(), filter.c_str(), false); })) { continue; @@ -2102,6 +2103,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(); @@ -2111,8 +2122,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/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index bf61fd5d4..16a8ef521 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -314,3 +314,16 @@ void GetChoiceDialogHelpText(SmallStringBase& dest); 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/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 8dbc6692a..b68b3f452 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -1123,3 +1123,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/src/util/imgui_manager.h b/src/util/imgui_manager.h index fd865102b..3c914ab01 100644 --- a/src/util/imgui_manager.h +++ b/src/util/imgui_manager.h @@ -106,6 +106,9 @@ void SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y); /// Adds software cursors to ImGui render list. void RenderSoftwareCursors(); + +/// Strips icon characters from a string. +std::string StripIconCharacters(std::string_view str); } // namespace ImGuiManager namespace Host {