diff options
Diffstat (limited to 'src/yuzu')
29 files changed, 1138 insertions, 472 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index b16b54032..e1bab2112 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -141,6 +141,8 @@ add_executable(yuzu util/limitable_input_dialog.h util/sequence_dialog/sequence_dialog.cpp util/sequence_dialog/sequence_dialog.h + util/url_request_interceptor.cpp + util/url_request_interceptor.h util/util.cpp util/util.h compatdb.cpp @@ -217,7 +219,8 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core) target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) -if (ENABLE_VULKAN AND NOT WIN32) +target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) +if (NOT WIN32) target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() @@ -278,8 +281,3 @@ endif() if (NOT APPLE) target_compile_definitions(yuzu PRIVATE HAS_OPENGL) endif() - -if (ENABLE_VULKAN) - target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) - target_compile_definitions(yuzu PRIVATE HAS_VULKAN) -endif() diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index 6944478f3..a15e8ca2a 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -660,8 +660,8 @@ QtControllerSelector::QtControllerSelector(GMainWindow& parent) { QtControllerSelector::~QtControllerSelector() = default; void QtControllerSelector::ReconfigureControllers( - std::function<void()> callback, const Core::Frontend::ControllerParameters& parameters) const { - this->callback = std::move(callback); + std::function<void()> callback_, const Core::Frontend::ControllerParameters& parameters) const { + callback = std::move(callback_); emit MainWindowReconfigureControllers(parameters); } diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h index 7a421d856..3518eed56 100644 --- a/src/yuzu/applets/controller.h +++ b/src/yuzu/applets/controller.h @@ -147,7 +147,7 @@ public: ~QtControllerSelector() override; void ReconfigureControllers( - std::function<void()> callback, + std::function<void()> callback_, const Core::Frontend::ControllerParameters& parameters) const override; signals: diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp index 08ed57355..53a993cf6 100644 --- a/src/yuzu/applets/error.cpp +++ b/src/yuzu/applets/error.cpp @@ -17,7 +17,7 @@ QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { QtErrorDisplay::~QtErrorDisplay() = default; void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); emit MainWindowDisplayError( tr("An error has occured.\nPlease try again or contact the developer of the " "software.\n\nError Code: %1-%2 (0x%3)") @@ -28,7 +28,7 @@ void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); emit MainWindowDisplayError( @@ -44,7 +44,7 @@ void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::secon void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); emit MainWindowDisplayError( tr("An error has occured.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp index c9a2f8601..4bf2bfd40 100644 --- a/src/yuzu/applets/profile_select.cpp +++ b/src/yuzu/applets/profile_select.cpp @@ -150,8 +150,8 @@ QtProfileSelector::QtProfileSelector(GMainWindow& parent) { QtProfileSelector::~QtProfileSelector() = default; void QtProfileSelector::SelectProfile( - std::function<void(std::optional<Common::UUID>)> callback) const { - this->callback = std::move(callback); + std::function<void(std::optional<Common::UUID>)> callback_) const { + callback = std::move(callback_); emit MainWindowSelectProfile(); } diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h index 29c33cca0..4e9037488 100644 --- a/src/yuzu/applets/profile_select.h +++ b/src/yuzu/applets/profile_select.h @@ -60,7 +60,7 @@ public: explicit QtProfileSelector(GMainWindow& parent); ~QtProfileSelector() override; - void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override; + void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback_) const override; signals: void MainWindowSelectProfile() const; diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index af36f07c6..ab8cfd8ee 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -135,8 +135,8 @@ void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16st } void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, - std::function<void()> finished_check) const { - this->finished_check = std::move(finished_check); + std::function<void()> finished_check_) const { + finished_check = std::move(finished_check_); emit MainWindowTextCheckDialog(error_message); } diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index 44bcece75..9e1094cce 100644 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -61,7 +61,7 @@ public: void RequestText(std::function<void(std::optional<std::u16string>)> out, Core::Frontend::SoftwareKeyboardParameters parameters) const override; void SendTextCheckDialog(std::u16string error_message, - std::function<void()> finished_check) const override; + std::function<void()> finished_check_) const override; signals: void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index 33f1c385d..e482ba029 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp @@ -1,115 +1,414 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <mutex> - +#ifdef YUZU_USE_QT_WEB_ENGINE #include <QKeyEvent> -#include "core/hle/lock.h" +#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 "input_common/keyboard.h" +#include "input_common/main.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 -constexpr char NX_SHIM_INJECT_SCRIPT[] = R"( - window.nx = {}; - window.nx.playReport = {}; - window.nx.playReport.setCounterSetIdentifier = function () { - console.log("nx.playReport.setCounterSetIdentifier called - unimplemented"); - }; +namespace { - window.nx.playReport.incrementCounter = function () { - console.log("nx.playReport.incrementCounter called - unimplemented"); - }; +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, + 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{QWebEngineSettings::globalSettings()} { + 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->setRequestInterceptor(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(std::string_view main_url, + std::string_view additional_args) { + is_local = true; + + LoadExtractedFonts(); + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::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::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) { + 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(); - window.nx.footer = {}; - window.nx.footer.unsetAssign = function () { - console.log("nx.footer.unsetAssign called - unimplemented"); + 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 <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))); + } }; - var yuzu_key_callbacks = []; - window.nx.footer.setAssign = function(key, discard1, func, discard2) { - switch (key) { - case 'A': - yuzu_key_callbacks[0] = func; - break; - case 'B': - yuzu_key_callbacks[1] = func; - break; - case 'X': - yuzu_key_callbacks[2] = func; - break; - case 'Y': - yuzu_key_callbacks[3] = func; - break; - case 'L': - yuzu_key_callbacks[6] = func; - break; - case 'R': - yuzu_key_callbacks[7] = func; - break; + (f(T), ...); +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); } }; - var applet_done = false; - window.nx.endApplet = function() { - applet_done = true; + (f(T), ...); +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowKeyButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); + } }; - window.onkeypress = function(e) { if (e.keyCode === 13) { applet_done = true; } }; -)"; + (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(); + } -QString GetNXShimInjectionScript() { - return QString::fromStdString(NX_SHIM_INJECT_SCRIPT); + input_thread_running = false; + if (input_thread.joinable()) { + input_thread.join(); + } } -NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {} +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<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>(); -void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) { - parent()->event(event); + 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)); + } } -void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) { - parent()->event(event); +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 QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { - connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage, - Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this, - &QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this, - &QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection); + 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::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) { - this->unpack_romfs_callback = std::move(unpack_romfs_callback); - this->finished_callback = std::move(finished_callback); +void QtWebBrowser::OpenLocalWebPage( + std::string_view 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( + 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('?'); - const auto index = url.find('?'); if (index == std::string::npos) { - emit MainWindowOpenPage(url, ""); + emit MainWindowOpenWebPage(external_url, "", false); } else { - const auto front = url.substr(0, index); - const auto back = url.substr(index); - emit MainWindowOpenPage(front, back); + emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), + false); } } -void QtWebBrowser::MainWindowUnpackRomFS() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - unpack_romfs_callback(); +void QtWebBrowser::MainWindowExtractOfflineRomFS() { + extract_romfs_callback(); } -void QtWebBrowser::MainWindowFinishedBrowsing() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - finished_callback(); +void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url) { + callback(exit_reason, last_url); } diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h index b38437e46..47f960d69 100644 --- a/src/yuzu/applets/web_browser.h +++ b/src/yuzu/applets/web_browser.h @@ -1,10 +1,13 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <functional> +#include <atomic> +#include <memory> +#include <thread> + #include <QObject> #ifdef YUZU_USE_QT_WEB_ENGINE @@ -13,19 +16,172 @@ #include "core/frontend/applets/web_browser.h" +enum class HIDButton : u8; + class GMainWindow; +class InputInterpreter; +class UrlRequestInterceptor; + +namespace Core { +class System; +} + +namespace InputCommon { +class InputSubsystem; +} #ifdef YUZU_USE_QT_WEB_ENGINE -QString GetNXShimInjectionScript(); +enum class UserAgent { + WebApplet, + ShopN, + LoginApplet, + ShareApplet, + LobbyApplet, + WifiWebAuthApplet, +}; + +class QWebEngineProfile; +class QWebEngineSettings; + +class QtNXWebEngineView : public QWebEngineView { + Q_OBJECT -class NXInputWebEngineView : public QWebEngineView { public: - explicit NXInputWebEngineView(QWidget* parent = nullptr); + explicit QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_); + ~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); + + /** + * 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. + * + * @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]] Service::AM::Applets::WebExitReason GetExitReason() const; + void SetExitReason(Service::AM::Applets::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(); protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + +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(); + + /// Loads the extracted fonts using JavaScript. + void LoadExtractedFonts(); + + InputCommon::InputSubsystem* input_subsystem; + + 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{}; + + Service::AM::Applets::WebExitReason exit_reason{ + Service::AM::Applets::WebExitReason::EndButtonPressed}; + + std::string last_url{"http://localhost/"}; + + bool is_local{}; + + QWebEngineProfile* default_profile; + QWebEngineSettings* global_settings; }; #endif @@ -34,19 +190,28 @@ class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserAppl Q_OBJECT public: - explicit QtWebBrowser(GMainWindow& main_window); + explicit QtWebBrowser(GMainWindow& parent); ~QtWebBrowser() override; - void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) override; + void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback_, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback_) const override; + + void OpenExternalWebPage(std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback_) const override; signals: - void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; + void MainWindowOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local) const; private: - void MainWindowUnpackRomFS(); - void MainWindowFinishedBrowsing(); + void MainWindowExtractOfflineRomFS(); + + void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url); + + mutable std::function<void()> extract_romfs_callback; - std::function<void()> unpack_romfs_callback; - std::function<void()> finished_callback; + mutable std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback; }; diff --git a/src/yuzu/applets/web_browser_scripts.h b/src/yuzu/applets/web_browser_scripts.h new file mode 100644 index 000000000..992837a85 --- /dev/null +++ b/src/yuzu/applets/web_browser_scripts.h @@ -0,0 +1,193 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +constexpr char NX_FONT_CSS[] = R"( +(function() { + css = document.createElement('style'); + css.type = 'text/css'; + css.id = 'nx_font'; + css.innerText = ` +/* FontStandard */ +@font-face { + font-family: 'FontStandard'; + src: url('%1') format('truetype'); +} + +/* FontChineseSimplified */ +@font-face { + font-family: 'FontChineseSimplified'; + src: url('%2') format('truetype'); +} + +/* FontExtendedChineseSimplified */ +@font-face { + font-family: 'FontExtendedChineseSimplified'; + src: url('%3') format('truetype'); +} + +/* FontChineseTraditional */ +@font-face { + font-family: 'FontChineseTraditional'; + src: url('%4') format('truetype'); +} + +/* FontKorean */ +@font-face { + font-family: 'FontKorean'; + src: url('%5') format('truetype'); +} + +/* FontNintendoExtended */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%6') format('truetype'); +} + +/* FontNintendoExtended2 */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%7') format('truetype'); +} +`; + + document.head.appendChild(css); +})(); +)"; + +constexpr char LOAD_NX_FONT[] = R"( +(function() { + var elements = document.querySelectorAll("*"); + + for (var i = 0; i < elements.length; i++) { + var style = window.getComputedStyle(elements[i], null); + if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") || + style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) { + elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } else { + elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } + } +})(); +)"; + +constexpr char GAMEPAD_SCRIPT[] = R"( +window.addEventListener("gamepadconnected", function(e) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); +}); + +window.addEventListener("gamepaddisconnected", function(e) { + console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id); +}); +)"; + +constexpr char WINDOW_NX_SCRIPT[] = R"( +var end_applet = false; +var yuzu_key_callbacks = []; + +(function() { + class WindowNX { + constructor() { + yuzu_key_callbacks[1] = function() { window.history.back(); }; + yuzu_key_callbacks[2] = function() { window.nx.endApplet(); }; + } + + addEventListener(type, listener, options) { + console.log("nx.addEventListener called, type=%s", type); + + window.addEventListener(type, listener, options); + } + + endApplet() { + console.log("nx.endApplet called"); + + end_applet = true; + } + + playSystemSe(system_se) { + console.log("nx.playSystemSe is not implemented, system_se=%s", system_se); + } + + sendMessage(message) { + console.log("nx.sendMessage is not implemented, message=%s", message); + } + + setCursorScrollSpeed(scroll_speed) { + console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed); + } + } + + class WindowNXFooter { + setAssign(key, label, func, option) { + console.log("nx.footer.setAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = func; + break; + case "B": + yuzu_key_callbacks[1] = func; + break; + case "X": + yuzu_key_callbacks[2] = func; + break; + case "Y": + yuzu_key_callbacks[3] = func; + break; + case "L": + yuzu_key_callbacks[6] = func; + break; + case "R": + yuzu_key_callbacks[7] = func; + break; + } + } + + setFixed(kind) { + console.log("nx.footer.setFixed is not implemented, kind=%s", kind); + } + + unsetAssign(key) { + console.log("nx.footer.unsetAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = function() {}; + break; + case "B": + yuzu_key_callbacks[1] = function() {}; + break; + case "X": + yuzu_key_callbacks[2] = function() {}; + break; + case "Y": + yuzu_key_callbacks[3] = function() {}; + break; + case "L": + yuzu_key_callbacks[6] = function() {}; + break; + case "R": + yuzu_key_callbacks[7] = function() {}; + break; + } + } + } + + class WindowNXPlayReport { + incrementCounter(counter_id) { + console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id); + } + + setCounterSetIdentifier(counter_id) { + console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id); + } + } + + window.nx = new WindowNX(); + window.nx.footer = new WindowNXFooter(); + window.nx.playReport = new WindowNXPlayReport(); +})(); +)"; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 489104d5f..e124836b5 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -19,7 +19,7 @@ #include <QOpenGLContext> #endif -#if !defined(WIN32) && HAS_VULKAN +#if !defined(WIN32) #include <qpa/qplatformnativeinterface.h> #endif @@ -241,14 +241,12 @@ private: std::unique_ptr<Core::Frontend::GraphicsContext> context; }; -#ifdef HAS_VULKAN class VulkanRenderWidget : public RenderWidget { public: explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) { windowHandle()->setSurfaceType(QWindow::VulkanSurface); } }; -#endif static Core::Frontend::WindowSystemType GetWindowSystemType() { // Determine WSI type based on Qt platform. @@ -268,7 +266,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* Core::Frontend::EmuWindow::WindowSystemInfo wsi; wsi.type = GetWindowSystemType(); -#ifdef HAS_VULKAN // Our Win32 Qt external doesn't have the private API. #if defined(WIN32) || defined(__APPLE__) wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; @@ -281,7 +278,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; #endif wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f; -#endif return wsi; } @@ -569,6 +565,10 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p layout); } +bool GRenderWindow::IsLoadingComplete() const { + return first_frame; +} + void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) { setMinimumSize(minimal_size.first, minimal_size.second); } @@ -594,18 +594,12 @@ bool GRenderWindow::InitializeOpenGL() { } bool GRenderWindow::InitializeVulkan() { -#ifdef HAS_VULKAN auto child = new VulkanRenderWidget(this); child_widget = child; child_widget->windowHandle()->create(); main_context = std::make_unique<DummyContext>(); return true; -#else - QMessageBox::critical(this, tr("Vulkan not available!"), - tr("yuzu has not been compiled with Vulkan support.")); - return false; -#endif } bool GRenderWindow::LoadOpenGL() { diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index a6d788d40..ebe5cb965 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -162,6 +162,8 @@ public: /// Destroy the previous run's child_widget which should also destroy the child_window void ReleaseRenderTarget(); + bool IsLoadingComplete() const; + void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index fcc38b3af..34c2a5f8b 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -511,6 +511,9 @@ void Config::ReadControlValues() { ReadTouchscreenValues(); ReadMotionTouchValues(); + Settings::values.emulate_analog_keyboard = + ReadSetting(QStringLiteral("emulate_analog_keyboard"), false).toBool(); + ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), false); ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), true); @@ -634,8 +637,6 @@ void Config::ReadDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini Settings::values.record_frame_times = qt_config->value(QStringLiteral("record_frame_times"), false).toBool(); - Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); - Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); Settings::values.program_args = ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString(); Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); @@ -1186,6 +1187,8 @@ void Config::SaveControlValues() { QString::fromStdString(Settings::values.touch_device), QStringLiteral("engine:emu_window")); WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); + WriteSetting(QStringLiteral("emulate_analog_keyboard"), + Settings::values.emulate_analog_keyboard, false); qt_config->endGroup(); } @@ -1231,8 +1234,6 @@ void Config::SaveDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times); - WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); - WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); WriteSetting(QStringLiteral("program_args"), QString::fromStdString(Settings::values.program_args), QString{}); WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); @@ -1588,14 +1589,12 @@ void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool void Config::Reload() { ReadValues(); - Settings::Sanitize(); // To apply default value changes SaveValues(); Settings::Apply(Core::System::GetInstance()); } void Config::Save() { - Settings::Sanitize(); SaveValues(); } diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 027099ab7..121873f95 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -28,9 +28,6 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co ConfigureDebug::~ConfigureDebug() = default; void ConfigureDebug::SetConfiguration() { - ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); - ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); - ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->toggle_console->setChecked(UISettings::values.show_console); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); @@ -45,8 +42,6 @@ void ConfigureDebug::SetConfiguration() { } void ConfigureDebug::ApplyConfiguration() { - Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); - Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 6f94fe304..9186aa732 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>400</width> - <height>467</height> + <height>486</height> </rect> </property> <property name="windowTitle"> @@ -15,57 +15,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_1"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>GDB</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_1"> - <item> - <widget class="QCheckBox" name="toggle_gdbstub"> - <property name="text"> - <string>Enable GDB Stub</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_1"> - <property name="text"> - <string>Port:</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="gdbport_spinbox"> - <property name="maximum"> - <number>65536</number> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - </layout> - </item> - <item> <widget class="QGroupBox" name="groupBox_2"> <property name="title"> <string>Logging</string> @@ -258,8 +207,6 @@ </layout> </widget> <tabstops> - <tabstop>toggle_gdbstub</tabstop> - <tabstop>gdbport_spinbox</tabstop> <tabstop>log_filter_edit</tabstop> <tabstop>toggle_console</tabstop> <tabstop>open_log_button</tabstop> @@ -269,22 +216,5 @@ <tabstop>quest_flag</tabstop> </tabstops> <resources/> - <connections> - <connection> - <sender>toggle_gdbstub</sender> - <signal>toggled(bool)</signal> - <receiver>gdbport_spinbox</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>84</x> - <y>157</y> - </hint> - <hint type="destinationlabel"> - <x>342</x> - <y>158</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 6fda0ce35..b78a5dff0 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -4,22 +4,17 @@ #include <QColorDialog> #include <QComboBox> -#ifdef HAS_VULKAN #include <QVulkanInstance> -#endif #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" -#ifdef HAS_VULKAN -#include "video_core/renderer_vulkan/renderer_vulkan.h" -#endif - ConfigureGraphics::ConfigureGraphics(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGraphics) { vulkan_device = Settings::values.vulkan_device.GetValue(); @@ -218,12 +213,10 @@ void ConfigureGraphics::UpdateDeviceComboBox() { } void ConfigureGraphics::RetrieveVulkanDevices() { -#ifdef HAS_VULKAN vulkan_devices.clear(); - for (auto& name : Vulkan::RendererVulkan::EnumerateDevices()) { + for (const auto& name : Vulkan::RendererVulkan::EnumerateDevices()) { vulkan_devices.push_back(QString::fromStdString(name)); } -#endif } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index abaf03630..4e557bc6f 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -121,6 +121,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); + Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked(); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); } @@ -147,6 +148,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); + ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); UpdateUIEnabled(); diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index a880a7c68..f207e5d3b 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2546,14 +2546,27 @@ </property> </widget> </item> - <item row="4" column="2"> + <item row="1" column="0"> + <widget class="QCheckBox" name="emulate_analog_keyboard"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Emulate Analog with Keyboard Input</string> + </property> + </widget> + </item> + <item row="5" column="2"> <widget class="QPushButton" name="touchscreen_advanced"> <property name="text"> <string>Advanced</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="2" column="1"> <spacer name="horizontalSpacer_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -2569,21 +2582,21 @@ </property> </spacer> </item> - <item row="1" column="2"> + <item row="2" column="2"> <widget class="QPushButton" name="mouse_advanced"> <property name="text"> <string>Advanced</string> </property> </widget> </item> - <item row="4" column="0"> + <item row="5" column="0"> <widget class="QCheckBox" name="touchscreen_enabled"> <property name="text"> <string>Touchscreen</string> </property> </widget> </item> - <item row="1" column="0"> + <item row="2" column="0"> <widget class="QCheckBox" name="mouse_enabled"> <property name="minimumSize"> <size> @@ -2596,28 +2609,28 @@ </property> </widget> </item> - <item row="6" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="motion_touch"> <property name="text"> <string>Motion / Touch</string> </property> </widget> </item> - <item row="6" column="2"> + <item row="7" column="2"> <widget class="QPushButton" name="buttonMotionTouch"> <property name="text"> <string>Configure</string> </property> </widget> </item> - <item row="5" column="0"> + <item row="6" column="0"> <widget class="QCheckBox" name="debug_enabled"> <property name="text"> <string>Debug Controller</string> </property> </widget> </item> - <item row="5" column="2"> + <item row="6" column="2"> <widget class="QPushButton" name="debug_configure"> <property name="text"> <string>Configure</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index f9915fb7a..3c7500ee3 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -173,61 +173,31 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); } - if (param.Get("engine", "") == "sdl") { + const auto engine_str = param.Get("engine", ""); + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + const bool invert_x = param.Get("invert_x", "+") == "-"; + const bool invert_y = param.Get("invert_y", "+") == "-"; + if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") { if (dir == "modifier") { return QObject::tr("[unused]"); } - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("Axis %1").arg(axis_x_str); - } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("Axis %1").arg(axis_y_str); + if (dir == "left") { + const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - return {}; - } - - if (param.Get("engine", "") == "gcpad") { - if (dir == "modifier") { - return QObject::tr("[unused]"); + if (dir == "right") { + const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("GC Axis %1").arg(axis_x_str); + if (dir == "up") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("GC Axis %1").arg(axis_y_str); - } - - return {}; - } - - if (param.Get("engine", "") == "mouse") { - if (dir == "modifier") { - return QObject::tr("[unused]"); - } - - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("Mouse %1").arg(axis_x_str); - } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("Mouse %1").arg(axis_y_str); + if (dir == "down") { + const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } return {}; @@ -396,6 +366,25 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i analogs_param[analog_id].Clear(); analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); }); + context_menu.addAction(tr("Invert axis"), [&] { + if (sub_button_id == 2 || sub_button_id == 3) { + const bool invert_value = + analogs_param[analog_id].Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_x", invert_str); + } + if (sub_button_id == 0 || sub_button_id == 1) { + const bool invert_value = + analogs_param[analog_id].Get("invert_y", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_y", invert_str); + } + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id) { + analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( + analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + } + }); context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( menu_location)); }); diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index 2afac591a..c2a7113da 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -183,8 +183,8 @@ void ConfigureMotionTouch::ConnectEvents() { } void ConfigureMotionTouch::OnUDPAddServer() { - QRegExp re(tr("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[" - "0-9][0-9]?)$")); // a valid ip address + QRegExp re(tr(R"re(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4]" + "[0-9]|[01]?[0-9][0-9]?)$)re")); // a valid ip address bool ok; QString port_text = ui->udp_port->text(); QString server_text = ui->udp_server->text(); diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index 0e26f765b..efdc6aa50 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -48,7 +48,7 @@ private: MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { setObjectName(QStringLiteral("MicroProfile")); - setWindowTitle(tr("MicroProfile")); + setWindowTitle(tr("&MicroProfile")); resize(1000, 600); // Remove the "?" button from the titlebar and enable the maximize button setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index a20824719..0925c10b4 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -13,10 +13,10 @@ #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/synchronization_object.h" #include "core/hle/kernel/thread.h" #include "core/memory.h" @@ -101,7 +101,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() }; const auto& system = Core::System::GetInstance(); - add_threads(system.GlobalScheduler().GetThreadList()); + add_threads(system.GlobalSchedulerContext().GetThreadList()); return item_list; } @@ -349,14 +349,14 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); list.push_back( std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore()))); - list.push_back( - std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask()))); + list.push_back(std::make_unique<WaitTreeText>( + tr("affinity mask = %1").arg(thread.GetAffinityMask().GetAffinityMask()))); list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID()))); list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") .arg(thread.GetPriority()) .arg(thread.GetNominalPriority()))); list.push_back(std::make_unique<WaitTreeText>( - tr("last running ticks = %1").arg(thread.GetLastRunningTicks()))); + tr("last running ticks = %1").arg(thread.GetLastScheduledTick()))); const VAddr mutex_wait_address = thread.GetMutexWaitAddress(); if (mutex_wait_address != 0) { @@ -457,7 +457,7 @@ void WaitTreeModel::InitItems() { thread_items = WaitTreeItem::MakeThreadItemList(); } -WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) { +WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("&Wait Tree"), parent) { setObjectName(QStringLiteral("WaitTreeWidget")); view = new QTreeView(this); view->setHeaderHidden(true); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 248855aff..df935022d 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -174,7 +174,8 @@ public: } bool operator<(const QStandardItem& other) const override { - return data(CompatNumberRole) < other.data(CompatNumberRole); + return data(CompatNumberRole).value<QString>() < + other.data(CompatNumberRole).value<QString>(); } }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 26f5e42ed..ab66d7f93 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -28,8 +28,6 @@ #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.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 // defines. @@ -83,6 +81,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/core.h" #include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -124,14 +123,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/discord_impl.h" #endif -#ifdef YUZU_USE_QT_WEB_ENGINE -#include <QWebEngineProfile> -#include <QWebEngineScript> -#include <QWebEngineScriptCollection> -#include <QWebEngineSettings> -#include <QWebEngineView> -#endif - #ifdef QT_STATICPLUGIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif @@ -148,8 +139,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; constexpr int default_mouse_timeout = 2500; -constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; - /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there * is a bitfield "callout_flags" options, used to track if a message has already been shown to the @@ -191,6 +180,30 @@ static void InitializeLogging() { #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() : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, @@ -259,6 +272,9 @@ GMainWindow::GMainWindow() FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); + // Remove cached contents generated during the previous session + RemoveCachedContents(); + // Gen keys if necessary OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); @@ -350,150 +366,141 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message emit SoftwareKeyboardFinishedCheckDialog(); } +void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE -void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { - NXInputWebEngineView web_browser_view(this); + if (disable_web_applet) { + emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, + "http://localhost/"); + return; + } - // Scope to contain the QProgressDialog for initialization - { - QProgressDialog progress(this); - progress.setMinimumDuration(200); - progress.setLabelText(tr("Loading Web Applet...")); - progress.setRange(0, 4); - progress.setValue(0); - progress.show(); + QtNXWebEngineView web_browser_view(this, Core::System::GetInstance(), input_subsystem.get()); - auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); }); + ui.action_Pause->setEnabled(false); + ui.action_Restart->setEnabled(false); + ui.action_Stop->setEnabled(false); - while (!future.isFinished()) - QApplication::processEvents(); + { + QProgressDialog loading_progress(this); + loading_progress.setLabelText(tr("Loading Web Applet...")); + loading_progress.setRange(0, 3); + loading_progress.setValue(0); + + if (is_local && !Common::FS::Exists(std::string(main_url))) { + loading_progress.show(); - progress.setValue(1); + auto future = QtConcurrent::run([this] { emit WebBrowserExtractOfflineRomFS(); }); - // Load the special shim script to handle input and exit. - QWebEngineScript nx_shim; - nx_shim.setSourceCode(GetNXShimInjectionScript()); - nx_shim.setWorldId(QWebEngineScript::MainWorld); - nx_shim.setName(QStringLiteral("nx_inject.js")); - nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation); - nx_shim.setRunsOnSubFrames(true); - web_browser_view.page()->profile()->scripts()->insert(nx_shim); + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + } - web_browser_view.load( - QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() + - QString::fromStdString(std::string(additional_args)))); + loading_progress.setValue(1); - progress.setValue(2); + if (is_local) { + web_browser_view.LoadLocalWebPage(main_url, additional_args); + } else { + web_browser_view.LoadExternalWebPage(main_url, additional_args); + } - render_window->hide(); - web_browser_view.setFocus(); + 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()) / - Layout::ScreenUndocked::Width); - web_browser_view.settings()->setAttribute( - QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + static_cast<qreal>(Layout::ScreenUndocked::Width)); + web_browser_view.setFocus(); web_browser_view.show(); - progress.setValue(3); + loading_progress.setValue(2); - QApplication::processEvents(); + QCoreApplication::processEvents(); - progress.setValue(4); + loading_progress.setValue(3); } - bool finished = false; - QAction* exit_action = new QAction(tr("Exit Web Applet"), this); - connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; }); - ui.menubar->addAction(exit_action); + bool exit_check = false; - auto& npad = - Core::System::GetInstance() - .ServiceManager() - .GetService<Service::HID::Hid>("hid") - ->GetAppletResource() - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); - - const auto fire_js_keypress = [&web_browser_view](u32 key_code) { - web_browser_view.page()->runJavaScript( - QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));") - .arg(key_code)); - }; + // TODO (Morph): Remove this + QAction* exit_action = new QAction(tr("Disable Web Applet"), this); + connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] { + const auto result = QMessageBox::warning( + this, tr("Disable Web Applet"), + tr("Disabling the web applet will cause it to not be shown again for the rest of the " + "emulated session. This can lead to undefined behavior and should only be used with " + "Super Mario 3D All-Stars. Are you sure you want to disable the web applet?"), + QMessageBox::Yes | QMessageBox::No); + if (result == QMessageBox::Yes) { + disable_web_applet = true; + web_browser_view.SetFinished(true); + } + }); + ui.menubar->addAction(exit_action); - QMessageBox::information( - this, tr("Exit"), - tr("To exit the web application, use the game provided controls to select exit, select the " - "'Exit Web Applet' option in the menu bar, or press the 'Enter' key.")); - - bool running_exit_check = false; - while (!finished) { - QApplication::processEvents(); - - if (!running_exit_check) { - web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"), - [&](const QVariant& res) { - running_exit_check = false; - if (res.toBool()) - finished = true; - }); - running_exit_check = true; + 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( + Service::AM::Applets::WebExitReason::EndButtonPressed); + } + }); + + exit_check = true; } - const auto input = npad.GetAndResetPressState(); - for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) { - if ((input & (1 << i)) != 0) { - LOG_DEBUG(Frontend, "firing input for button id={:02X}", i); - web_browser_view.page()->runJavaScript( - QStringLiteral("yuzu_key_callbacks[%1]();").arg(i)); + if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) { + if (!web_browser_view.IsFinished()) { + web_browser_view.SetFinished(true); + web_browser_view.SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL); } + + web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString()); } - if (input & 0x00888000) // RStick Down | LStick Down | DPad Down - fire_js_keypress(40); // Down Arrow Key - else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right - fire_js_keypress(39); // Right Arrow Key - else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up - fire_js_keypress(38); // Up Arrow Key - else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left - fire_js_keypress(37); // Left Arrow Key - else if (input & 0x00000001) // A Button - fire_js_keypress(13); // Enter Key + 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->show(); + render_window->setFocus(); - ui.menubar->removeAction(exit_action); - // Needed to update render window focus/show and remove menubar action - QApplication::processEvents(); - emit WebBrowserFinishedBrowsing(); -} + if (render_window->IsLoadingComplete()) { + render_window->show(); + } -#else + ui.action_Pause->setEnabled(true); + ui.action_Restart->setEnabled(true); + ui.action_Stop->setEnabled(true); -void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { -#ifndef __linux__ - QMessageBox::warning( - this, tr("Web Applet"), - tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot " - "properly display the game manual or web page requested."), - QMessageBox::Ok, QMessageBox::Ok); -#endif + ui.menubar->removeAction(exit_action); - LOG_INFO(Frontend, - "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at " - "'{}' with arguments '{}'!", - filename, additional_args); + QCoreApplication::processEvents(); - emit WebBrowserFinishedBrowsing(); -} + emit WebBrowserClosed(exit_reason, last_url); + +#else + + // Utilize the same fallback as the default web browser applet. + emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); #endif +} void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING @@ -573,9 +580,8 @@ void GMainWindow::InitializeWidgets() { if (emulation_running) { return; } - const bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue(); - Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); + Settings::values.use_asynchronous_gpu_emulation.SetValue( + !Settings::values.use_asynchronous_gpu_emulation.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); Settings::Apply(Core::System::GetInstance()); }); @@ -592,16 +598,13 @@ void GMainWindow::InitializeWidgets() { return; } Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue()); - const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue(); - Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); Settings::Apply(Core::System::GetInstance()); }); multicore_status_button->setText(tr("MULTICORE")); multicore_status_button->setCheckable(true); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); + statusBar()->insertPermanentWidget(0, multicore_status_button); statusBar()->insertPermanentWidget(0, async_status_button); @@ -615,11 +618,6 @@ void GMainWindow::InitializeWidgets() { }); renderer_status_button->toggle(); -#ifndef HAS_VULKAN - renderer_status_button->setChecked(false); - renderer_status_button->setCheckable(false); - renderer_status_button->setDisabled(true); -#else renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); connect(renderer_status_button, &QPushButton::clicked, [this] { @@ -634,7 +632,6 @@ void GMainWindow::InitializeWidgets() { Settings::Apply(Core::System::GetInstance()); }); -#endif // HAS_VULKAN statusBar()->insertPermanentWidget(0, renderer_status_button); statusBar()->setVisible(true); @@ -670,7 +667,7 @@ void GMainWindow::InitializeRecentFileMenuActions() { } ui.menu_recent_files->addSeparator(); QAction* action_clear_recent_files = new QAction(this); - action_clear_recent_files->setText(tr("Clear Recent Files")); + action_clear_recent_files->setText(tr("&Clear Recent Files")); connect(action_clear_recent_files, &QAction::triggered, this, [this] { UISettings::values.recent_files.clear(); UpdateRecentFiles(); @@ -932,7 +929,10 @@ void GMainWindow::ConnectMenuEvents() { &GMainWindow::OnDisplayTitleBars); connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); - connect(ui.action_Reset_Window_Size, &QAction::triggered, this, &GMainWindow::ResetWindowSize); + connect(ui.action_Reset_Window_Size_720, &QAction::triggered, this, + &GMainWindow::ResetWindowSize720); + connect(ui.action_Reset_Window_Size_1080, &QAction::triggered, this, + &GMainWindow::ResetWindowSize1080); // Fullscreen connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); @@ -994,7 +994,6 @@ bool GMainWindow::LoadROM(const QString& filename, std::size_t program_index) { system.SetAppletFrontendSet({ std::make_unique<QtControllerSelector>(*this), // Controller Selector - nullptr, // E-Commerce std::make_unique<QtErrorDisplay>(*this), // Error Display nullptr, // Parental Controls nullptr, // Photo Viewer @@ -1107,6 +1106,11 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { ConfigureVibration::SetAllVibrationDevices(); + // Save configurations + UpdateUISettings(); + game_list->SaveInterfaceLayout(); + config->Save(); + Settings::LogSettings(); if (UISettings::values.select_user_on_boot) { @@ -1240,9 +1244,7 @@ void GMainWindow::ShutdownGame() { emu_frametime_label->setVisible(false); async_status_button->setEnabled(true); multicore_status_button->setEnabled(true); -#ifdef HAS_VULKAN renderer_status_button->setEnabled(true); -#endif emulation_running = false; @@ -1529,7 +1531,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); for (const auto& entry : dlc_entries) { - if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { + if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); @@ -2103,11 +2105,12 @@ void GMainWindow::OnStartGame() { qRegisterMetaType<std::string>("std::string"); qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); 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); ui.action_Start->setEnabled(false); - ui.action_Start->setText(tr("Continue")); + ui.action_Start->setText(tr("&Continue")); ui.action_Pause->setEnabled(true); ui.action_Stop->setEnabled(true); @@ -2251,7 +2254,7 @@ void GMainWindow::ToggleWindowMode() { } } -void GMainWindow::ResetWindowSize() { +void GMainWindow::ResetWindowSize720() { const auto aspect_ratio = Layout::EmulationAspectRatio( static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width); @@ -2265,6 +2268,20 @@ void GMainWindow::ResetWindowSize() { } } +void GMainWindow::ResetWindowSize1080() { + const auto aspect_ratio = Layout::EmulationAspectRatio( + static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), + static_cast<float>(Layout::ScreenDocked::Height) / Layout::ScreenDocked::Width); + if (!ui.action_Single_Window_Mode->isChecked()) { + render_window->resize(Layout::ScreenDocked::Height / aspect_ratio, + Layout::ScreenDocked::Height); + } else { + resize(Layout::ScreenDocked::Height / aspect_ratio, + Layout::ScreenDocked::Height + menuBar()->height() + + (ui.action_Show_Status_Bar->isChecked() ? statusBar()->height() : 0)); + } +} + void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence; @@ -2512,14 +2529,27 @@ void GMainWindow::UpdateStatusBar() { void GMainWindow::UpdateStatusButtons() { dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); - Settings::values.use_asynchronous_gpu_emulation.SetValue( - Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); -#ifdef HAS_VULKAN renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); +} + +void GMainWindow::UpdateUISettings() { + if (!ui.action_Fullscreen->isChecked()) { + UISettings::values.geometry = saveGeometry(); + UISettings::values.renderwindow_geometry = render_window->saveGeometry(); + } + UISettings::values.state = saveState(); +#if MICROPROFILE_ENABLED + UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); + UISettings::values.microprofile_visible = microProfileDialog->isVisible(); #endif + UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); + UISettings::values.fullscreen = ui.action_Fullscreen->isChecked(); + UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); + UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); + UISettings::values.first_start = false; } void GMainWindow::HideMouseCursor() { @@ -2709,7 +2739,7 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv dlc_match.reserve(dlc_entries.size()); std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { - return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id && + return FileSys::GetBaseTitleID(entry.title_id) == program_id && installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; }); @@ -2755,22 +2785,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { return; } - if (!ui.action_Fullscreen->isChecked()) { - UISettings::values.geometry = saveGeometry(); - UISettings::values.renderwindow_geometry = render_window->saveGeometry(); - } - UISettings::values.state = saveState(); -#if MICROPROFILE_ENABLED - UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); - UISettings::values.microprofile_visible = microProfileDialog->isVisible(); -#endif - UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); - UISettings::values.fullscreen = ui.action_Fullscreen->isChecked(); - UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); - UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); - UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); - UISettings::values.first_start = false; - + UpdateUISettings(); game_list->SaveInterfaceLayout(); hotkey_registry.SaveHotkeys(); @@ -2946,7 +2961,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { UpdateWindowTitle(); if (emulation_running) - ui.action_Start->setText(tr("Continue")); + ui.action_Start->setText(tr("&Continue")); } void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 6242341d1..ea6d2c30d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -55,6 +55,10 @@ namespace InputCommon { class InputSubsystem; } +namespace Service::AM::Applets { +enum class WebExitReason : u32; +} + enum class EmulatedDirectoryTarget { NAND, SDMC, @@ -126,8 +130,8 @@ signals: void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); void SoftwareKeyboardFinishedCheckDialog(); - void WebBrowserUnpackRomFS(); - void WebBrowserFinishedBrowsing(); + void WebBrowserExtractOfflineRomFS(); + void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); public slots: void OnLoadComplete(); @@ -138,7 +142,8 @@ public slots: void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); - void WebBrowserOpenPage(std::string_view filename, std::string_view arguments); + void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local); void OnAppFocusStateChanged(Qt::ApplicationState state); private: @@ -237,7 +242,8 @@ private slots: void ShowFullscreen(); void HideFullscreen(); void ToggleWindowMode(); - void ResetWindowSize(); + void ResetWindowSize720(); + void ResetWindowSize1080(); void OnCaptureScreenshot(); void OnCoreError(Core::System::ResultStatus, std::string); void OnReinitializeKeys(ReinitializeKeyBehavior behavior); @@ -257,6 +263,7 @@ private: const std::string& title_version = {}); void UpdateStatusBar(); void UpdateStatusButtons(); + void UpdateUISettings(); void HideMouseCursor(); void ShowMouseCursor(); void OpenURL(const QUrl& url); @@ -321,6 +328,9 @@ private: // Last game booted, used for multi-process apps QString last_filename_booted; + // Disables the web applet for the rest of the emulated session + bool disable_web_applet{}; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 2f3792247..e2ad5baf6 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -25,16 +25,7 @@ </property> <widget class="QWidget" name="centralwidget"> <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> + <property name="margin"> <number>0</number> </property> </layout> @@ -45,7 +36,7 @@ <x>0</x> <y>0</y> <width>1280</width> - <height>21</height> + <height>26</height> </rect> </property> <widget class="QMenu" name="menu_File"> @@ -54,7 +45,7 @@ </property> <widget class="QMenu" name="menu_recent_files"> <property name="title"> - <string>Recent Files</string> + <string>&Recent Files</string> </property> </widget> <addaction name="action_Install_File_NAND"/> @@ -89,7 +80,7 @@ </property> <widget class="QMenu" name="menu_View_Debugging"> <property name="title"> - <string>Debugging</string> + <string>&Debugging</string> </property> </widget> <addaction name="action_Fullscreen"/> @@ -97,13 +88,14 @@ <addaction name="action_Display_Dock_Widget_Headers"/> <addaction name="action_Show_Filter_Bar"/> <addaction name="action_Show_Status_Bar"/> - <addaction name="action_Reset_Window_Size"/> + <addaction name="action_Reset_Window_Size_720"/> + <addaction name="action_Reset_Window_Size_1080"/> <addaction name="separator"/> <addaction name="menu_View_Debugging"/> </widget> <widget class="QMenu" name="menu_Tools"> <property name="title"> - <string>Tools</string> + <string>&Tools</string> </property> <addaction name="action_Rederive"/> <addaction name="separator"/> @@ -131,17 +123,17 @@ <bool>true</bool> </property> <property name="text"> - <string>Install Files to NAND...</string> + <string>&Install Files to NAND...</string> </property> </action> <action name="action_Load_File"> <property name="text"> - <string>Load File...</string> + <string>L&oad File...</string> </property> </action> <action name="action_Load_Folder"> <property name="text"> - <string>Load Folder...</string> + <string>Load &Folder...</string> </property> </action> <action name="action_Exit"> @@ -175,12 +167,12 @@ </action> <action name="action_Rederive"> <property name="text"> - <string>Reinitialize keys...</string> + <string>&Reinitialize keys...</string> </property> </action> <action name="action_About"> <property name="text"> - <string>About yuzu</string> + <string>&About yuzu</string> </property> </action> <action name="action_Single_Window_Mode"> @@ -188,12 +180,12 @@ <bool>true</bool> </property> <property name="text"> - <string>Single Window Mode</string> + <string>Single &Window Mode</string> </property> </action> <action name="action_Configure"> <property name="text"> - <string>Configure...</string> + <string>Con&figure...</string> </property> </action> <action name="action_Display_Dock_Widget_Headers"> @@ -201,7 +193,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Display Dock Widget Headers</string> + <string>Display D&ock Widget Headers</string> </property> </action> <action name="action_Show_Filter_Bar"> @@ -209,7 +201,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Show Filter Bar</string> + <string>Show &Filter Bar</string> </property> </action> <action name="action_Show_Status_Bar"> @@ -217,12 +209,26 @@ <bool>true</bool> </property> <property name="text"> + <string>Show &Status Bar</string> + </property> + <property name="iconText"> <string>Show Status Bar</string> </property> </action> - <action name="action_Reset_Window_Size"> + <action name="action_Reset_Window_Size_720"> + <property name="text"> + <string>Reset Window Size to &720p</string> + </property> + <property name="iconText"> + <string>Reset Window Size to 720p</string> + </property> + </action> + <action name="action_Reset_Window_Size_1080"> <property name="text"> - <string>Reset Window Size</string> + <string>Reset Window Size to &1080p</string> + </property> + <property name="iconText"> + <string>Reset Window Size to 1080p</string> </property> </action> <action name="action_Fullscreen"> @@ -230,7 +236,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Fullscreen</string> + <string>F&ullscreen</string> </property> </action> <action name="action_Restart"> @@ -238,7 +244,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Restart</string> + <string>&Restart</string> </property> </action> <action name="action_Load_Amiibo"> @@ -246,7 +252,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Load Amiibo...</string> + <string>Load &Amiibo...</string> </property> </action> <action name="action_Report_Compatibility"> @@ -254,7 +260,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Report Compatibility</string> + <string>&Report Compatibility</string> </property> <property name="visible"> <bool>false</bool> @@ -262,22 +268,22 @@ </action> <action name="action_Open_Mods_Page"> <property name="text"> - <string>Open Mods Page</string> + <string>Open &Mods Page</string> </property> </action> <action name="action_Open_Quickstart_Guide"> <property name="text"> - <string>Open Quickstart Guide</string> + <string>Open &Quickstart Guide</string> </property> </action> <action name="action_Open_FAQ"> <property name="text"> - <string>FAQ</string> + <string>&FAQ</string> </property> </action> <action name="action_Open_yuzu_Folder"> <property name="text"> - <string>Open yuzu Folder</string> + <string>Open &yuzu Folder</string> </property> </action> <action name="action_Capture_Screenshot"> @@ -285,7 +291,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Capture Screenshot</string> + <string>&Capture Screenshot</string> </property> </action> <action name="action_Configure_Current_Game"> @@ -293,7 +299,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Configure Current Game...</string> + <string>Configure C&urrent Game...</string> </property> </action> </widget> diff --git a/src/yuzu/util/url_request_interceptor.cpp b/src/yuzu/util/url_request_interceptor.cpp new file mode 100644 index 000000000..2d491d8c0 --- /dev/null +++ b/src/yuzu/util/url_request_interceptor.cpp @@ -0,0 +1,32 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef YUZU_USE_QT_WEB_ENGINE + +#include "yuzu/util/url_request_interceptor.h" + +UrlRequestInterceptor::UrlRequestInterceptor(QObject* p) : QWebEngineUrlRequestInterceptor(p) {} + +UrlRequestInterceptor::~UrlRequestInterceptor() = default; + +void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + const auto resource_type = info.resourceType(); + + switch (resource_type) { + case QWebEngineUrlRequestInfo::ResourceTypeMainFrame: + requested_url = info.requestUrl(); + emit FrameChanged(); + break; + case QWebEngineUrlRequestInfo::ResourceTypeSubFrame: + case QWebEngineUrlRequestInfo::ResourceTypeXhr: + emit FrameChanged(); + break; + } +} + +QUrl UrlRequestInterceptor::GetRequestedURL() const { + return requested_url; +} + +#endif diff --git a/src/yuzu/util/url_request_interceptor.h b/src/yuzu/util/url_request_interceptor.h new file mode 100644 index 000000000..8a7f7499f --- /dev/null +++ b/src/yuzu/util/url_request_interceptor.h @@ -0,0 +1,30 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#ifdef YUZU_USE_QT_WEB_ENGINE + +#include <QObject> +#include <QWebEngineUrlRequestInterceptor> + +class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor { + Q_OBJECT + +public: + explicit UrlRequestInterceptor(QObject* p = nullptr); + ~UrlRequestInterceptor() override; + + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + + QUrl GetRequestedURL() const; + +signals: + void FrameChanged(); + +private: + QUrl requested_url; +}; + +#endif |
