yuzu-android/src/yuzu/applets/qt_web_browser.cpp
Kyle Kienapfel 31c6ba7ecd tweak API usage in qt_web_browser.cpp
In testing future versions of Qt I forgot to compile with `YUZU_USE_QT_WEB_ENGINE`, so with that flag enabled there are two issues that cropped up.

1. yuzu currently uses setRequestInterceptor, added in Qt 5.6, deprecated in 5.13 with this explaination at https://doc.qt.io/qt-5/qwebengineprofile-obsolete.html
Interceptors installed with this method will call QWebEngineUrlRequestInterceptor::interceptRequest on the I/O thread. Therefore the user has to provide thread-safe interaction with the other user classes. For a duration of this call ui thread is blocked. Use setUrlRequestInterceptor instead.

2. QWebEngineSettings::globalSettings() pointer no longer exists in later versions of Qt

From what I can tell, QtNXWebEngineView doesn't need to set these globally,
when we make changes to settings(), QtWebEngineView::page() creates the page
object if it doesn't exist yet. I don't see the page object being destroyed
or otherwise replaced, except via destroying the QtNXWebEngineView object.

The globalSettings() make sense if Pages or Views objects are being
created outside of yuzu's control.

To test this I've compared what BrowseNX and Odyssey's Action guide do in mainline 1049 and this PR.

For now we're going to go up the chain to QWebEngineProfile::defaultProfile()->settings()
2022-06-21 17:48:17 -07:00

438 lines
15 KiB
C++

// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef YUZU_USE_QT_WEB_ENGINE
#include <QApplication>
#include <QKeyEvent>
#include <QWebEngineProfile>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
#include <QWebEngineSettings>
#include <QWebEngineUrlScheme>
#include "core/hid/input_interpreter.h"
#include "yuzu/applets/qt_web_browser_scripts.h"
#endif
#include "common/fs/path_util.h"
#include "core/core.h"
#include "input_common/drivers/keyboard.h"
#include "yuzu/applets/qt_web_browser.h"
#include "yuzu/main.h"
#include "yuzu/util/url_request_interceptor.h"
#ifdef YUZU_USE_QT_WEB_ENGINE
namespace {
constexpr int HIDButtonToKey(Core::HID::NpadButton button) {
switch (button) {
case Core::HID::NpadButton::Left:
case Core::HID::NpadButton::StickLLeft:
return Qt::Key_Left;
case Core::HID::NpadButton::Up:
case Core::HID::NpadButton::StickLUp:
return Qt::Key_Up;
case Core::HID::NpadButton::Right:
case Core::HID::NpadButton::StickLRight:
return Qt::Key_Right;
case Core::HID::NpadButton::Down:
case Core::HID::NpadButton::StickLDown:
return Qt::Key_Down;
default:
return 0;
}
}
} // Anonymous namespace
QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
InputCommon::InputSubsystem* input_subsystem_)
: QWebEngineView(parent), input_subsystem{input_subsystem_},
url_interceptor(std::make_unique<UrlRequestInterceptor>()),
input_interpreter(std::make_unique<InputInterpreter>(system)),
default_profile{QWebEngineProfile::defaultProfile()}, global_settings{
default_profile->settings()} {
default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String(
Common::FS::GetYuzuPath(Common::FS::YuzuPath::YuzuDir) / "qtwebengine")));
QWebEngineScript gamepad;
QWebEngineScript window_nx;
gamepad.setName(QStringLiteral("gamepad_script.js"));
window_nx.setName(QStringLiteral("window_nx_script.js"));
gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT));
window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
gamepad.setWorldId(QWebEngineScript::MainWorld);
window_nx.setWorldId(QWebEngineScript::MainWorld);
gamepad.setRunsOnSubFrames(true);
window_nx.setRunsOnSubFrames(true);
default_profile->scripts()->insert(gamepad);
default_profile->scripts()->insert(window_nx);
default_profile->setUrlRequestInterceptor(url_interceptor.get());
global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true);
global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false);
global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto"));
connect(
page(), &QWebEnginePage::windowCloseRequested, page(),
[this] {
if (page()->url() == url_interceptor->GetRequestedURL()) {
SetFinished(true);
SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed);
}
},
Qt::QueuedConnection);
}
QtNXWebEngineView::~QtNXWebEngineView() {
SetFinished(true);
StopInputThread();
}
void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url,
const std::string& additional_args) {
is_local = true;
LoadExtractedFonts();
FocusFirstLinkElement();
SetUserAgent(UserAgent::WebApplet);
SetFinished(false);
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
SetLastURL("http://localhost/");
StartInputThread();
load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() +
QString::fromStdString(additional_args)));
}
void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url,
const std::string& additional_args) {
is_local = false;
FocusFirstLinkElement();
SetUserAgent(UserAgent::WebApplet);
SetFinished(false);
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
SetLastURL("http://localhost/");
StartInputThread();
load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args)));
}
void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
const QString user_agent_str = [user_agent] {
switch (user_agent) {
case UserAgent::WebApplet:
default:
return QStringLiteral("WebApplet");
case UserAgent::ShopN:
return QStringLiteral("ShopN");
case UserAgent::LoginApplet:
return QStringLiteral("LoginApplet");
case UserAgent::ShareApplet:
return QStringLiteral("ShareApplet");
case UserAgent::LobbyApplet:
return QStringLiteral("LobbyApplet");
case UserAgent::WifiWebAuthApplet:
return QStringLiteral("WifiWebAuthApplet");
}
}();
QWebEngineProfile::defaultProfile()->setHttpUserAgent(
QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
"(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
.arg(user_agent_str));
}
bool QtNXWebEngineView::IsFinished() const {
return finished;
}
void QtNXWebEngineView::SetFinished(bool finished_) {
finished = finished_;
}
Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const {
return exit_reason;
}
void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) {
exit_reason = exit_reason_;
}
const std::string& QtNXWebEngineView::GetLastURL() const {
return last_url;
}
void QtNXWebEngineView::SetLastURL(std::string last_url_) {
last_url = std::move(last_url_);
}
QString QtNXWebEngineView::GetCurrentURL() const {
return url_interceptor->GetRequestedURL().toString();
}
void QtNXWebEngineView::hide() {
SetFinished(true);
StopInputThread();
QWidget::hide();
}
void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
if (is_local) {
input_subsystem->GetKeyboard()->PressKey(event->key());
}
}
void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
if (is_local) {
input_subsystem->GetKeyboard()->ReleaseKey(event->key());
}
}
template <Core::HID::NpadButton... T>
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
const auto f = [this](Core::HID::NpadButton button) {
if (input_interpreter->IsButtonPressedOnce(button)) {
page()->runJavaScript(
QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)),
[this, button](const QVariant& variant) {
if (variant.toBool()) {
switch (button) {
case Core::HID::NpadButton::A:
SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
break;
case Core::HID::NpadButton::B:
SendKeyPressEvent(Qt::Key_B);
break;
case Core::HID::NpadButton::X:
SendKeyPressEvent(Qt::Key_X);
break;
case Core::HID::NpadButton::Y:
SendKeyPressEvent(Qt::Key_Y);
break;
default:
break;
}
}
});
page()->runJavaScript(
QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
.arg(static_cast<u8>(button)));
}
};
(f(T), ...);
}
template <Core::HID::NpadButton... T>
void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
const auto f = [this](Core::HID::NpadButton button) {
if (input_interpreter->IsButtonPressedOnce(button)) {
SendKeyPressEvent(HIDButtonToKey(button));
}
};
(f(T), ...);
}
template <Core::HID::NpadButton... T>
void QtNXWebEngineView::HandleWindowKeyButtonHold() {
const auto f = [this](Core::HID::NpadButton button) {
if (input_interpreter->IsButtonHeld(button)) {
SendKeyPressEvent(HIDButtonToKey(button));
}
};
(f(T), ...);
}
void QtNXWebEngineView::SendKeyPressEvent(int key) {
if (key == 0) {
return;
}
QCoreApplication::postEvent(focusProxy(),
new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier));
QCoreApplication::postEvent(focusProxy(),
new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier));
}
void QtNXWebEngineView::StartInputThread() {
if (input_thread_running) {
return;
}
input_thread_running = true;
input_thread = std::thread(&QtNXWebEngineView::InputThread, this);
}
void QtNXWebEngineView::StopInputThread() {
if (is_local) {
QWidget::releaseKeyboard();
}
input_thread_running = false;
if (input_thread.joinable()) {
input_thread.join();
}
}
void QtNXWebEngineView::InputThread() {
// Wait for 1 second before allowing any inputs to be processed.
std::this_thread::sleep_for(std::chrono::seconds(1));
if (is_local) {
QWidget::grabKeyboard();
}
while (input_thread_running) {
input_interpreter->PollInput();
HandleWindowFooterButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
HandleWindowKeyButtonPressedOnce<
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown>();
HandleWindowKeyButtonHold<
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void QtNXWebEngineView::LoadExtractedFonts() {
QWebEngineScript nx_font_css;
QWebEngineScript load_nx_font;
auto fonts_dir_str = Common::FS::PathToUTF8String(
Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/");
std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/');
const auto fonts_dir = QString::fromStdString(fonts_dir_str);
nx_font_css.setName(QStringLiteral("nx_font_css.js"));
load_nx_font.setName(QStringLiteral("load_nx_font.js"));
nx_font_css.setSourceCode(
QString::fromStdString(NX_FONT_CSS)
.arg(fonts_dir + QStringLiteral("FontStandard.ttf"))
.arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf"))
.arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf"))
.arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf"))
.arg(fonts_dir + QStringLiteral("FontKorean.ttf"))
.arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf"))
.arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf")));
load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT));
nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
nx_font_css.setWorldId(QWebEngineScript::MainWorld);
load_nx_font.setWorldId(QWebEngineScript::MainWorld);
nx_font_css.setRunsOnSubFrames(true);
load_nx_font.setRunsOnSubFrames(true);
default_profile->scripts()->insert(nx_font_css);
default_profile->scripts()->insert(load_nx_font);
connect(
url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(),
[this] {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT));
},
Qt::QueuedConnection);
}
void QtNXWebEngineView::FocusFirstLinkElement() {
QWebEngineScript focus_link_element;
focus_link_element.setName(QStringLiteral("focus_link_element.js"));
focus_link_element.setSourceCode(QString::fromStdString(FOCUS_LINK_ELEMENT_SCRIPT));
focus_link_element.setWorldId(QWebEngineScript::MainWorld);
focus_link_element.setInjectionPoint(QWebEngineScript::Deferred);
focus_link_element.setRunsOnSubFrames(true);
default_profile->scripts()->insert(focus_link_element);
}
#endif
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
&GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
}
QtWebBrowser::~QtWebBrowser() = default;
void QtWebBrowser::OpenLocalWebPage(
const std::string& local_url, std::function<void()> extract_romfs_callback_,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
extract_romfs_callback = std::move(extract_romfs_callback_);
callback = std::move(callback_);
const auto index = local_url.find('?');
if (index == std::string::npos) {
emit MainWindowOpenWebPage(local_url, "", true);
} else {
emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
}
}
void QtWebBrowser::OpenExternalWebPage(
const std::string& external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
callback = std::move(callback_);
const auto index = external_url.find('?');
if (index == std::string::npos) {
emit MainWindowOpenWebPage(external_url, "", false);
} else {
emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
false);
}
}
void QtWebBrowser::MainWindowExtractOfflineRomFS() {
extract_romfs_callback();
}
void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
std::string last_url) {
callback(exit_reason, last_url);
}