applets/web: Implement the online web browser applet

This commit is contained in:
Morph 2020-12-08 06:20:45 -05:00
parent 51cddcb8b8
commit 82fa9f8d56
8 changed files with 167 additions and 64 deletions

View file

@ -20,4 +20,13 @@ void DefaultWebBrowserApplet::OpenLocalWebPage(
callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
} }
void DefaultWebBrowserApplet::OpenExternalWebPage(
std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const {
LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open external web page at {}",
external_url);
callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
}
} // namespace Core::Frontend } // namespace Core::Frontend

View file

@ -18,6 +18,10 @@ public:
virtual void OpenLocalWebPage( virtual void OpenLocalWebPage(
std::string_view local_url, std::function<void()> extract_romfs_callback, std::string_view local_url, std::function<void()> extract_romfs_callback,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0; std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
virtual void OpenExternalWebPage(
std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
}; };
class DefaultWebBrowserApplet final : public WebBrowserApplet { class DefaultWebBrowserApplet final : public WebBrowserApplet {
@ -27,6 +31,10 @@ public:
void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback, void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback) const override; callback) const override;
void OpenExternalWebPage(std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback) const override;
}; };
} // namespace Core::Frontend } // namespace Core::Frontend

View file

@ -409,7 +409,9 @@ void WebBrowser::InitializeOffline() {
void WebBrowser::InitializeShare() {} void WebBrowser::InitializeShare() {}
void WebBrowser::InitializeWeb() {} void WebBrowser::InitializeWeb() {
external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value());
}
void WebBrowser::InitializeWifi() {} void WebBrowser::InitializeWifi() {}
@ -456,8 +458,12 @@ void WebBrowser::ExecuteShare() {
} }
void WebBrowser::ExecuteWeb() { void WebBrowser::ExecuteWeb() {
LOG_WARNING(Service_AM, "(STUBBED) called, Web Applet is not implemented"); LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
WebBrowserExit(WebExitReason::EndButtonPressed);
frontend.OpenExternalWebPage(external_url,
[this](WebExitReason exit_reason, std::string last_url) {
WebBrowserExit(exit_reason, last_url);
});
} }
void WebBrowser::ExecuteWifi() { void WebBrowser::ExecuteWifi() {

View file

@ -79,6 +79,8 @@ private:
std::string offline_document; std::string offline_document;
FileSys::VirtualFile offline_romfs; FileSys::VirtualFile offline_romfs;
std::string external_url;
Core::System& system; Core::System& system;
}; };

View file

@ -51,59 +51,32 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
InputCommon::InputSubsystem* input_subsystem_) InputCommon::InputSubsystem* input_subsystem_)
: QWebEngineView(parent), input_subsystem{input_subsystem_}, : QWebEngineView(parent), input_subsystem{input_subsystem_},
url_interceptor(std::make_unique<UrlRequestInterceptor>()), url_interceptor(std::make_unique<UrlRequestInterceptor>()),
input_interpreter(std::make_unique<InputInterpreter>(system)) { input_interpreter(std::make_unique<InputInterpreter>(system)),
QWebEngineScript nx_font_css; default_profile{QWebEngineProfile::defaultProfile()},
QWebEngineScript load_nx_font; global_settings{QWebEngineSettings::globalSettings()} {
QWebEngineScript gamepad; QWebEngineScript gamepad;
QWebEngineScript window_nx; QWebEngineScript window_nx;
const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath(
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir))));
nx_font_css.setName(QStringLiteral("nx_font_css.js"));
load_nx_font.setName(QStringLiteral("load_nx_font.js"));
gamepad.setName(QStringLiteral("gamepad_script.js")); gamepad.setName(QStringLiteral("gamepad_script.js"));
window_nx.setName(QStringLiteral("window_nx_script.js")); window_nx.setName(QStringLiteral("window_nx_script.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));
gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT));
window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
nx_font_css.setWorldId(QWebEngineScript::MainWorld);
load_nx_font.setWorldId(QWebEngineScript::MainWorld);
gamepad.setWorldId(QWebEngineScript::MainWorld); gamepad.setWorldId(QWebEngineScript::MainWorld);
window_nx.setWorldId(QWebEngineScript::MainWorld); window_nx.setWorldId(QWebEngineScript::MainWorld);
nx_font_css.setRunsOnSubFrames(true);
load_nx_font.setRunsOnSubFrames(true);
gamepad.setRunsOnSubFrames(true); gamepad.setRunsOnSubFrames(true);
window_nx.setRunsOnSubFrames(true); window_nx.setRunsOnSubFrames(true);
auto* default_profile = QWebEngineProfile::defaultProfile();
default_profile->scripts()->insert(nx_font_css);
default_profile->scripts()->insert(load_nx_font);
default_profile->scripts()->insert(gamepad); default_profile->scripts()->insert(gamepad);
default_profile->scripts()->insert(window_nx); default_profile->scripts()->insert(window_nx);
default_profile->setRequestInterceptor(url_interceptor.get()); default_profile->setRequestInterceptor(url_interceptor.get());
auto* global_settings = QWebEngineSettings::globalSettings();
global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
@ -111,13 +84,7 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true);
global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false);
connect( global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto"));
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);
connect( connect(
page(), &QWebEnginePage::windowCloseRequested, page(), page(), &QWebEnginePage::windowCloseRequested, page(),
@ -137,6 +104,9 @@ QtNXWebEngineView::~QtNXWebEngineView() {
void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url, void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url,
std::string_view additional_args) { std::string_view additional_args) {
is_local = true;
LoadExtractedFonts();
SetUserAgent(UserAgent::WebApplet); SetUserAgent(UserAgent::WebApplet);
SetFinished(false); SetFinished(false);
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
@ -147,6 +117,20 @@ void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url,
QString::fromStdString(std::string(additional_args)))); QString::fromStdString(std::string(additional_args))));
} }
void QtNXWebEngineView::LoadExternalWebPage(std::string_view main_url,
std::string_view additional_args) {
is_local = false;
SetUserAgent(UserAgent::WebApplet);
SetFinished(false);
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
SetLastURL("http://localhost/");
StartInputThread();
load(QUrl(QString::fromStdString(std::string(main_url)) +
QString::fromStdString(std::string(additional_args))));
}
void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
const QString user_agent_str = [user_agent] { const QString user_agent_str = [user_agent] {
switch (user_agent) { switch (user_agent) {
@ -208,12 +192,16 @@ void QtNXWebEngineView::hide() {
} }
void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) { void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
if (is_local) {
input_subsystem->GetKeyboard()->PressKey(event->key()); input_subsystem->GetKeyboard()->PressKey(event->key());
} }
}
void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
if (is_local) {
input_subsystem->GetKeyboard()->ReleaseKey(event->key()); input_subsystem->GetKeyboard()->ReleaseKey(event->key());
} }
}
template <HIDButton... T> template <HIDButton... T>
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
@ -294,7 +282,10 @@ void QtNXWebEngineView::StartInputThread() {
} }
void QtNXWebEngineView::StopInputThread() { void QtNXWebEngineView::StopInputThread() {
if (is_local) {
QWidget::releaseKeyboard(); QWidget::releaseKeyboard();
}
input_thread_running = false; input_thread_running = false;
if (input_thread.joinable()) { if (input_thread.joinable()) {
input_thread.join(); input_thread.join();
@ -305,7 +296,9 @@ void QtNXWebEngineView::InputThread() {
// Wait for 1 second before allowing any inputs to be processed. // Wait for 1 second before allowing any inputs to be processed.
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
if (is_local) {
QWidget::grabKeyboard(); QWidget::grabKeyboard();
}
while (input_thread_running) { while (input_thread_running) {
input_interpreter->PollInput(); input_interpreter->PollInput();
@ -326,11 +319,53 @@ void QtNXWebEngineView::InputThread() {
} }
} }
void QtNXWebEngineView::LoadExtractedFonts() {
QWebEngineScript nx_font_css;
QWebEngineScript load_nx_font;
const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath(
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir))));
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);
}
#endif #endif
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
connect(this, &QtWebBrowser::MainWindowOpenLocalWebPage, &main_window, connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
&GMainWindow::WebBrowserOpenLocalWebPage, Qt::QueuedConnection); &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserClosed, this, connect(&main_window, &GMainWindow::WebBrowserClosed, this,
@ -340,17 +375,32 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
QtWebBrowser::~QtWebBrowser() = default; QtWebBrowser::~QtWebBrowser() = default;
void QtWebBrowser::OpenLocalWebPage( void QtWebBrowser::OpenLocalWebPage(
std::string_view local_url, std::function<void()> extract_romfs_callback, std::string_view local_url, std::function<void()> extract_romfs_callback_,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const { std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
this->extract_romfs_callback = std::move(extract_romfs_callback); extract_romfs_callback = std::move(extract_romfs_callback_);
this->callback = std::move(callback); callback = std::move(callback_);
const auto index = local_url.find('?'); const auto index = local_url.find('?');
if (index == std::string::npos) { if (index == std::string::npos) {
emit MainWindowOpenLocalWebPage(local_url, ""); emit MainWindowOpenWebPage(local_url, "", true);
} else { } else {
emit MainWindowOpenLocalWebPage(local_url.substr(0, index), local_url.substr(index)); emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
}
}
void QtWebBrowser::OpenExternalWebPage(
std::string_view 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);
} }
} }

View file

@ -18,8 +18,8 @@
enum class HIDButton : u8; enum class HIDButton : u8;
class InputInterpreter;
class GMainWindow; class GMainWindow;
class InputInterpreter;
class UrlRequestInterceptor; class UrlRequestInterceptor;
namespace Core { namespace Core {
@ -41,6 +41,9 @@ enum class UserAgent {
WifiWebAuthApplet, WifiWebAuthApplet,
}; };
class QWebEngineProfile;
class QWebEngineSettings;
class QtNXWebEngineView : public QWebEngineView { class QtNXWebEngineView : public QWebEngineView {
Q_OBJECT Q_OBJECT
@ -57,6 +60,14 @@ public:
*/ */
void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args); void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args);
/**
* Loads an external website. Cannot be used to load local urls.
*
* @param main_url The url to the website.
* @param additional_args Additional arguments appended to the main url.
*/
void LoadExternalWebPage(std::string_view main_url, std::string_view additional_args);
/** /**
* Sets the background color of the web page. * Sets the background color of the web page.
* *
@ -147,6 +158,9 @@ private:
/// The thread where input is being polled and processed. /// The thread where input is being polled and processed.
void InputThread(); void InputThread();
/// Loads the extracted fonts using JavaScript.
void LoadExtractedFonts();
InputCommon::InputSubsystem* input_subsystem; InputCommon::InputSubsystem* input_subsystem;
std::unique_ptr<UrlRequestInterceptor> url_interceptor; std::unique_ptr<UrlRequestInterceptor> url_interceptor;
@ -163,6 +177,11 @@ private:
Service::AM::Applets::WebExitReason::EndButtonPressed}; Service::AM::Applets::WebExitReason::EndButtonPressed};
std::string last_url{"http://localhost/"}; std::string last_url{"http://localhost/"};
bool is_local{};
QWebEngineProfile* default_profile;
QWebEngineSettings* global_settings;
}; };
#endif #endif
@ -174,13 +193,17 @@ public:
explicit QtWebBrowser(GMainWindow& parent); explicit QtWebBrowser(GMainWindow& parent);
~QtWebBrowser() override; ~QtWebBrowser() override;
void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback, void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback_,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback) const override; callback_) const override;
void OpenExternalWebPage(std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback_) const override;
signals: signals:
void MainWindowOpenLocalWebPage(std::string_view main_url, void MainWindowOpenWebPage(std::string_view main_url, std::string_view additional_args,
std::string_view additional_args) const; bool is_local) const;
private: private:
void MainWindowExtractOfflineRomFS(); void MainWindowExtractOfflineRomFS();

View file

@ -366,13 +366,13 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
emit SoftwareKeyboardFinishedCheckDialog(); emit SoftwareKeyboardFinishedCheckDialog();
} }
void GMainWindow::WebBrowserOpenLocalWebPage(std::string_view main_url, void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args,
std::string_view additional_args) { bool is_local) {
#ifdef YUZU_USE_QT_WEB_ENGINE #ifdef YUZU_USE_QT_WEB_ENGINE
if (disable_web_applet) { if (disable_web_applet) {
emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed,
"http://localhost"); "http://localhost/");
return; return;
} }
@ -388,7 +388,7 @@ void GMainWindow::WebBrowserOpenLocalWebPage(std::string_view main_url,
loading_progress.setRange(0, 3); loading_progress.setRange(0, 3);
loading_progress.setValue(0); loading_progress.setValue(0);
if (!Common::FS::Exists(std::string(main_url))) { if (is_local && !Common::FS::Exists(std::string(main_url))) {
loading_progress.show(); loading_progress.show();
auto future = QtConcurrent::run([this] { emit WebBrowserExtractOfflineRomFS(); }); auto future = QtConcurrent::run([this] { emit WebBrowserExtractOfflineRomFS(); });
@ -400,7 +400,11 @@ void GMainWindow::WebBrowserOpenLocalWebPage(std::string_view main_url,
loading_progress.setValue(1); loading_progress.setValue(1);
if (is_local) {
web_browser_view.LoadLocalWebPage(main_url, additional_args); web_browser_view.LoadLocalWebPage(main_url, additional_args);
} else {
web_browser_view.LoadExternalWebPage(main_url, additional_args);
}
if (render_window->IsLoadingComplete()) { if (render_window->IsLoadingComplete()) {
render_window->hide(); render_window->hide();
@ -493,7 +497,7 @@ void GMainWindow::WebBrowserOpenLocalWebPage(std::string_view main_url,
#else #else
// Utilize the same fallback as the default web browser applet. // Utilize the same fallback as the default web browser applet.
emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost"); emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
#endif #endif
} }

View file

@ -142,7 +142,8 @@ public slots:
void ProfileSelectorSelectProfile(); void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
void WebBrowserOpenLocalWebPage(std::string_view main_url, std::string_view additional_args); void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args,
bool is_local);
void OnAppFocusStateChanged(Qt::ApplicationState state); void OnAppFocusStateChanged(Qt::ApplicationState state);
private: private: