diff options
| author | bunnei <bunneidev@gmail.com> | 2019-01-10 17:04:38 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-01-10 17:04:38 -0500 | 
| commit | 83e8ad23310937bb72f4412c15f45231a19202f7 (patch) | |
| tree | 80301af69b14a701f16e21d41ced436850085031 /src/yuzu | |
| parent | 912f2a520a77f478a33bbfb426b0aebf3148e5f7 (diff) | |
| parent | 2378ecd0e8709efc47fbc73ef41e5ee0b952ee59 (diff) | |
Merge pull request #1939 from DarkLordZach/web-applet
applets: Implement HLE web browser applet (LibAppletOff)
Diffstat (limited to 'src/yuzu')
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | src/yuzu/applets/web_browser.cpp | 113 | ||||
| -rw-r--r-- | src/yuzu/applets/web_browser.h | 53 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 151 | ||||
| -rw-r--r-- | src/yuzu/main.h | 7 | 
5 files changed, 331 insertions, 0 deletions
| diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 5446be6be..1f852df4b 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -11,6 +11,8 @@ add_executable(yuzu      applets/profile_select.h      applets/software_keyboard.cpp      applets/software_keyboard.h +    applets/web_browser.cpp +    applets/web_browser.h      bootmanager.cpp      bootmanager.h      compatibility_list.cpp @@ -157,6 +159,11 @@ if (USE_DISCORD_PRESENCE)      target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)  endif() +if (YUZU_USE_QT_WEB_ENGINE) +    target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets) +    target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) +endif () +  if(UNIX AND NOT APPLE)      install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")  endif() diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp new file mode 100644 index 000000000..c59b7ade1 --- /dev/null +++ b/src/yuzu/applets/web_browser.cpp @@ -0,0 +1,113 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> + +#include <QKeyEvent> + +#include "core/hle/lock.h" +#include "yuzu/applets/web_browser.h" +#include "yuzu/main.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"); +    }; + +    window.nx.playReport.incrementCounter = function () { +        console.log("nx.playReport.incrementCounter called - unimplemented"); +    }; + +    window.nx.footer = {}; +    window.nx.footer.unsetAssign = function () { +        console.log("nx.footer.unsetAssign called - unimplemented"); +    }; + +    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; +        } +    }; + +    var applet_done = false; +    window.nx.endApplet = function() { +        applet_done = true; +    }; +)"; + +QString GetNXShimInjectionScript() { +    return QString::fromStdString(NX_SHIM_INJECT_SCRIPT); +} + +NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {} + +void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) { +    parent()->event(event); +} + +void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) { +    parent()->event(event); +} + +#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); +} + +QtWebBrowser::~QtWebBrowser() = default; + +void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, +                            std::function<void()> finished_callback) const { +    this->unpack_romfs_callback = unpack_romfs_callback; +    this->finished_callback = finished_callback; + +    const auto index = url.find('?'); +    if (index == std::string::npos) { +        emit MainWindowOpenPage(url, ""); +    } else { +        const auto front = url.substr(0, index); +        const auto back = url.substr(index); +        emit MainWindowOpenPage(front, back); +    } +} + +void QtWebBrowser::MainWindowUnpackRomFS() { +    // Acquire the HLE mutex +    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    unpack_romfs_callback(); +} + +void QtWebBrowser::MainWindowFinishedBrowsing() { +    // Acquire the HLE mutex +    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    finished_callback(); +} diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h new file mode 100644 index 000000000..bba273767 --- /dev/null +++ b/src/yuzu/applets/web_browser.h @@ -0,0 +1,53 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <QObject> + +#ifdef YUZU_USE_QT_WEB_ENGINE +#include <QWebEngineView> +#endif + +#include "core/frontend/applets/web_browser.h" + +class GMainWindow; + +#ifdef YUZU_USE_QT_WEB_ENGINE + +QString GetNXShimInjectionScript(); + +class NXInputWebEngineView : public QWebEngineView { +public: +    explicit NXInputWebEngineView(QWidget* parent = nullptr); + +protected: +    void keyPressEvent(QKeyEvent* event) override; +    void keyReleaseEvent(QKeyEvent* event) override; +}; + +#endif + +class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { +    Q_OBJECT + +public: +    explicit QtWebBrowser(GMainWindow& main_window); +    ~QtWebBrowser() override; + +    void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, +                  std::function<void()> finished_callback) const override; + +signals: +    void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; + +public slots: +    void MainWindowUnpackRomFS(); +    void MainWindowFinishedBrowsing(); + +private: +    mutable std::function<void()> unpack_romfs_callback; +    mutable std::function<void()> finished_callback; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 1d5a2b51a..f564de994 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -10,11 +10,14 @@  // VFS includes must be before glad as they will conflict with Windows file api, which uses defines.  #include "applets/profile_select.h"  #include "applets/software_keyboard.h" +#include "applets/web_browser.h"  #include "configuration/configure_per_general.h"  #include "core/file_sys/vfs.h"  #include "core/file_sys/vfs_real.h"  #include "core/hle/service/acc/profile_manager.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. @@ -96,6 +99,14 @@ 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 @@ -252,6 +263,144 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message      emit SoftwareKeyboardFinishedCheckDialog();  } +#ifdef YUZU_USE_QT_WEB_ENGINE + +void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { +    NXInputWebEngineView web_browser_view(this); + +    // Scope to contain the QProgressDialog for initalization +    { +        QProgressDialog progress(this); +        progress.setMinimumDuration(200); +        progress.setLabelText(tr("Loading Web Applet...")); +        progress.setRange(0, 4); +        progress.setValue(0); +        progress.show(); + +        auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); }); + +        while (!future.isFinished()) +            QApplication::processEvents(); + +        progress.setValue(1); + +        // 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("nx_inject.js"); +        nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation); +        nx_shim.setRunsOnSubFrames(true); +        web_browser_view.page()->profile()->scripts()->insert(nx_shim); + +        web_browser_view.load( +            QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() + +                 QString::fromStdString(std::string(additional_args)))); + +        progress.setValue(2); + +        render_window->hide(); +        web_browser_view.setFocus(); + +        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); + +        web_browser_view.show(); + +        progress.setValue(3); + +        QApplication::processEvents(); + +        progress.setValue(4); +    } + +    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); + +    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(QString::fromStdString(std::to_string(key_code)))); +    }; + +    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; +        } + +        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 (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 +    } + +    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(); +} + +#else + +void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { +    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); + +    LOG_INFO(Frontend, +             "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at " +             "'{}' with arguments '{}'!", +             filename, additional_args); + +    emit WebBrowserFinishedBrowsing(); +} + +#endif +  void GMainWindow::InitializeWidgets() {  #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING      ui.action_Report_Compatibility->setVisible(true); @@ -612,6 +761,7 @@ bool GMainWindow::LoadROM(const QString& filename) {      system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));      system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this)); +    system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this));      const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -1325,6 +1475,7 @@ void GMainWindow::OnStartGame() {      qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");      qRegisterMetaType<std::string>("std::string");      qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); +    qRegisterMetaType<std::string_view>("std::string_view");      connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index d560bf75b..2d705ad54 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -13,6 +13,7 @@  #include "common/common_types.h"  #include "core/core.h" +#include "core/hle/service/acc/profile_manager.h"  #include "ui_main.h"  #include "yuzu/compatibility_list.h"  #include "yuzu/hotkeys.h" @@ -26,6 +27,7 @@ class GraphicsSurfaceWidget;  class GRenderWindow;  class MicroProfileDialog;  class ProfilerWidget; +class QLabel;  class WaitTreeWidget;  enum class GameListOpenTarget; @@ -103,11 +105,16 @@ signals:      void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);      void SoftwareKeyboardFinishedCheckDialog(); +    void WebBrowserUnpackRomFS(); +    void WebBrowserFinishedBrowsing(); +  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); +  private:      void InitializeWidgets();      void InitializeDebugWidgets(); | 
