Merge pull request #1939 from DarkLordZach/web-applet
applets: Implement HLE web browser applet (LibAppletOff)
This commit is contained in:
commit
83e8ad2331
30 changed files with 1413 additions and 623 deletions
|
@ -1,12 +1,12 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget cmake ninja-build ccache
|
||||
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev qtwebengine5-dev wget cmake ninja-build ccache
|
||||
|
||||
cd /yuzu
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
|
||||
ninja
|
||||
|
||||
ccache -s
|
||||
|
|
|
@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
|||
|
||||
mkdir build && cd build
|
||||
cmake --version
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
|
||||
make -j4
|
||||
|
||||
ccache -s
|
||||
|
|
|
@ -19,6 +19,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
|||
|
||||
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
@ -302,7 +304,7 @@ endif()
|
|||
if (ENABLE_QT)
|
||||
if (YUZU_USE_BUNDLED_QT)
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
|
||||
set(QT_VER qt-5.10.0-msvc2015_64)
|
||||
set(QT_VER qt-5.12.0-msvc2017_64)
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
|
||||
endif()
|
||||
|
@ -319,6 +321,10 @@ if (ENABLE_QT)
|
|||
endif()
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets ${QT_PREFIX_HINT})
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
# Platform-specific library requirements
|
||||
|
|
|
@ -5,6 +5,7 @@ function(copy_yuzu_Qt5_deps target_dir)
|
|||
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
|
||||
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
|
||||
set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
|
||||
set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/")
|
||||
set(PLATFORMS ${DLL_DEST}platforms/)
|
||||
set(STYLES ${DLL_DEST}styles/)
|
||||
set(IMAGEFORMATS ${DLL_DEST}imageformats/)
|
||||
|
@ -17,6 +18,31 @@ function(copy_yuzu_Qt5_deps target_dir)
|
|||
Qt5OpenGL$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Widgets$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
|
||||
Qt5Network$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Positioning$<$<CONFIG:Debug>:d>.*
|
||||
Qt5PrintSupport$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Qml$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Quick$<$<CONFIG:Debug>:d>.*
|
||||
Qt5QuickWidgets$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebChannel$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebEngine$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebEngineCore$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebEngineWidgets$<$<CONFIG:Debug>:d>.*
|
||||
QtWebEngineProcess$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
|
||||
windows_copy_files(${target_dir} ${Qt5_RESOURCES_DIR} ${DLL_DEST}
|
||||
qtwebengine_resources.pak
|
||||
qtwebengine_devtools_resources.pak
|
||||
qtwebengine_resources_100p.pak
|
||||
qtwebengine_resources_200p.pak
|
||||
icudtl.dat
|
||||
)
|
||||
endif ()
|
||||
|
||||
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)
|
||||
|
|
|
@ -42,7 +42,7 @@ before_build:
|
|||
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
|
||||
if ($env:BUILD_TYPE -eq 'msvc') {
|
||||
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
|
||||
} else {
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ after_build:
|
|||
Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
|
||||
rm "$RELEASE_DIST\*.exe"
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Copy-Item .\license.txt -Destination $RELEASE_DIST
|
||||
Copy-Item .\README.md -Destination $RELEASE_DIST
|
||||
7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
|
||||
|
|
|
@ -88,6 +88,8 @@ add_library(core STATIC
|
|||
frontend/applets/profile_select.h
|
||||
frontend/applets/software_keyboard.cpp
|
||||
frontend/applets/software_keyboard.h
|
||||
frontend/applets/web_browser.cpp
|
||||
frontend/applets/web_browser.h
|
||||
frontend/emu_window.cpp
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
|
@ -173,6 +175,8 @@ add_library(core STATIC
|
|||
hle/service/am/applets/software_keyboard.h
|
||||
hle/service/am/applets/stub_applet.cpp
|
||||
hle/service/am/applets/stub_applet.h
|
||||
hle/service/am/applets/web_browser.cpp
|
||||
hle/service/am/applets/web_browser.h
|
||||
hle/service/am/idle.cpp
|
||||
hle/service/am/idle.h
|
||||
hle/service/am/omm.cpp
|
||||
|
|
|
@ -31,7 +31,9 @@
|
|||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
#include "frontend/applets/software_keyboard.h"
|
||||
#include "frontend/applets/web_browser.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
@ -103,6 +105,8 @@ struct System::Impl {
|
|||
profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
|
||||
if (software_keyboard == nullptr)
|
||||
software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
|
||||
if (web_browser == nullptr)
|
||||
web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
|
@ -199,6 +203,11 @@ struct System::Impl {
|
|||
// Close app loader
|
||||
app_loader.reset();
|
||||
|
||||
// Clear all applets
|
||||
profile_selector.reset();
|
||||
software_keyboard.reset();
|
||||
web_browser.reset();
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
|
@ -233,6 +242,7 @@ struct System::Impl {
|
|||
/// Frontend applets
|
||||
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
|
||||
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
|
||||
std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser;
|
||||
|
||||
/// Service manager
|
||||
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
||||
|
@ -443,6 +453,14 @@ const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() cons
|
|||
return *impl->software_keyboard;
|
||||
}
|
||||
|
||||
void System::SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet) {
|
||||
impl->web_browser = std::move(applet);
|
||||
}
|
||||
|
||||
const Core::Frontend::WebBrowserApplet& System::GetWebBrowser() const {
|
||||
return *impl->web_browser;
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
|
||||
return impl->Init(*this, emu_window);
|
||||
}
|
||||
|
|
|
@ -11,11 +11,12 @@
|
|||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
class ProfileSelectApplet;
|
||||
class SoftwareKeyboardApplet;
|
||||
class WebBrowserApplet;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
|
@ -250,6 +251,10 @@ public:
|
|||
|
||||
const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
|
||||
|
||||
void SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet);
|
||||
|
||||
const Core::Frontend::WebBrowserApplet& GetWebBrowser() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
|
|
|
@ -119,6 +119,9 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
|
|||
|
||||
VirtualDir out = std::move(root);
|
||||
|
||||
if (type == RomFSExtractionType::SingleDiscard)
|
||||
return out->GetSubdirectories().front();
|
||||
|
||||
while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
|
||||
if (out->GetSubdirectories().front()->GetName() == "data" &&
|
||||
type == RomFSExtractionType::Truncated)
|
||||
|
|
|
@ -33,8 +33,9 @@ struct IVFCHeader {
|
|||
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
|
||||
|
||||
enum class RomFSExtractionType {
|
||||
Full, // Includes data directory
|
||||
Truncated, // Traverses into data directory
|
||||
Full, // Includes data directory
|
||||
Truncated, // Traverses into data directory
|
||||
SingleDiscard, // Traverses into the first subdirectory of root
|
||||
};
|
||||
|
||||
// Converts a RomFS binary blob to VFS Filesystem
|
||||
|
|
24
src/core/frontend/applets/web_browser.cpp
Normal file
24
src/core/frontend/applets/web_browser.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
WebBrowserApplet::~WebBrowserApplet() = default;
|
||||
|
||||
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
|
||||
|
||||
void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
|
||||
std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) const {
|
||||
LOG_INFO(Service_AM,
|
||||
"(STUBBED) called - No suitable web browser implementation found to open website page "
|
||||
"at '{}'!",
|
||||
filename);
|
||||
finished_callback();
|
||||
}
|
||||
|
||||
} // namespace Core::Frontend
|
28
src/core/frontend/applets/web_browser.h
Normal file
28
src/core/frontend/applets/web_browser.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
class WebBrowserApplet {
|
||||
public:
|
||||
virtual ~WebBrowserApplet();
|
||||
|
||||
virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) const = 0;
|
||||
};
|
||||
|
||||
class DefaultWebBrowserApplet final : public WebBrowserApplet {
|
||||
public:
|
||||
~DefaultWebBrowserApplet() override;
|
||||
|
||||
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) const override;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
|
@ -23,6 +23,7 @@
|
|||
#include "core/hle/service/am/applets/profile_select.h"
|
||||
#include "core/hle/service/am/applets/software_keyboard.h"
|
||||
#include "core/hle/service/am/applets/stub_applet.h"
|
||||
#include "core/hle/service/am/applets/web_browser.h"
|
||||
#include "core/hle/service/am/idle.h"
|
||||
#include "core/hle/service/am/omm.h"
|
||||
#include "core/hle/service/am/spsm.h"
|
||||
|
@ -44,6 +45,7 @@ constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
|
|||
enum class AppletId : u32 {
|
||||
ProfileSelect = 0x10,
|
||||
SoftwareKeyboard = 0x11,
|
||||
LibAppletOff = 0x17,
|
||||
};
|
||||
|
||||
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
|
||||
|
@ -730,10 +732,10 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
|
|||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
|
||||
const std::vector<u8> data{ctx.ReadBuffer()};
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
|
||||
|
||||
if (data.size() > backing.buffer.size() - offset) {
|
||||
LOG_ERROR(Service_AM,
|
||||
"offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
|
||||
|
@ -753,10 +755,10 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
|
|||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
|
||||
const std::size_t size{ctx.GetWriteBufferSize()};
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
|
||||
|
||||
if (size > backing.buffer.size() - offset) {
|
||||
LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
|
||||
backing.buffer.size(), size, offset);
|
||||
|
@ -791,6 +793,8 @@ static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
|
|||
return std::make_shared<Applets::ProfileSelect>();
|
||||
case AppletId::SoftwareKeyboard:
|
||||
return std::make_shared<Applets::SoftwareKeyboard>();
|
||||
case AppletId::LibAppletOff:
|
||||
return std::make_shared<Applets::WebBrowser>();
|
||||
default:
|
||||
LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!",
|
||||
static_cast<u32>(id));
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "common/assert.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/profile_select.h"
|
||||
|
||||
|
|
184
src/core/hle/service/am/applets/web_browser.cpp
Normal file
184
src/core/hle/service/am/applets/web_browser.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/am/applets/web_browser.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
|
||||
// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
|
||||
// but some may be worth an implementation.
|
||||
constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
|
||||
|
||||
struct WebBufferHeader {
|
||||
u16 count;
|
||||
INSERT_PADDING_BYTES(6);
|
||||
};
|
||||
static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
|
||||
|
||||
struct WebArgumentHeader {
|
||||
u16 type;
|
||||
u16 size;
|
||||
u32 offset;
|
||||
};
|
||||
static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
|
||||
|
||||
struct WebArgumentResult {
|
||||
u32 result_code;
|
||||
std::array<char, 0x1000> last_url;
|
||||
u64 last_url_size;
|
||||
};
|
||||
static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
|
||||
|
||||
static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
|
||||
WebBufferHeader header;
|
||||
ASSERT(sizeof(WebBufferHeader) <= data.size());
|
||||
std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
|
||||
|
||||
u64 offset = sizeof(WebBufferHeader);
|
||||
for (u16 i = 0; i < header.count; ++i) {
|
||||
WebArgumentHeader arg;
|
||||
ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
|
||||
std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
|
||||
offset += sizeof(WebArgumentHeader);
|
||||
|
||||
if (arg.type == type) {
|
||||
std::vector<u8> out(arg.size);
|
||||
offset += arg.offset;
|
||||
ASSERT(offset + arg.size <= data.size());
|
||||
std::memcpy(out.data(), data.data() + offset, out.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
offset += arg.offset + arg.size;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static FileSys::VirtualFile GetManualRomFS() {
|
||||
auto& loader{Core::System::GetInstance().GetAppLoader()};
|
||||
|
||||
FileSys::VirtualFile out;
|
||||
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
|
||||
return out;
|
||||
|
||||
const auto& installed{FileSystem::GetUnionContents()};
|
||||
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
|
||||
FileSys::ContentRecordType::Manual);
|
||||
|
||||
if (res != nullptr)
|
||||
return res->GetRomFS();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WebBrowser::WebBrowser() = default;
|
||||
|
||||
WebBrowser::~WebBrowser() = default;
|
||||
|
||||
void WebBrowser::Initialize() {
|
||||
Applet::Initialize();
|
||||
|
||||
complete = false;
|
||||
temporary_dir.clear();
|
||||
filename.clear();
|
||||
status = RESULT_SUCCESS;
|
||||
|
||||
const auto web_arg_storage = broker.PopNormalDataToApplet();
|
||||
ASSERT(web_arg_storage != nullptr);
|
||||
const auto& web_arg = web_arg_storage->GetData();
|
||||
|
||||
const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
|
||||
filename = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(url_data.data()), url_data.size());
|
||||
|
||||
temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
|
||||
"web_applet_manual",
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||
|
||||
manual_romfs = GetManualRomFS();
|
||||
if (manual_romfs == nullptr) {
|
||||
status = ResultCode(-1);
|
||||
LOG_ERROR(Service_AM, "Failed to find manual for current process!");
|
||||
}
|
||||
|
||||
filename =
|
||||
FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
}
|
||||
|
||||
bool WebBrowser::TransactionComplete() const {
|
||||
return complete;
|
||||
}
|
||||
|
||||
ResultCode WebBrowser::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteInteractive() {
|
||||
UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
|
||||
}
|
||||
|
||||
void WebBrowser::Execute() {
|
||||
if (complete)
|
||||
return;
|
||||
|
||||
if (status != RESULT_SUCCESS) {
|
||||
complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& frontend{Core::System::GetInstance().GetWebBrowser()};
|
||||
|
||||
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
|
||||
}
|
||||
|
||||
void WebBrowser::UnpackRomFS() {
|
||||
if (unpacked)
|
||||
return;
|
||||
|
||||
ASSERT(manual_romfs != nullptr);
|
||||
const auto dir =
|
||||
FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
||||
const auto& vfs{Core::System::GetInstance().GetFilesystem()};
|
||||
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
|
||||
FileSys::VfsRawCopyD(dir, temp_dir);
|
||||
|
||||
unpacked = true;
|
||||
}
|
||||
|
||||
void WebBrowser::Finalize() {
|
||||
complete = true;
|
||||
|
||||
WebArgumentResult out{};
|
||||
out.result_code = 0;
|
||||
out.last_url_size = 0;
|
||||
|
||||
std::vector<u8> data(sizeof(WebArgumentResult));
|
||||
std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
|
||||
|
||||
broker.PushNormalDataFromApplet(IStorage{data});
|
||||
broker.SignalStateChanged();
|
||||
|
||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||
}
|
||||
|
||||
} // namespace Service::AM::Applets
|
44
src/core/hle/service/am/applets/web_browser.h
Normal file
44
src/core/hle/service/am/applets/web_browser.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
class WebBrowser final : public Applet {
|
||||
public:
|
||||
WebBrowser();
|
||||
~WebBrowser() override;
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
bool TransactionComplete() const override;
|
||||
ResultCode GetStatus() const override;
|
||||
void ExecuteInteractive() override;
|
||||
void Execute() override;
|
||||
|
||||
// Callback to be fired when the frontend needs the manual RomFS unpacked to temporary
|
||||
// directory. This is a blocking call and may take a while as some manuals can be up to 100MB in
|
||||
// size. Attempting to access files at filename before invocation is likely to not work.
|
||||
void UnpackRomFS();
|
||||
|
||||
// Callback to be fired when the frontend is finished browsing. This will delete the temporary
|
||||
// manual RomFS extracted files, so ensure this is only called at actual finalization.
|
||||
void Finalize();
|
||||
|
||||
private:
|
||||
bool complete = false;
|
||||
bool unpacked = false;
|
||||
ResultCode status = RESULT_SUCCESS;
|
||||
|
||||
FileSys::VirtualFile manual_romfs;
|
||||
std::string temporary_dir;
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
} // namespace Service::AM::Applets
|
|
@ -410,6 +410,8 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
|
|||
libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw;
|
||||
libnx_entry.pad.l_stick = pad_state.l_stick;
|
||||
libnx_entry.pad.r_stick = pad_state.r_stick;
|
||||
|
||||
press_state |= static_cast<u32>(pad_state.pad_states.raw);
|
||||
}
|
||||
std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
|
||||
shared_memory_entries.size() * sizeof(NPadEntry));
|
||||
|
@ -636,6 +638,10 @@ void Controller_NPad::ClearAllControllers() {
|
|||
});
|
||||
}
|
||||
|
||||
u32 Controller_NPad::GetAndResetPressState() {
|
||||
return std::exchange(press_state, 0);
|
||||
}
|
||||
|
||||
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
|
||||
const bool support_handheld =
|
||||
std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) !=
|
||||
|
|
|
@ -124,6 +124,10 @@ public:
|
|||
void ConnectAllDisconnectedControllers();
|
||||
void ClearAllControllers();
|
||||
|
||||
// Logical OR for all buttons presses on all controllers
|
||||
// Specifically for cheat engine and other features.
|
||||
u32 GetAndResetPressState();
|
||||
|
||||
static std::size_t NPadIdToIndex(u32 npad_id);
|
||||
static u32 IndexToNPad(std::size_t index);
|
||||
|
||||
|
@ -292,6 +296,8 @@ private:
|
|||
bool is_connected;
|
||||
};
|
||||
|
||||
u32 press_state{};
|
||||
|
||||
NPadType style{};
|
||||
std::array<NPadEntry, 10> shared_memory_entries{};
|
||||
std::array<
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,12 +4,122 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "controllers/controller_base.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
struct EventType;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class SharedMemory;
|
||||
}
|
||||
|
||||
namespace SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
enum class HidController : std::size_t {
|
||||
DebugPad,
|
||||
Touchscreen,
|
||||
Mouse,
|
||||
Keyboard,
|
||||
XPad,
|
||||
Unknown1,
|
||||
Unknown2,
|
||||
Unknown3,
|
||||
SixAxisSensor,
|
||||
NPad,
|
||||
Gesture,
|
||||
|
||||
MaxControllers,
|
||||
};
|
||||
|
||||
class IAppletResource final : public ServiceFramework<IAppletResource> {
|
||||
public:
|
||||
IAppletResource();
|
||||
~IAppletResource() override;
|
||||
|
||||
void ActivateController(HidController controller);
|
||||
void DeactivateController(HidController controller);
|
||||
|
||||
template <typename T>
|
||||
T& GetController(HidController controller) {
|
||||
return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& GetController(HidController controller) const {
|
||||
return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void MakeController(HidController controller) {
|
||||
controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
|
||||
}
|
||||
|
||||
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
|
||||
void UpdateControllers(u64 userdata, int cycles_late);
|
||||
|
||||
Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
|
||||
|
||||
CoreTiming::EventType* pad_update_event;
|
||||
|
||||
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
|
||||
controllers{};
|
||||
};
|
||||
|
||||
class Hid final : public ServiceFramework<Hid> {
|
||||
public:
|
||||
Hid();
|
||||
~Hid() override;
|
||||
|
||||
std::shared_ptr<IAppletResource> GetAppletResource();
|
||||
|
||||
private:
|
||||
void CreateAppletResource(Kernel::HLERequestContext& ctx);
|
||||
void ActivateXpad(Kernel::HLERequestContext& ctx);
|
||||
void ActivateDebugPad(Kernel::HLERequestContext& ctx);
|
||||
void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
|
||||
void ActivateMouse(Kernel::HLERequestContext& ctx);
|
||||
void ActivateKeyboard(Kernel::HLERequestContext& ctx);
|
||||
void ActivateGesture(Kernel::HLERequestContext& ctx);
|
||||
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
|
||||
void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
|
||||
void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
|
||||
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
|
||||
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
|
||||
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
|
||||
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
|
||||
void ActivateNpad(Kernel::HLERequestContext& ctx);
|
||||
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
|
||||
void DisconnectNpad(Kernel::HLERequestContext& ctx);
|
||||
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
|
||||
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
|
||||
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
|
||||
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
|
||||
void SendVibrationValue(Kernel::HLERequestContext& ctx);
|
||||
void SendVibrationValues(Kernel::HLERequestContext& ctx);
|
||||
void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx);
|
||||
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
|
||||
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
|
||||
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
|
||||
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
|
||||
void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
|
||||
void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
|
||||
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
|
||||
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<IAppletResource> applet_resource;
|
||||
};
|
||||
|
||||
/// Reload input devices. Used when input configuration changed
|
||||
void ReloadInputDevices();
|
||||
|
||||
|
|
|
@ -259,6 +259,15 @@ public:
|
|||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RomFS of the manual of the application
|
||||
* @param file The raw manual RomFS of the game
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
virtual ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) {
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
protected:
|
||||
FileSys::VirtualFile file;
|
||||
bool is_loaded = false;
|
||||
|
|
|
@ -158,4 +158,12 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) {
|
|||
nacp = *nacp_file;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) {
|
||||
const auto nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Manual);
|
||||
if (nsp->GetStatus() != ResultStatus::Success || nca == nullptr)
|
||||
return ResultStatus::ErrorNoRomFS;
|
||||
file = nca->GetRomFS();
|
||||
return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success;
|
||||
}
|
||||
} // namespace Loader
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
ResultStatus ReadControlData(FileSys::NACP& nacp) override;
|
||||
ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileSys::NSP> nsp;
|
||||
|
|
|
@ -128,4 +128,13 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) {
|
|||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) {
|
||||
const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(),
|
||||
FileSys::ContentRecordType::Manual);
|
||||
if (xci->GetStatus() != ResultStatus::Success || nca == nullptr)
|
||||
return ResultStatus::ErrorXCIMissingPartition;
|
||||
file = nca->GetRomFS();
|
||||
return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success;
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
ResultStatus ReadControlData(FileSys::NACP& control) override;
|
||||
ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileSys::XCI> xci;
|
||||
|
|
|
@ -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()
|
||||
|
|
113
src/yuzu/applets/web_browser.cpp
Normal file
113
src/yuzu/applets/web_browser.cpp
Normal file
|
@ -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();
|
||||
}
|
53
src/yuzu/applets/web_browser.h
Normal file
53
src/yuzu/applets/web_browser.h
Normal file
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue