|
10 | 10 | #include <cmath> |
11 | 11 | #include <cstdio> |
12 | 12 | #include <cstring> |
| 13 | +#include <filesystem> |
13 | 14 | #include <set> |
14 | 15 | #include <string> |
15 | 16 | #include <string_view> |
|
19 | 20 |
|
20 | 21 | #include "IconsFontAwesome5.h" |
21 | 22 | #include "IconsPromptFont.h" |
| 23 | +#include "helpers.hpp" |
22 | 24 | #include "config.hpp" |
23 | 25 | #include "emulator.hpp" |
24 | 26 | #include "frontend_settings.hpp" |
@@ -113,6 +115,56 @@ namespace |
113 | 115 | return value; |
114 | 116 | } |
115 | 117 |
|
| 118 | + std::string percentEncodeUrl(std::string_view value) |
| 119 | + { |
| 120 | + std::string encoded; |
| 121 | + encoded.reserve(value.size()); |
| 122 | + for (unsigned char c : value) { |
| 123 | + const bool unreserved = |
| 124 | + (c >= 'a' && c <= 'z') || |
| 125 | + (c >= 'A' && c <= 'Z') || |
| 126 | + (c >= '0' && c <= '9') || |
| 127 | + c == '-' || c == '_' || c == '.' || c == '~' || c == '/' || c == ':'; |
| 128 | + if (unreserved) { |
| 129 | + encoded.push_back(static_cast<char>(c)); |
| 130 | + } else { |
| 131 | + static constexpr char hex[] = "0123456789ABCDEF"; |
| 132 | + encoded.push_back('%'); |
| 133 | + encoded.push_back(hex[c >> 4]); |
| 134 | + encoded.push_back(hex[c & 0x0F]); |
| 135 | + } |
| 136 | + } |
| 137 | + return encoded; |
| 138 | + } |
| 139 | + |
| 140 | + std::string fileUrlForPath(const std::filesystem::path& path) |
| 141 | + { |
| 142 | + std::error_code ec; |
| 143 | + const std::filesystem::path absolute = std::filesystem::absolute(path, ec); |
| 144 | + const std::string generic = (ec || absolute.empty()) ? path.generic_string() : absolute.generic_string(); |
| 145 | + std::string url = "file://"; |
| 146 | + #ifdef _WIN32 |
| 147 | + url.push_back('/'); |
| 148 | + #endif |
| 149 | + url += percentEncodeUrl(generic); |
| 150 | + return url; |
| 151 | + } |
| 152 | + |
| 153 | + bool openUrl(std::string_view url) |
| 154 | + { |
| 155 | + const std::string url_string(url); |
| 156 | + if (SDL_OpenURL(url_string.c_str()) != 0) { |
| 157 | + Helpers::warn("Failed to open URL %s: %s", url_string.c_str(), SDL_GetError()); |
| 158 | + return false; |
| 159 | + } |
| 160 | + return true; |
| 161 | + } |
| 162 | + |
| 163 | + bool openPathInBrowser(const std::filesystem::path& path) |
| 164 | + { |
| 165 | + return openUrl(fileUrlForPath(path)); |
| 166 | + } |
| 167 | + |
116 | 168 | const char* windowIconFilename(FrontendSettings::WindowIcon icon) |
117 | 169 | { |
118 | 170 | switch (icon) { |
@@ -1332,6 +1384,8 @@ bool PandaFsuiAdapter::initialize(const fsui::FontStack& fonts) |
1332 | 1384 | }; |
1333 | 1385 | fsuiContext.host.detect_prompt_icon_pack = []() { return fsui::DetectPromptIconPackFromSDL(); }; |
1334 | 1386 | fsuiContext.host.detect_swap_north_west_gamepad_buttons = []() { return false; }; |
| 1387 | + fsuiContext.host.open_file_browser = [this](const std::filesystem::path& path) { openPathInBrowser(path); }; |
| 1388 | + fsuiContext.host.open_url = [](std::string_view url) { openUrl(url); }; |
1335 | 1389 | fsuiContext.host.runtime_overlay_options = fsui::RuntimeOverlayOptions{ |
1336 | 1390 | .show_inputs = true, |
1337 | 1391 | .show_settings = true, |
|
0 commit comments