diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/yuzu/applets/web_browser.cpp | 333 | ||||
| -rw-r--r-- | src/yuzu/applets/web_browser.h | 155 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 110 | ||||
| -rw-r--r-- | src/yuzu/main.h | 7 |
4 files changed, 600 insertions, 5 deletions
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index 92b53fed0..26b9df51a 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp | |||
| @@ -2,10 +2,339 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "core/hle/lock.h" | 5 | #ifdef YUZU_USE_QT_WEB_ENGINE |
| 6 | #include <QKeyEvent> | ||
| 7 | |||
| 8 | #include <QWebEngineProfile> | ||
| 9 | #include <QWebEngineScript> | ||
| 10 | #include <QWebEngineScriptCollection> | ||
| 11 | #include <QWebEngineSettings> | ||
| 12 | #include <QWebEngineUrlScheme> | ||
| 13 | #endif | ||
| 14 | |||
| 15 | #include "common/file_util.h" | ||
| 16 | #include "core/core.h" | ||
| 17 | #include "core/frontend/input_interpreter.h" | ||
| 6 | #include "yuzu/applets/web_browser.h" | 18 | #include "yuzu/applets/web_browser.h" |
| 19 | #include "yuzu/applets/web_browser_scripts.h" | ||
| 7 | #include "yuzu/main.h" | 20 | #include "yuzu/main.h" |
| 21 | #include "yuzu/util/url_request_interceptor.h" | ||
| 22 | |||
| 23 | #ifdef YUZU_USE_QT_WEB_ENGINE | ||
| 24 | |||
| 25 | namespace { | ||
| 26 | |||
| 27 | constexpr int HIDButtonToKey(HIDButton button) { | ||
| 28 | switch (button) { | ||
| 29 | case HIDButton::DLeft: | ||
| 30 | case HIDButton::LStickLeft: | ||
| 31 | return Qt::Key_Left; | ||
| 32 | case HIDButton::DUp: | ||
| 33 | case HIDButton::LStickUp: | ||
| 34 | return Qt::Key_Up; | ||
| 35 | case HIDButton::DRight: | ||
| 36 | case HIDButton::LStickRight: | ||
| 37 | return Qt::Key_Right; | ||
| 38 | case HIDButton::DDown: | ||
| 39 | case HIDButton::LStickDown: | ||
| 40 | return Qt::Key_Down; | ||
| 41 | default: | ||
| 42 | return 0; | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | } // Anonymous namespace | ||
| 47 | |||
| 48 | QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system) | ||
| 49 | : QWebEngineView(parent), url_interceptor(std::make_unique<UrlRequestInterceptor>()), | ||
| 50 | input_interpreter(std::make_unique<InputInterpreter>(system)) { | ||
| 51 | QWebEngineScript nx_font_css; | ||
| 52 | QWebEngineScript load_nx_font; | ||
| 53 | QWebEngineScript gamepad; | ||
| 54 | QWebEngineScript window_nx; | ||
| 55 | |||
| 56 | const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath( | ||
| 57 | fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)))); | ||
| 58 | |||
| 59 | nx_font_css.setName(QStringLiteral("nx_font_css.js")); | ||
| 60 | load_nx_font.setName(QStringLiteral("load_nx_font.js")); | ||
| 61 | gamepad.setName(QStringLiteral("gamepad_script.js")); | ||
| 62 | window_nx.setName(QStringLiteral("window_nx_script.js")); | ||
| 63 | |||
| 64 | nx_font_css.setSourceCode( | ||
| 65 | QString::fromStdString(NX_FONT_CSS) | ||
| 66 | .arg(fonts_dir + QStringLiteral("/FontStandard.ttf")) | ||
| 67 | .arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf")) | ||
| 68 | .arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf")) | ||
| 69 | .arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf")) | ||
| 70 | .arg(fonts_dir + QStringLiteral("/FontKorean.ttf")) | ||
| 71 | .arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf")) | ||
| 72 | .arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf"))); | ||
| 73 | load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); | ||
| 74 | gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); | ||
| 75 | window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); | ||
| 76 | |||
| 77 | nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); | ||
| 78 | load_nx_font.setInjectionPoint(QWebEngineScript::Deferred); | ||
| 79 | gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); | ||
| 80 | window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); | ||
| 81 | |||
| 82 | nx_font_css.setWorldId(QWebEngineScript::MainWorld); | ||
| 83 | load_nx_font.setWorldId(QWebEngineScript::MainWorld); | ||
| 84 | gamepad.setWorldId(QWebEngineScript::MainWorld); | ||
| 85 | window_nx.setWorldId(QWebEngineScript::MainWorld); | ||
| 86 | |||
| 87 | nx_font_css.setRunsOnSubFrames(true); | ||
| 88 | load_nx_font.setRunsOnSubFrames(true); | ||
| 89 | gamepad.setRunsOnSubFrames(true); | ||
| 90 | window_nx.setRunsOnSubFrames(true); | ||
| 91 | |||
| 92 | auto* default_profile = QWebEngineProfile::defaultProfile(); | ||
| 93 | |||
| 94 | default_profile->scripts()->insert(nx_font_css); | ||
| 95 | default_profile->scripts()->insert(load_nx_font); | ||
| 96 | default_profile->scripts()->insert(gamepad); | ||
| 97 | default_profile->scripts()->insert(window_nx); | ||
| 98 | |||
| 99 | default_profile->setRequestInterceptor(url_interceptor.get()); | ||
| 100 | |||
| 101 | auto* global_settings = QWebEngineSettings::globalSettings(); | ||
| 102 | |||
| 103 | global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); | ||
| 104 | global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); | ||
| 105 | global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); | ||
| 106 | global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); | ||
| 107 | global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); | ||
| 108 | global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); | ||
| 109 | |||
| 110 | connect( | ||
| 111 | url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(), | ||
| 112 | [this] { | ||
| 113 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); | ||
| 114 | page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT)); | ||
| 115 | }, | ||
| 116 | Qt::QueuedConnection); | ||
| 117 | |||
| 118 | connect( | ||
| 119 | page(), &QWebEnginePage::windowCloseRequested, page(), | ||
| 120 | [this] { | ||
| 121 | if (page()->url() == url_interceptor->GetRequestedURL()) { | ||
| 122 | SetFinished(true); | ||
| 123 | SetExitReason(WebExitReason::WindowClosed); | ||
| 124 | } | ||
| 125 | }, | ||
| 126 | Qt::QueuedConnection); | ||
| 127 | } | ||
| 128 | |||
| 129 | QtNXWebEngineView::~QtNXWebEngineView() { | ||
| 130 | SetFinished(true); | ||
| 131 | StopInputThread(); | ||
| 132 | } | ||
| 133 | |||
| 134 | void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url, | ||
| 135 | std::string_view additional_args) { | ||
| 136 | SetUserAgent(UserAgent::WebApplet); | ||
| 137 | SetFinished(false); | ||
| 138 | SetExitReason(WebExitReason::EndButtonPressed); | ||
| 139 | SetLastURL("http://localhost/"); | ||
| 140 | StartInputThread(); | ||
| 141 | |||
| 142 | load(QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(main_url))).toString() + | ||
| 143 | QString::fromStdString(std::string(additional_args)))); | ||
| 144 | } | ||
| 145 | |||
| 146 | void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { | ||
| 147 | const QString user_agent_str = [user_agent] { | ||
| 148 | switch (user_agent) { | ||
| 149 | case UserAgent::WebApplet: | ||
| 150 | default: | ||
| 151 | return QStringLiteral("WebApplet"); | ||
| 152 | case UserAgent::ShopN: | ||
| 153 | return QStringLiteral("ShopN"); | ||
| 154 | case UserAgent::LoginApplet: | ||
| 155 | return QStringLiteral("LoginApplet"); | ||
| 156 | case UserAgent::ShareApplet: | ||
| 157 | return QStringLiteral("ShareApplet"); | ||
| 158 | case UserAgent::LobbyApplet: | ||
| 159 | return QStringLiteral("LobbyApplet"); | ||
| 160 | case UserAgent::WifiWebAuthApplet: | ||
| 161 | return QStringLiteral("WifiWebAuthApplet"); | ||
| 162 | } | ||
| 163 | }(); | ||
| 164 | |||
| 165 | QWebEngineProfile::defaultProfile()->setHttpUserAgent( | ||
| 166 | QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 " | ||
| 167 | "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389") | ||
| 168 | .arg(user_agent_str)); | ||
| 169 | } | ||
| 170 | |||
| 171 | bool QtNXWebEngineView::IsFinished() const { | ||
| 172 | return finished; | ||
| 173 | } | ||
| 174 | |||
| 175 | void QtNXWebEngineView::SetFinished(bool finished_) { | ||
| 176 | finished = finished_; | ||
| 177 | } | ||
| 178 | |||
| 179 | WebExitReason QtNXWebEngineView::GetExitReason() const { | ||
| 180 | return exit_reason; | ||
| 181 | } | ||
| 182 | |||
| 183 | void QtNXWebEngineView::SetExitReason(WebExitReason exit_reason_) { | ||
| 184 | exit_reason = exit_reason_; | ||
| 185 | } | ||
| 8 | 186 | ||
| 9 | QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {} | 187 | const std::string& QtNXWebEngineView::GetLastURL() const { |
| 188 | return last_url; | ||
| 189 | } | ||
| 190 | |||
| 191 | void QtNXWebEngineView::SetLastURL(std::string last_url_) { | ||
| 192 | last_url = std::move(last_url_); | ||
| 193 | } | ||
| 194 | |||
| 195 | QString QtNXWebEngineView::GetCurrentURL() const { | ||
| 196 | return url_interceptor->GetRequestedURL().toString(); | ||
| 197 | } | ||
| 198 | |||
| 199 | void QtNXWebEngineView::hide() { | ||
| 200 | SetFinished(true); | ||
| 201 | StopInputThread(); | ||
| 202 | |||
| 203 | QWidget::hide(); | ||
| 204 | } | ||
| 205 | |||
| 206 | template <HIDButton... T> | ||
| 207 | void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { | ||
| 208 | const auto f = [this](HIDButton button) { | ||
| 209 | if (input_interpreter->IsButtonPressedOnce(button)) { | ||
| 210 | page()->runJavaScript( | ||
| 211 | QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)), | ||
| 212 | [&](const QVariant& variant) { | ||
| 213 | if (variant.toBool()) { | ||
| 214 | switch (button) { | ||
| 215 | case HIDButton::A: | ||
| 216 | SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>(); | ||
| 217 | break; | ||
| 218 | case HIDButton::B: | ||
| 219 | SendKeyPressEvent(Qt::Key_B); | ||
| 220 | break; | ||
| 221 | case HIDButton::X: | ||
| 222 | SendKeyPressEvent(Qt::Key_X); | ||
| 223 | break; | ||
| 224 | case HIDButton::Y: | ||
| 225 | SendKeyPressEvent(Qt::Key_Y); | ||
| 226 | break; | ||
| 227 | default: | ||
| 228 | break; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | }); | ||
| 232 | |||
| 233 | page()->runJavaScript( | ||
| 234 | QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") | ||
| 235 | .arg(static_cast<u8>(button))); | ||
| 236 | } | ||
| 237 | }; | ||
| 238 | |||
| 239 | (f(T), ...); | ||
| 240 | } | ||
| 241 | |||
| 242 | template <HIDButton... T> | ||
| 243 | void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { | ||
| 244 | const auto f = [this](HIDButton button) { | ||
| 245 | if (input_interpreter->IsButtonPressedOnce(button)) { | ||
| 246 | SendKeyPressEvent(HIDButtonToKey(button)); | ||
| 247 | } | ||
| 248 | }; | ||
| 249 | |||
| 250 | (f(T), ...); | ||
| 251 | } | ||
| 252 | |||
| 253 | template <HIDButton... T> | ||
| 254 | void QtNXWebEngineView::HandleWindowKeyButtonHold() { | ||
| 255 | const auto f = [this](HIDButton button) { | ||
| 256 | if (input_interpreter->IsButtonHeld(button)) { | ||
| 257 | SendKeyPressEvent(HIDButtonToKey(button)); | ||
| 258 | } | ||
| 259 | }; | ||
| 260 | |||
| 261 | (f(T), ...); | ||
| 262 | } | ||
| 263 | |||
| 264 | void QtNXWebEngineView::SendKeyPressEvent(int key) { | ||
| 265 | if (key == 0) { | ||
| 266 | return; | ||
| 267 | } | ||
| 268 | |||
| 269 | QCoreApplication::postEvent(focusProxy(), | ||
| 270 | new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier)); | ||
| 271 | QCoreApplication::postEvent(focusProxy(), | ||
| 272 | new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier)); | ||
| 273 | } | ||
| 274 | |||
| 275 | void QtNXWebEngineView::StartInputThread() { | ||
| 276 | if (input_thread_running) { | ||
| 277 | return; | ||
| 278 | } | ||
| 279 | |||
| 280 | input_thread_running = true; | ||
| 281 | input_thread = std::thread(&QtNXWebEngineView::InputThread, this); | ||
| 282 | } | ||
| 283 | |||
| 284 | void QtNXWebEngineView::StopInputThread() { | ||
| 285 | input_thread_running = false; | ||
| 286 | if (input_thread.joinable()) { | ||
| 287 | input_thread.join(); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | void QtNXWebEngineView::InputThread() { | ||
| 292 | // Wait for 1 second before allowing any inputs to be processed. | ||
| 293 | std::this_thread::sleep_for(std::chrono::seconds(1)); | ||
| 294 | |||
| 295 | while (input_thread_running) { | ||
| 296 | input_interpreter->PollInput(); | ||
| 297 | |||
| 298 | HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y, | ||
| 299 | HIDButton::L, HIDButton::R>(); | ||
| 300 | |||
| 301 | HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight, | ||
| 302 | HIDButton::DDown, HIDButton::LStickLeft, | ||
| 303 | HIDButton::LStickUp, HIDButton::LStickRight, | ||
| 304 | HIDButton::LStickDown>(); | ||
| 305 | |||
| 306 | HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight, | ||
| 307 | HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp, | ||
| 308 | HIDButton::LStickRight, HIDButton::LStickDown>(); | ||
| 309 | |||
| 310 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | #endif | ||
| 315 | |||
| 316 | QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { | ||
| 317 | connect(this, &QtWebBrowser::MainWindowOpenLocalWebPage, &main_window, | ||
| 318 | &GMainWindow::WebBrowserOpenLocalWebPage, Qt::QueuedConnection); | ||
| 319 | connect(&main_window, &GMainWindow::WebBrowserClosed, this, | ||
| 320 | &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection); | ||
| 321 | } | ||
| 10 | 322 | ||
| 11 | QtWebBrowser::~QtWebBrowser() = default; | 323 | QtWebBrowser::~QtWebBrowser() = default; |
| 324 | |||
| 325 | void QtWebBrowser::OpenLocalWebPage( | ||
| 326 | std::string_view local_url, std::function<void(WebExitReason, std::string)> callback) const { | ||
| 327 | this->callback = std::move(callback); | ||
| 328 | |||
| 329 | const auto index = local_url.find('?'); | ||
| 330 | |||
| 331 | if (index == std::string::npos) { | ||
| 332 | emit MainWindowOpenLocalWebPage(local_url, ""); | ||
| 333 | } else { | ||
| 334 | emit MainWindowOpenLocalWebPage(local_url.substr(0, index), local_url.substr(index)); | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | void QtWebBrowser::MainWindowWebBrowserClosed(WebExitReason exit_reason, std::string last_url) { | ||
| 339 | callback(exit_reason, last_url); | ||
| 340 | } | ||
diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h index af053ace7..74f2b49d2 100644 --- a/src/yuzu/applets/web_browser.h +++ b/src/yuzu/applets/web_browser.h | |||
| @@ -4,6 +4,10 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <atomic> | ||
| 8 | #include <memory> | ||
| 9 | #include <thread> | ||
| 10 | |||
| 7 | #include <QObject> | 11 | #include <QObject> |
| 8 | 12 | ||
| 9 | #ifdef YUZU_USE_QT_WEB_ENGINE | 13 | #ifdef YUZU_USE_QT_WEB_ENGINE |
| @@ -12,12 +16,161 @@ | |||
| 12 | 16 | ||
| 13 | #include "core/frontend/applets/web_browser.h" | 17 | #include "core/frontend/applets/web_browser.h" |
| 14 | 18 | ||
| 19 | enum class HIDButton : u8; | ||
| 20 | |||
| 21 | class InputInterpreter; | ||
| 15 | class GMainWindow; | 22 | class GMainWindow; |
| 23 | class UrlRequestInterceptor; | ||
| 24 | |||
| 25 | namespace Core { | ||
| 26 | class System; | ||
| 27 | } | ||
| 28 | |||
| 29 | #ifdef YUZU_USE_QT_WEB_ENGINE | ||
| 30 | |||
| 31 | enum class UserAgent { | ||
| 32 | WebApplet, | ||
| 33 | ShopN, | ||
| 34 | LoginApplet, | ||
| 35 | ShareApplet, | ||
| 36 | LobbyApplet, | ||
| 37 | WifiWebAuthApplet, | ||
| 38 | }; | ||
| 39 | |||
| 40 | class QtNXWebEngineView : public QWebEngineView { | ||
| 41 | Q_OBJECT | ||
| 42 | |||
| 43 | public: | ||
| 44 | explicit QtNXWebEngineView(QWidget* parent, Core::System& system); | ||
| 45 | ~QtNXWebEngineView() override; | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Loads a HTML document that exists locally. Cannot be used to load external websites. | ||
| 49 | * | ||
| 50 | * @param main_url The url to the file. | ||
| 51 | * @param additional_args Additional arguments appended to the main url. | ||
| 52 | */ | ||
| 53 | void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Sets the background color of the web page. | ||
| 57 | * | ||
| 58 | * @param color The color to set. | ||
| 59 | */ | ||
| 60 | void SetBackgroundColor(QColor color); | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Sets the user agent of the web browser. | ||
| 64 | * | ||
| 65 | * @param user_agent The user agent enum. | ||
| 66 | */ | ||
| 67 | void SetUserAgent(UserAgent user_agent); | ||
| 68 | |||
| 69 | [[nodiscard]] bool IsFinished() const; | ||
| 70 | void SetFinished(bool finished_); | ||
| 71 | |||
| 72 | [[nodiscard]] WebExitReason GetExitReason() const; | ||
| 73 | void SetExitReason(WebExitReason exit_reason_); | ||
| 74 | |||
| 75 | [[nodiscard]] const std::string& GetLastURL() const; | ||
| 76 | void SetLastURL(std::string last_url_); | ||
| 77 | |||
| 78 | /** | ||
| 79 | * This gets the current URL that has been requested by the webpage. | ||
| 80 | * This only applies to the main frame. Sub frames and other resources are ignored. | ||
| 81 | * | ||
| 82 | * @return Currently requested URL | ||
| 83 | */ | ||
| 84 | [[nodiscard]] QString GetCurrentURL() const; | ||
| 85 | |||
| 86 | public slots: | ||
| 87 | void hide(); | ||
| 88 | |||
| 89 | private: | ||
| 90 | /** | ||
| 91 | * Handles button presses to execute functions assigned in yuzu_key_callbacks. | ||
| 92 | * yuzu_key_callbacks contains specialized functions for the buttons in the window footer | ||
| 93 | * that can be overriden by games to achieve desired functionality. | ||
| 94 | * | ||
| 95 | * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks | ||
| 96 | */ | ||
| 97 | template <HIDButton... T> | ||
| 98 | void HandleWindowFooterButtonPressedOnce(); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Handles button presses and converts them into keyboard input. | ||
| 102 | * This should only be used to convert D-Pad or Analog Stick input into arrow keys. | ||
| 103 | * | ||
| 104 | * @tparam HIDButton The list of buttons that can be converted into keyboard input. | ||
| 105 | */ | ||
| 106 | template <HIDButton... T> | ||
| 107 | void HandleWindowKeyButtonPressedOnce(); | ||
| 108 | |||
| 109 | /** | ||
| 110 | * Handles button holds and converts them into keyboard input. | ||
| 111 | * This should only be used to convert D-Pad or Analog Stick input into arrow keys. | ||
| 112 | * | ||
| 113 | * @tparam HIDButton The list of buttons that can be converted into keyboard input. | ||
| 114 | */ | ||
| 115 | template <HIDButton... T> | ||
| 116 | void HandleWindowKeyButtonHold(); | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Sends a key press event to QWebEngineView. | ||
| 120 | * | ||
| 121 | * @param key Qt key code. | ||
| 122 | */ | ||
| 123 | void SendKeyPressEvent(int key); | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Sends multiple key press events to QWebEngineView. | ||
| 127 | * | ||
| 128 | * @tparam int Qt key code. | ||
| 129 | */ | ||
| 130 | template <int... T> | ||
| 131 | void SendMultipleKeyPressEvents() { | ||
| 132 | (SendKeyPressEvent(T), ...); | ||
| 133 | } | ||
| 134 | |||
| 135 | void StartInputThread(); | ||
| 136 | void StopInputThread(); | ||
| 137 | |||
| 138 | /// The thread where input is being polled and processed. | ||
| 139 | void InputThread(); | ||
| 140 | |||
| 141 | std::unique_ptr<UrlRequestInterceptor> url_interceptor; | ||
| 142 | |||
| 143 | std::unique_ptr<InputInterpreter> input_interpreter; | ||
| 144 | |||
| 145 | std::thread input_thread; | ||
| 146 | |||
| 147 | std::atomic<bool> input_thread_running{}; | ||
| 148 | |||
| 149 | std::atomic<bool> finished{}; | ||
| 150 | |||
| 151 | WebExitReason exit_reason{WebExitReason::EndButtonPressed}; | ||
| 152 | |||
| 153 | std::string last_url{"http://localhost/"}; | ||
| 154 | }; | ||
| 155 | |||
| 156 | #endif | ||
| 16 | 157 | ||
| 17 | class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { | 158 | class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { |
| 18 | Q_OBJECT | 159 | Q_OBJECT |
| 19 | 160 | ||
| 20 | public: | 161 | public: |
| 21 | explicit QtWebBrowser(GMainWindow& main_window); | 162 | explicit QtWebBrowser(GMainWindow& parent); |
| 22 | ~QtWebBrowser() override; | 163 | ~QtWebBrowser() override; |
| 164 | |||
| 165 | void OpenLocalWebPage(std::string_view local_url, | ||
| 166 | std::function<void(WebExitReason, std::string)> callback) const override; | ||
| 167 | |||
| 168 | signals: | ||
| 169 | void MainWindowOpenLocalWebPage(std::string_view main_url, | ||
| 170 | std::string_view additional_args) const; | ||
| 171 | |||
| 172 | private: | ||
| 173 | void MainWindowWebBrowserClosed(WebExitReason exit_reason, std::string last_url); | ||
| 174 | |||
| 175 | mutable std::function<void(WebExitReason, std::string)> callback; | ||
| 23 | }; | 176 | }; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7d4bba854..bab76db1e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -28,8 +28,6 @@ | |||
| 28 | #include "core/hle/service/am/applet_ae.h" | 28 | #include "core/hle/service/am/applet_ae.h" |
| 29 | #include "core/hle/service/am/applet_oe.h" | 29 | #include "core/hle/service/am/applet_oe.h" |
| 30 | #include "core/hle/service/am/applets/applets.h" | 30 | #include "core/hle/service/am/applets/applets.h" |
| 31 | #include "core/hle/service/hid/controllers/npad.h" | ||
| 32 | #include "core/hle/service/hid/hid.h" | ||
| 33 | 31 | ||
| 34 | // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows | 32 | // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows |
| 35 | // defines. | 33 | // defines. |
| @@ -182,6 +180,30 @@ static void InitializeLogging() { | |||
| 182 | #endif | 180 | #endif |
| 183 | } | 181 | } |
| 184 | 182 | ||
| 183 | static void RemoveCachedContents() { | ||
| 184 | const auto offline_fonts = Common::FS::SanitizePath( | ||
| 185 | fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | ||
| 186 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 187 | |||
| 188 | const auto offline_manual = Common::FS::SanitizePath( | ||
| 189 | fmt::format("{}/offline_web_applet_manual", | ||
| 190 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | ||
| 191 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 192 | const auto offline_legal_information = Common::FS::SanitizePath( | ||
| 193 | fmt::format("{}/offline_web_applet_legal_information", | ||
| 194 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | ||
| 195 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 196 | const auto offline_system_data = Common::FS::SanitizePath( | ||
| 197 | fmt::format("{}/offline_web_applet_system_data", | ||
| 198 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | ||
| 199 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 200 | |||
| 201 | Common::FS::DeleteDirRecursively(offline_fonts); | ||
| 202 | Common::FS::DeleteDirRecursively(offline_manual); | ||
| 203 | Common::FS::DeleteDirRecursively(offline_legal_information); | ||
| 204 | Common::FS::DeleteDirRecursively(offline_system_data); | ||
| 205 | } | ||
| 206 | |||
| 185 | GMainWindow::GMainWindow() | 207 | GMainWindow::GMainWindow() |
| 186 | : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, | 208 | : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, |
| 187 | config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, | 209 | config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, |
| @@ -250,6 +272,9 @@ GMainWindow::GMainWindow() | |||
| 250 | FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); | 272 | FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); |
| 251 | Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); | 273 | Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); |
| 252 | 274 | ||
| 275 | // Remove cached contents generated during the previous session | ||
| 276 | RemoveCachedContents(); | ||
| 277 | |||
| 253 | // Gen keys if necessary | 278 | // Gen keys if necessary |
| 254 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); | 279 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); |
| 255 | 280 | ||
| @@ -341,6 +366,86 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message | |||
| 341 | emit SoftwareKeyboardFinishedCheckDialog(); | 366 | emit SoftwareKeyboardFinishedCheckDialog(); |
| 342 | } | 367 | } |
| 343 | 368 | ||
| 369 | void GMainWindow::WebBrowserOpenLocalWebPage(std::string_view main_url, | ||
| 370 | std::string_view additional_args) { | ||
| 371 | #ifdef YUZU_USE_QT_WEB_ENGINE | ||
| 372 | |||
| 373 | QtNXWebEngineView web_browser_view(this, Core::System::GetInstance()); | ||
| 374 | |||
| 375 | web_browser_view.LoadLocalWebPage(main_url, additional_args); | ||
| 376 | |||
| 377 | ui.action_Pause->setEnabled(false); | ||
| 378 | ui.action_Restart->setEnabled(false); | ||
| 379 | ui.action_Stop->setEnabled(false); | ||
| 380 | |||
| 381 | if (render_window->IsLoadingComplete()) { | ||
| 382 | render_window->hide(); | ||
| 383 | } | ||
| 384 | |||
| 385 | const auto& layout = render_window->GetFramebufferLayout(); | ||
| 386 | web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight()); | ||
| 387 | web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height()); | ||
| 388 | web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) / | ||
| 389 | static_cast<qreal>(Layout::ScreenUndocked::Width)); | ||
| 390 | |||
| 391 | web_browser_view.setFocus(); | ||
| 392 | web_browser_view.show(); | ||
| 393 | |||
| 394 | bool exit_check = false; | ||
| 395 | |||
| 396 | while (!web_browser_view.IsFinished()) { | ||
| 397 | QCoreApplication::processEvents(); | ||
| 398 | |||
| 399 | if (!exit_check) { | ||
| 400 | web_browser_view.page()->runJavaScript( | ||
| 401 | QStringLiteral("end_applet;"), [&](const QVariant& variant) { | ||
| 402 | exit_check = false; | ||
| 403 | if (variant.toBool()) { | ||
| 404 | web_browser_view.SetFinished(true); | ||
| 405 | web_browser_view.SetExitReason(WebExitReason::EndButtonPressed); | ||
| 406 | } | ||
| 407 | }); | ||
| 408 | |||
| 409 | exit_check = true; | ||
| 410 | } | ||
| 411 | |||
| 412 | if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) { | ||
| 413 | if (!web_browser_view.IsFinished()) { | ||
| 414 | web_browser_view.SetFinished(true); | ||
| 415 | web_browser_view.SetExitReason(WebExitReason::CallbackURL); | ||
| 416 | } | ||
| 417 | |||
| 418 | web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString()); | ||
| 419 | } | ||
| 420 | |||
| 421 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| 422 | } | ||
| 423 | |||
| 424 | const auto exit_reason = web_browser_view.GetExitReason(); | ||
| 425 | const auto last_url = web_browser_view.GetLastURL(); | ||
| 426 | |||
| 427 | web_browser_view.hide(); | ||
| 428 | |||
| 429 | render_window->setFocus(); | ||
| 430 | |||
| 431 | if (render_window->IsLoadingComplete()) { | ||
| 432 | render_window->show(); | ||
| 433 | } | ||
| 434 | |||
| 435 | ui.action_Pause->setEnabled(true); | ||
| 436 | ui.action_Restart->setEnabled(true); | ||
| 437 | ui.action_Stop->setEnabled(true); | ||
| 438 | |||
| 439 | emit WebBrowserClosed(exit_reason, last_url); | ||
| 440 | |||
| 441 | #else | ||
| 442 | |||
| 443 | // Utilize the same fallback as the default web browser applet. | ||
| 444 | emit WebBrowserClosed(WebExitReason::WindowClosed, "http://localhost"); | ||
| 445 | |||
| 446 | #endif | ||
| 447 | } | ||
| 448 | |||
| 344 | void GMainWindow::InitializeWidgets() { | 449 | void GMainWindow::InitializeWidgets() { |
| 345 | #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING | 450 | #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING |
| 346 | ui.action_Report_Compatibility->setVisible(true); | 451 | ui.action_Report_Compatibility->setVisible(true); |
| @@ -1948,6 +2053,7 @@ void GMainWindow::OnStartGame() { | |||
| 1948 | qRegisterMetaType<std::string>("std::string"); | 2053 | qRegisterMetaType<std::string>("std::string"); |
| 1949 | qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); | 2054 | qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); |
| 1950 | qRegisterMetaType<std::string_view>("std::string_view"); | 2055 | qRegisterMetaType<std::string_view>("std::string_view"); |
| 2056 | qRegisterMetaType<Service::AM::Applets::WebExitReason>("Service::AM::Applets::WebExitReason"); | ||
| 1951 | 2057 | ||
| 1952 | connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); | 2058 | connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); |
| 1953 | 2059 | ||
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index f311f2b5b..22f64fc9c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -55,6 +55,10 @@ namespace InputCommon { | |||
| 55 | class InputSubsystem; | 55 | class InputSubsystem; |
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | namespace Service::AM::Applets { | ||
| 59 | enum class WebExitReason : u32; | ||
| 60 | } | ||
| 61 | |||
| 58 | enum class EmulatedDirectoryTarget { | 62 | enum class EmulatedDirectoryTarget { |
| 59 | NAND, | 63 | NAND, |
| 60 | SDMC, | 64 | SDMC, |
| @@ -126,6 +130,8 @@ signals: | |||
| 126 | void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); | 130 | void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); |
| 127 | void SoftwareKeyboardFinishedCheckDialog(); | 131 | void SoftwareKeyboardFinishedCheckDialog(); |
| 128 | 132 | ||
| 133 | void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); | ||
| 134 | |||
| 129 | public slots: | 135 | public slots: |
| 130 | void OnLoadComplete(); | 136 | void OnLoadComplete(); |
| 131 | void OnExecuteProgram(std::size_t program_index); | 137 | void OnExecuteProgram(std::size_t program_index); |
| @@ -135,6 +141,7 @@ public slots: | |||
| 135 | void ProfileSelectorSelectProfile(); | 141 | void ProfileSelectorSelectProfile(); |
| 136 | void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); | 142 | void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); |
| 137 | void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); | 143 | void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); |
| 144 | void WebBrowserOpenLocalWebPage(std::string_view main_url, std::string_view additional_args); | ||
| 138 | void OnAppFocusStateChanged(Qt::ApplicationState state); | 145 | void OnAppFocusStateChanged(Qt::ApplicationState state); |
| 139 | 146 | ||
| 140 | private: | 147 | private: |