applets/web: Implement the Qt web browser applet frontend
This commit is contained in:
parent
d5e0923e3d
commit
93cb783853
|
@ -2,10 +2,339 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "core/hle/lock.h"
|
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||||
#include "yuzu/applets/web_browser.h"
|
#include <QKeyEvent>
|
||||||
#include "yuzu/main.h"
|
|
||||||
|
|
||||||
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {}
|
#include <QWebEngineProfile>
|
||||||
|
#include <QWebEngineScript>
|
||||||
|
#include <QWebEngineScriptCollection>
|
||||||
|
#include <QWebEngineSettings>
|
||||||
|
#include <QWebEngineUrlScheme>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/frontend/input_interpreter.h"
|
||||||
|
#include "yuzu/applets/web_browser.h"
|
||||||
|
#include "yuzu/applets/web_browser_scripts.h"
|
||||||
|
#include "yuzu/main.h"
|
||||||
|
#include "yuzu/util/url_request_interceptor.h"
|
||||||
|
|
||||||
|
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int HIDButtonToKey(HIDButton button) {
|
||||||
|
switch (button) {
|
||||||
|
case HIDButton::DLeft:
|
||||||
|
case HIDButton::LStickLeft:
|
||||||
|
return Qt::Key_Left;
|
||||||
|
case HIDButton::DUp:
|
||||||
|
case HIDButton::LStickUp:
|
||||||
|
return Qt::Key_Up;
|
||||||
|
case HIDButton::DRight:
|
||||||
|
case HIDButton::LStickRight:
|
||||||
|
return Qt::Key_Right;
|
||||||
|
case HIDButton::DDown:
|
||||||
|
case HIDButton::LStickDown:
|
||||||
|
return Qt::Key_Down;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system)
|
||||||
|
: QWebEngineView(parent), url_interceptor(std::make_unique<UrlRequestInterceptor>()),
|
||||||
|
input_interpreter(std::make_unique<InputInterpreter>(system)) {
|
||||||
|
QWebEngineScript nx_font_css;
|
||||||
|
QWebEngineScript load_nx_font;
|
||||||
|
QWebEngineScript gamepad;
|
||||||
|
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"));
|
||||||
|
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));
|
||||||
|
window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
|
||||||
|
|
||||||
|
nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
|
||||||
|
load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
|
||||||
|
gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||||
|
window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||||
|
|
||||||
|
nx_font_css.setWorldId(QWebEngineScript::MainWorld);
|
||||||
|
load_nx_font.setWorldId(QWebEngineScript::MainWorld);
|
||||||
|
gamepad.setWorldId(QWebEngineScript::MainWorld);
|
||||||
|
window_nx.setWorldId(QWebEngineScript::MainWorld);
|
||||||
|
|
||||||
|
nx_font_css.setRunsOnSubFrames(true);
|
||||||
|
load_nx_font.setRunsOnSubFrames(true);
|
||||||
|
gamepad.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(window_nx);
|
||||||
|
|
||||||
|
default_profile->setRequestInterceptor(url_interceptor.get());
|
||||||
|
|
||||||
|
auto* global_settings = QWebEngineSettings::globalSettings();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
page(), &QWebEnginePage::windowCloseRequested, page(),
|
||||||
|
[this] {
|
||||||
|
if (page()->url() == url_interceptor->GetRequestedURL()) {
|
||||||
|
SetFinished(true);
|
||||||
|
SetExitReason(WebExitReason::WindowClosed);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
QtNXWebEngineView::~QtNXWebEngineView() {
|
||||||
|
SetFinished(true);
|
||||||
|
StopInputThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url,
|
||||||
|
std::string_view additional_args) {
|
||||||
|
SetUserAgent(UserAgent::WebApplet);
|
||||||
|
SetFinished(false);
|
||||||
|
SetExitReason(WebExitReason::EndButtonPressed);
|
||||||
|
SetLastURL("http://localhost/");
|
||||||
|
StartInputThread();
|
||||||
|
|
||||||
|
load(QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(main_url))).toString() +
|
||||||
|
QString::fromStdString(std::string(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_;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebExitReason QtNXWebEngineView::GetExitReason() const {
|
||||||
|
return exit_reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtNXWebEngineView::SetExitReason(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <HIDButton... T>
|
||||||
|
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
|
||||||
|
const auto f = [this](HIDButton button) {
|
||||||
|
if (input_interpreter->IsButtonPressedOnce(button)) {
|
||||||
|
page()->runJavaScript(
|
||||||
|
QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)),
|
||||||
|
[&](const QVariant& variant) {
|
||||||
|
if (variant.toBool()) {
|
||||||
|
switch (button) {
|
||||||
|
case HIDButton::A:
|
||||||
|
SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
|
||||||
|
break;
|
||||||
|
case HIDButton::B:
|
||||||
|
SendKeyPressEvent(Qt::Key_B);
|
||||||
|
break;
|
||||||
|
case HIDButton::X:
|
||||||
|
SendKeyPressEvent(Qt::Key_X);
|
||||||
|
break;
|
||||||
|
case HIDButton::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 <HIDButton... T>
|
||||||
|
void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
|
||||||
|
const auto f = [this](HIDButton button) {
|
||||||
|
if (input_interpreter->IsButtonPressedOnce(button)) {
|
||||||
|
SendKeyPressEvent(HIDButtonToKey(button));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(f(T), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <HIDButton... T>
|
||||||
|
void QtNXWebEngineView::HandleWindowKeyButtonHold() {
|
||||||
|
const auto f = [this](HIDButton 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() {
|
||||||
|
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));
|
||||||
|
|
||||||
|
while (input_thread_running) {
|
||||||
|
input_interpreter->PollInput();
|
||||||
|
|
||||||
|
HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y,
|
||||||
|
HIDButton::L, HIDButton::R>();
|
||||||
|
|
||||||
|
HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
|
||||||
|
HIDButton::DDown, HIDButton::LStickLeft,
|
||||||
|
HIDButton::LStickUp, HIDButton::LStickRight,
|
||||||
|
HIDButton::LStickDown>();
|
||||||
|
|
||||||
|
HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
|
||||||
|
HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp,
|
||||||
|
HIDButton::LStickRight, HIDButton::LStickDown>();
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
|
||||||
|
connect(this, &QtWebBrowser::MainWindowOpenLocalWebPage, &main_window,
|
||||||
|
&GMainWindow::WebBrowserOpenLocalWebPage, Qt::QueuedConnection);
|
||||||
|
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
|
||||||
|
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
QtWebBrowser::~QtWebBrowser() = default;
|
QtWebBrowser::~QtWebBrowser() = default;
|
||||||
|
|
||||||
|
void QtWebBrowser::OpenLocalWebPage(
|
||||||
|
std::string_view local_url, std::function<void(WebExitReason, std::string)> callback) const {
|
||||||
|
this->callback = std::move(callback);
|
||||||
|
|
||||||
|
const auto index = local_url.find('?');
|
||||||
|
|
||||||
|
if (index == std::string::npos) {
|
||||||
|
emit MainWindowOpenLocalWebPage(local_url, "");
|
||||||
|
} else {
|
||||||
|
emit MainWindowOpenLocalWebPage(local_url.substr(0, index), local_url.substr(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtWebBrowser::MainWindowWebBrowserClosed(WebExitReason exit_reason, std::string last_url) {
|
||||||
|
callback(exit_reason, last_url);
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||||
|
@ -12,12 +16,161 @@
|
||||||
|
|
||||||
#include "core/frontend/applets/web_browser.h"
|
#include "core/frontend/applets/web_browser.h"
|
||||||
|
|
||||||
|
enum class HIDButton : u8;
|
||||||
|
|
||||||
|
class InputInterpreter;
|
||||||
class GMainWindow;
|
class GMainWindow;
|
||||||
|
class UrlRequestInterceptor;
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||||
|
|
||||||
|
enum class UserAgent {
|
||||||
|
WebApplet,
|
||||||
|
ShopN,
|
||||||
|
LoginApplet,
|
||||||
|
ShareApplet,
|
||||||
|
LobbyApplet,
|
||||||
|
WifiWebAuthApplet,
|
||||||
|
};
|
||||||
|
|
||||||
|
class QtNXWebEngineView : public QWebEngineView {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QtNXWebEngineView(QWidget* parent, Core::System& system);
|
||||||
|
~QtNXWebEngineView() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a HTML document that exists locally. Cannot be used to load external websites.
|
||||||
|
*
|
||||||
|
* @param main_url The url to the file.
|
||||||
|
* @param additional_args Additional arguments appended to the main url.
|
||||||
|
*/
|
||||||
|
void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the background color of the web page.
|
||||||
|
*
|
||||||
|
* @param color The color to set.
|
||||||
|
*/
|
||||||
|
void SetBackgroundColor(QColor color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the user agent of the web browser.
|
||||||
|
*
|
||||||
|
* @param user_agent The user agent enum.
|
||||||
|
*/
|
||||||
|
void SetUserAgent(UserAgent user_agent);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsFinished() const;
|
||||||
|
void SetFinished(bool finished_);
|
||||||
|
|
||||||
|
[[nodiscard]] WebExitReason GetExitReason() const;
|
||||||
|
void SetExitReason(WebExitReason exit_reason_);
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string& GetLastURL() const;
|
||||||
|
void SetLastURL(std::string last_url_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This gets the current URL that has been requested by the webpage.
|
||||||
|
* This only applies to the main frame. Sub frames and other resources are ignored.
|
||||||
|
*
|
||||||
|
* @return Currently requested URL
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString GetCurrentURL() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void hide();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Handles button presses to execute functions assigned in yuzu_key_callbacks.
|
||||||
|
* yuzu_key_callbacks contains specialized functions for the buttons in the window footer
|
||||||
|
* that can be overriden by games to achieve desired functionality.
|
||||||
|
*
|
||||||
|
* @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
|
||||||
|
*/
|
||||||
|
template <HIDButton... T>
|
||||||
|
void HandleWindowFooterButtonPressedOnce();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles button presses and converts them into keyboard input.
|
||||||
|
* This should only be used to convert D-Pad or Analog Stick input into arrow keys.
|
||||||
|
*
|
||||||
|
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||||
|
*/
|
||||||
|
template <HIDButton... T>
|
||||||
|
void HandleWindowKeyButtonPressedOnce();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles button holds and converts them into keyboard input.
|
||||||
|
* This should only be used to convert D-Pad or Analog Stick input into arrow keys.
|
||||||
|
*
|
||||||
|
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||||
|
*/
|
||||||
|
template <HIDButton... T>
|
||||||
|
void HandleWindowKeyButtonHold();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a key press event to QWebEngineView.
|
||||||
|
*
|
||||||
|
* @param key Qt key code.
|
||||||
|
*/
|
||||||
|
void SendKeyPressEvent(int key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends multiple key press events to QWebEngineView.
|
||||||
|
*
|
||||||
|
* @tparam int Qt key code.
|
||||||
|
*/
|
||||||
|
template <int... T>
|
||||||
|
void SendMultipleKeyPressEvents() {
|
||||||
|
(SendKeyPressEvent(T), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartInputThread();
|
||||||
|
void StopInputThread();
|
||||||
|
|
||||||
|
/// The thread where input is being polled and processed.
|
||||||
|
void InputThread();
|
||||||
|
|
||||||
|
std::unique_ptr<UrlRequestInterceptor> url_interceptor;
|
||||||
|
|
||||||
|
std::unique_ptr<InputInterpreter> input_interpreter;
|
||||||
|
|
||||||
|
std::thread input_thread;
|
||||||
|
|
||||||
|
std::atomic<bool> input_thread_running{};
|
||||||
|
|
||||||
|
std::atomic<bool> finished{};
|
||||||
|
|
||||||
|
WebExitReason exit_reason{WebExitReason::EndButtonPressed};
|
||||||
|
|
||||||
|
std::string last_url{"http://localhost/"};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
|
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit QtWebBrowser(GMainWindow& main_window);
|
explicit QtWebBrowser(GMainWindow& parent);
|
||||||
~QtWebBrowser() override;
|
~QtWebBrowser() override;
|
||||||
|
|
||||||
|
void OpenLocalWebPage(std::string_view local_url,
|
||||||
|
std::function<void(WebExitReason, std::string)> callback) const override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void MainWindowOpenLocalWebPage(std::string_view main_url,
|
||||||
|
std::string_view additional_args) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void MainWindowWebBrowserClosed(WebExitReason exit_reason, std::string last_url);
|
||||||
|
|
||||||
|
mutable std::function<void(WebExitReason, std::string)> callback;
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,8 +28,6 @@
|
||||||
#include "core/hle/service/am/applet_ae.h"
|
#include "core/hle/service/am/applet_ae.h"
|
||||||
#include "core/hle/service/am/applet_oe.h"
|
#include "core/hle/service/am/applet_oe.h"
|
||||||
#include "core/hle/service/am/applets/applets.h"
|
#include "core/hle/service/am/applets/applets.h"
|
||||||
#include "core/hle/service/hid/controllers/npad.h"
|
|
||||||
#include "core/hle/service/hid/hid.h"
|
|
||||||
|
|
||||||
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
|
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
|
||||||
// defines.
|
// defines.
|
||||||
|
@ -182,6 +180,30 @@ static void InitializeLogging() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void RemoveCachedContents() {
|
||||||
|
const auto offline_fonts = Common::FS::SanitizePath(
|
||||||
|
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
|
||||||
|
Common::FS::DirectorySeparator::PlatformDefault);
|
||||||
|
|
||||||
|
const auto offline_manual = Common::FS::SanitizePath(
|
||||||
|
fmt::format("{}/offline_web_applet_manual",
|
||||||
|
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
|
||||||
|
Common::FS::DirectorySeparator::PlatformDefault);
|
||||||
|
const auto offline_legal_information = Common::FS::SanitizePath(
|
||||||
|
fmt::format("{}/offline_web_applet_legal_information",
|
||||||
|
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
|
||||||
|
Common::FS::DirectorySeparator::PlatformDefault);
|
||||||
|
const auto offline_system_data = Common::FS::SanitizePath(
|
||||||
|
fmt::format("{}/offline_web_applet_system_data",
|
||||||
|
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
|
||||||
|
Common::FS::DirectorySeparator::PlatformDefault);
|
||||||
|
|
||||||
|
Common::FS::DeleteDirRecursively(offline_fonts);
|
||||||
|
Common::FS::DeleteDirRecursively(offline_manual);
|
||||||
|
Common::FS::DeleteDirRecursively(offline_legal_information);
|
||||||
|
Common::FS::DeleteDirRecursively(offline_system_data);
|
||||||
|
}
|
||||||
|
|
||||||
GMainWindow::GMainWindow()
|
GMainWindow::GMainWindow()
|
||||||
: input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
|
: input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
|
||||||
config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
|
config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
|
||||||
|
@ -250,6 +272,9 @@ GMainWindow::GMainWindow()
|
||||||
FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
|
FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
|
||||||
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
|
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
|
||||||
|
|
||||||
|
// Remove cached contents generated during the previous session
|
||||||
|
RemoveCachedContents();
|
||||||
|
|
||||||
// Gen keys if necessary
|
// Gen keys if necessary
|
||||||
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
|
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
|
||||||
|
|
||||||
|
@ -341,6 +366,86 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
|
||||||
emit SoftwareKeyboardFinishedCheckDialog();
|
emit SoftwareKeyboardFinishedCheckDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::WebBrowserOpenLocalWebPage(std::string_view main_url,
|
||||||
|
std::string_view additional_args) {
|
||||||
|
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||||
|
|
||||||
|
QtNXWebEngineView web_browser_view(this, Core::System::GetInstance());
|
||||||
|
|
||||||
|
web_browser_view.LoadLocalWebPage(main_url, additional_args);
|
||||||
|
|
||||||
|
ui.action_Pause->setEnabled(false);
|
||||||
|
ui.action_Restart->setEnabled(false);
|
||||||
|
ui.action_Stop->setEnabled(false);
|
||||||
|
|
||||||
|
if (render_window->IsLoadingComplete()) {
|
||||||
|
render_window->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& layout = render_window->GetFramebufferLayout();
|
||||||
|
web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight());
|
||||||
|
web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height());
|
||||||
|
web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) /
|
||||||
|
static_cast<qreal>(Layout::ScreenUndocked::Width));
|
||||||
|
|
||||||
|
web_browser_view.setFocus();
|
||||||
|
web_browser_view.show();
|
||||||
|
|
||||||
|
bool exit_check = false;
|
||||||
|
|
||||||
|
while (!web_browser_view.IsFinished()) {
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
if (!exit_check) {
|
||||||
|
web_browser_view.page()->runJavaScript(
|
||||||
|
QStringLiteral("end_applet;"), [&](const QVariant& variant) {
|
||||||
|
exit_check = false;
|
||||||
|
if (variant.toBool()) {
|
||||||
|
web_browser_view.SetFinished(true);
|
||||||
|
web_browser_view.SetExitReason(WebExitReason::EndButtonPressed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exit_check = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) {
|
||||||
|
if (!web_browser_view.IsFinished()) {
|
||||||
|
web_browser_view.SetFinished(true);
|
||||||
|
web_browser_view.SetExitReason(WebExitReason::CallbackURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto exit_reason = web_browser_view.GetExitReason();
|
||||||
|
const auto last_url = web_browser_view.GetLastURL();
|
||||||
|
|
||||||
|
web_browser_view.hide();
|
||||||
|
|
||||||
|
render_window->setFocus();
|
||||||
|
|
||||||
|
if (render_window->IsLoadingComplete()) {
|
||||||
|
render_window->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.action_Pause->setEnabled(true);
|
||||||
|
ui.action_Restart->setEnabled(true);
|
||||||
|
ui.action_Stop->setEnabled(true);
|
||||||
|
|
||||||
|
emit WebBrowserClosed(exit_reason, last_url);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// Utilize the same fallback as the default web browser applet.
|
||||||
|
emit WebBrowserClosed(WebExitReason::WindowClosed, "http://localhost");
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::InitializeWidgets() {
|
void GMainWindow::InitializeWidgets() {
|
||||||
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
|
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
|
||||||
ui.action_Report_Compatibility->setVisible(true);
|
ui.action_Report_Compatibility->setVisible(true);
|
||||||
|
@ -1948,6 +2053,7 @@ void GMainWindow::OnStartGame() {
|
||||||
qRegisterMetaType<std::string>("std::string");
|
qRegisterMetaType<std::string>("std::string");
|
||||||
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
|
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
|
||||||
qRegisterMetaType<std::string_view>("std::string_view");
|
qRegisterMetaType<std::string_view>("std::string_view");
|
||||||
|
qRegisterMetaType<Service::AM::Applets::WebExitReason>("Service::AM::Applets::WebExitReason");
|
||||||
|
|
||||||
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
|
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,10 @@ namespace InputCommon {
|
||||||
class InputSubsystem;
|
class InputSubsystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Service::AM::Applets {
|
||||||
|
enum class WebExitReason : u32;
|
||||||
|
}
|
||||||
|
|
||||||
enum class EmulatedDirectoryTarget {
|
enum class EmulatedDirectoryTarget {
|
||||||
NAND,
|
NAND,
|
||||||
SDMC,
|
SDMC,
|
||||||
|
@ -126,6 +130,8 @@ signals:
|
||||||
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
|
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
|
||||||
void SoftwareKeyboardFinishedCheckDialog();
|
void SoftwareKeyboardFinishedCheckDialog();
|
||||||
|
|
||||||
|
void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void OnLoadComplete();
|
void OnLoadComplete();
|
||||||
void OnExecuteProgram(std::size_t program_index);
|
void OnExecuteProgram(std::size_t program_index);
|
||||||
|
@ -135,6 +141,7 @@ 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 OnAppFocusStateChanged(Qt::ApplicationState state);
|
void OnAppFocusStateChanged(Qt::ApplicationState state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Reference in New Issue