summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/yuzu/applets/web_browser.cpp333
-rw-r--r--src/yuzu/applets/web_browser.h155
-rw-r--r--src/yuzu/main.cpp110
-rw-r--r--src/yuzu/main.h7
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
25namespace {
26
27constexpr 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
48QtNXWebEngineView::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
129QtNXWebEngineView::~QtNXWebEngineView() {
130 SetFinished(true);
131 StopInputThread();
132}
133
134void 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
146void 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
171bool QtNXWebEngineView::IsFinished() const {
172 return finished;
173}
174
175void QtNXWebEngineView::SetFinished(bool finished_) {
176 finished = finished_;
177}
178
179WebExitReason QtNXWebEngineView::GetExitReason() const {
180 return exit_reason;
181}
182
183void QtNXWebEngineView::SetExitReason(WebExitReason exit_reason_) {
184 exit_reason = exit_reason_;
185}
8 186
9QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {} 187const std::string& QtNXWebEngineView::GetLastURL() const {
188 return last_url;
189}
190
191void QtNXWebEngineView::SetLastURL(std::string last_url_) {
192 last_url = std::move(last_url_);
193}
194
195QString QtNXWebEngineView::GetCurrentURL() const {
196 return url_interceptor->GetRequestedURL().toString();
197}
198
199void QtNXWebEngineView::hide() {
200 SetFinished(true);
201 StopInputThread();
202
203 QWidget::hide();
204}
205
206template <HIDButton... T>
207void 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
242template <HIDButton... T>
243void 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
253template <HIDButton... T>
254void 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
264void 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
275void 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
284void QtNXWebEngineView::StopInputThread() {
285 input_thread_running = false;
286 if (input_thread.joinable()) {
287 input_thread.join();
288 }
289}
290
291void 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
316QtWebBrowser::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
11QtWebBrowser::~QtWebBrowser() = default; 323QtWebBrowser::~QtWebBrowser() = default;
324
325void 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
338void 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
19enum class HIDButton : u8;
20
21class InputInterpreter;
15class GMainWindow; 22class GMainWindow;
23class UrlRequestInterceptor;
24
25namespace Core {
26class System;
27}
28
29#ifdef YUZU_USE_QT_WEB_ENGINE
30
31enum class UserAgent {
32 WebApplet,
33 ShopN,
34 LoginApplet,
35 ShareApplet,
36 LobbyApplet,
37 WifiWebAuthApplet,
38};
39
40class QtNXWebEngineView : public QWebEngineView {
41 Q_OBJECT
42
43public:
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
86public slots:
87 void hide();
88
89private:
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
17class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { 158class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
18 Q_OBJECT 159 Q_OBJECT
19 160
20public: 161public:
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
168signals:
169 void MainWindowOpenLocalWebPage(std::string_view main_url,
170 std::string_view additional_args) const;
171
172private:
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
183static 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
185GMainWindow::GMainWindow() 207GMainWindow::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
369void 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
344void GMainWindow::InitializeWidgets() { 449void 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 {
55class InputSubsystem; 55class InputSubsystem;
56} 56}
57 57
58namespace Service::AM::Applets {
59enum class WebExitReason : u32;
60}
61
58enum class EmulatedDirectoryTarget { 62enum 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
129public slots: 135public 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
140private: 147private: