qt_web_browser.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #ifdef YUZU_USE_QT_WEB_ENGINE
  4. #include <bit>
  5. #include <QApplication>
  6. #include <QKeyEvent>
  7. #include <QWebEngineProfile>
  8. #include <QWebEngineScript>
  9. #include <QWebEngineScriptCollection>
  10. #include <QWebEngineSettings>
  11. #include <QWebEngineUrlScheme>
  12. #include "core/hid/input_interpreter.h"
  13. #include "yuzu/applets/qt_web_browser_scripts.h"
  14. #endif
  15. #include "common/fs/path_util.h"
  16. #include "core/core.h"
  17. #include "input_common/drivers/keyboard.h"
  18. #include "yuzu/applets/qt_web_browser.h"
  19. #include "yuzu/main.h"
  20. #include "yuzu/util/url_request_interceptor.h"
  21. #ifdef YUZU_USE_QT_WEB_ENGINE
  22. namespace {
  23. constexpr int HIDButtonToKey(Core::HID::NpadButton button) {
  24. switch (button) {
  25. case Core::HID::NpadButton::Left:
  26. case Core::HID::NpadButton::StickLLeft:
  27. return Qt::Key_Left;
  28. case Core::HID::NpadButton::Up:
  29. case Core::HID::NpadButton::StickLUp:
  30. return Qt::Key_Up;
  31. case Core::HID::NpadButton::Right:
  32. case Core::HID::NpadButton::StickLRight:
  33. return Qt::Key_Right;
  34. case Core::HID::NpadButton::Down:
  35. case Core::HID::NpadButton::StickLDown:
  36. return Qt::Key_Down;
  37. default:
  38. return 0;
  39. }
  40. }
  41. } // Anonymous namespace
  42. QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
  43. InputCommon::InputSubsystem* input_subsystem_)
  44. : QWebEngineView(parent), input_subsystem{input_subsystem_},
  45. url_interceptor(std::make_unique<UrlRequestInterceptor>()),
  46. input_interpreter(std::make_unique<InputInterpreter>(system)),
  47. default_profile{QWebEngineProfile::defaultProfile()}, global_settings{
  48. default_profile->settings()} {
  49. default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String(
  50. Common::FS::GetYuzuPath(Common::FS::YuzuPath::YuzuDir) / "qtwebengine")));
  51. QWebEngineScript gamepad;
  52. QWebEngineScript window_nx;
  53. gamepad.setName(QStringLiteral("gamepad_script.js"));
  54. window_nx.setName(QStringLiteral("window_nx_script.js"));
  55. gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT));
  56. window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
  57. gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
  58. window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
  59. gamepad.setWorldId(QWebEngineScript::MainWorld);
  60. window_nx.setWorldId(QWebEngineScript::MainWorld);
  61. gamepad.setRunsOnSubFrames(true);
  62. window_nx.setRunsOnSubFrames(true);
  63. default_profile->scripts()->insert(gamepad);
  64. default_profile->scripts()->insert(window_nx);
  65. default_profile->setUrlRequestInterceptor(url_interceptor.get());
  66. global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
  67. global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
  68. global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
  69. global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
  70. global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true);
  71. global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false);
  72. global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto"));
  73. connect(
  74. page(), &QWebEnginePage::windowCloseRequested, page(),
  75. [this] {
  76. if (page()->url() == url_interceptor->GetRequestedURL()) {
  77. SetFinished(true);
  78. SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed);
  79. }
  80. },
  81. Qt::QueuedConnection);
  82. }
  83. QtNXWebEngineView::~QtNXWebEngineView() {
  84. SetFinished(true);
  85. StopInputThread();
  86. }
  87. void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url,
  88. const std::string& additional_args) {
  89. is_local = true;
  90. LoadExtractedFonts();
  91. FocusFirstLinkElement();
  92. SetUserAgent(UserAgent::WebApplet);
  93. SetFinished(false);
  94. SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
  95. SetLastURL("http://localhost/");
  96. StartInputThread();
  97. load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() +
  98. QString::fromStdString(additional_args)));
  99. }
  100. void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url,
  101. const std::string& additional_args) {
  102. is_local = false;
  103. FocusFirstLinkElement();
  104. SetUserAgent(UserAgent::WebApplet);
  105. SetFinished(false);
  106. SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
  107. SetLastURL("http://localhost/");
  108. StartInputThread();
  109. load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args)));
  110. }
  111. void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
  112. const QString user_agent_str = [user_agent] {
  113. switch (user_agent) {
  114. case UserAgent::WebApplet:
  115. default:
  116. return QStringLiteral("WebApplet");
  117. case UserAgent::ShopN:
  118. return QStringLiteral("ShopN");
  119. case UserAgent::LoginApplet:
  120. return QStringLiteral("LoginApplet");
  121. case UserAgent::ShareApplet:
  122. return QStringLiteral("ShareApplet");
  123. case UserAgent::LobbyApplet:
  124. return QStringLiteral("LobbyApplet");
  125. case UserAgent::WifiWebAuthApplet:
  126. return QStringLiteral("WifiWebAuthApplet");
  127. }
  128. }();
  129. QWebEngineProfile::defaultProfile()->setHttpUserAgent(
  130. QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
  131. "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
  132. .arg(user_agent_str));
  133. }
  134. bool QtNXWebEngineView::IsFinished() const {
  135. return finished;
  136. }
  137. void QtNXWebEngineView::SetFinished(bool finished_) {
  138. finished = finished_;
  139. }
  140. Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const {
  141. return exit_reason;
  142. }
  143. void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) {
  144. exit_reason = exit_reason_;
  145. }
  146. const std::string& QtNXWebEngineView::GetLastURL() const {
  147. return last_url;
  148. }
  149. void QtNXWebEngineView::SetLastURL(std::string last_url_) {
  150. last_url = std::move(last_url_);
  151. }
  152. QString QtNXWebEngineView::GetCurrentURL() const {
  153. return url_interceptor->GetRequestedURL().toString();
  154. }
  155. void QtNXWebEngineView::hide() {
  156. SetFinished(true);
  157. StopInputThread();
  158. QWidget::hide();
  159. }
  160. void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
  161. if (is_local) {
  162. input_subsystem->GetKeyboard()->PressKey(event->key());
  163. }
  164. }
  165. void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
  166. if (is_local) {
  167. input_subsystem->GetKeyboard()->ReleaseKey(event->key());
  168. }
  169. }
  170. template <Core::HID::NpadButton... T>
  171. void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
  172. const auto f = [this](Core::HID::NpadButton button) {
  173. if (input_interpreter->IsButtonPressedOnce(button)) {
  174. const auto button_index = std::countr_zero(static_cast<u64>(button));
  175. page()->runJavaScript(
  176. QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(button_index),
  177. [this, button](const QVariant& variant) {
  178. if (variant.toBool()) {
  179. switch (button) {
  180. case Core::HID::NpadButton::A:
  181. SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
  182. break;
  183. case Core::HID::NpadButton::B:
  184. SendKeyPressEvent(Qt::Key_B);
  185. break;
  186. case Core::HID::NpadButton::X:
  187. SendKeyPressEvent(Qt::Key_X);
  188. break;
  189. case Core::HID::NpadButton::Y:
  190. SendKeyPressEvent(Qt::Key_Y);
  191. break;
  192. default:
  193. break;
  194. }
  195. }
  196. });
  197. page()->runJavaScript(
  198. QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
  199. .arg(button_index));
  200. }
  201. };
  202. (f(T), ...);
  203. }
  204. template <Core::HID::NpadButton... T>
  205. void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
  206. const auto f = [this](Core::HID::NpadButton button) {
  207. if (input_interpreter->IsButtonPressedOnce(button)) {
  208. SendKeyPressEvent(HIDButtonToKey(button));
  209. }
  210. };
  211. (f(T), ...);
  212. }
  213. template <Core::HID::NpadButton... T>
  214. void QtNXWebEngineView::HandleWindowKeyButtonHold() {
  215. const auto f = [this](Core::HID::NpadButton button) {
  216. if (input_interpreter->IsButtonHeld(button)) {
  217. SendKeyPressEvent(HIDButtonToKey(button));
  218. }
  219. };
  220. (f(T), ...);
  221. }
  222. void QtNXWebEngineView::SendKeyPressEvent(int key) {
  223. if (key == 0) {
  224. return;
  225. }
  226. QCoreApplication::postEvent(focusProxy(),
  227. new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier));
  228. QCoreApplication::postEvent(focusProxy(),
  229. new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier));
  230. }
  231. void QtNXWebEngineView::StartInputThread() {
  232. if (input_thread_running) {
  233. return;
  234. }
  235. input_thread_running = true;
  236. input_thread = std::thread(&QtNXWebEngineView::InputThread, this);
  237. }
  238. void QtNXWebEngineView::StopInputThread() {
  239. if (is_local) {
  240. QWidget::releaseKeyboard();
  241. }
  242. input_thread_running = false;
  243. if (input_thread.joinable()) {
  244. input_thread.join();
  245. }
  246. }
  247. void QtNXWebEngineView::InputThread() {
  248. // Wait for 1 second before allowing any inputs to be processed.
  249. std::this_thread::sleep_for(std::chrono::seconds(1));
  250. if (is_local) {
  251. QWidget::grabKeyboard();
  252. }
  253. while (input_thread_running) {
  254. input_interpreter->PollInput();
  255. HandleWindowFooterButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
  256. Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
  257. Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
  258. HandleWindowKeyButtonPressedOnce<
  259. Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
  260. Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
  261. Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
  262. Core::HID::NpadButton::StickLDown>();
  263. HandleWindowKeyButtonHold<
  264. Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
  265. Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
  266. Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
  267. Core::HID::NpadButton::StickLDown>();
  268. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  269. }
  270. }
  271. void QtNXWebEngineView::LoadExtractedFonts() {
  272. QWebEngineScript nx_font_css;
  273. QWebEngineScript load_nx_font;
  274. auto fonts_dir_str = Common::FS::PathToUTF8String(
  275. Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/");
  276. std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/');
  277. const auto fonts_dir = QString::fromStdString(fonts_dir_str);
  278. nx_font_css.setName(QStringLiteral("nx_font_css.js"));
  279. load_nx_font.setName(QStringLiteral("load_nx_font.js"));
  280. nx_font_css.setSourceCode(
  281. QString::fromStdString(NX_FONT_CSS)
  282. .arg(fonts_dir + QStringLiteral("FontStandard.ttf"))
  283. .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf"))
  284. .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf"))
  285. .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf"))
  286. .arg(fonts_dir + QStringLiteral("FontKorean.ttf"))
  287. .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf"))
  288. .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf")));
  289. load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT));
  290. nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
  291. load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
  292. nx_font_css.setWorldId(QWebEngineScript::MainWorld);
  293. load_nx_font.setWorldId(QWebEngineScript::MainWorld);
  294. nx_font_css.setRunsOnSubFrames(true);
  295. load_nx_font.setRunsOnSubFrames(true);
  296. default_profile->scripts()->insert(nx_font_css);
  297. default_profile->scripts()->insert(load_nx_font);
  298. connect(
  299. url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(),
  300. [this] {
  301. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  302. page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT));
  303. },
  304. Qt::QueuedConnection);
  305. }
  306. void QtNXWebEngineView::FocusFirstLinkElement() {
  307. QWebEngineScript focus_link_element;
  308. focus_link_element.setName(QStringLiteral("focus_link_element.js"));
  309. focus_link_element.setSourceCode(QString::fromStdString(FOCUS_LINK_ELEMENT_SCRIPT));
  310. focus_link_element.setWorldId(QWebEngineScript::MainWorld);
  311. focus_link_element.setInjectionPoint(QWebEngineScript::Deferred);
  312. focus_link_element.setRunsOnSubFrames(true);
  313. default_profile->scripts()->insert(focus_link_element);
  314. }
  315. #endif
  316. QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
  317. connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
  318. &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
  319. connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
  320. &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
  321. connect(&main_window, &GMainWindow::WebBrowserClosed, this,
  322. &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
  323. }
  324. QtWebBrowser::~QtWebBrowser() = default;
  325. void QtWebBrowser::OpenLocalWebPage(
  326. const std::string& local_url, std::function<void()> extract_romfs_callback_,
  327. std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
  328. extract_romfs_callback = std::move(extract_romfs_callback_);
  329. callback = std::move(callback_);
  330. const auto index = local_url.find('?');
  331. if (index == std::string::npos) {
  332. emit MainWindowOpenWebPage(local_url, "", true);
  333. } else {
  334. emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
  335. }
  336. }
  337. void QtWebBrowser::OpenExternalWebPage(
  338. const std::string& external_url,
  339. std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
  340. callback = std::move(callback_);
  341. const auto index = external_url.find('?');
  342. if (index == std::string::npos) {
  343. emit MainWindowOpenWebPage(external_url, "", false);
  344. } else {
  345. emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
  346. false);
  347. }
  348. }
  349. void QtWebBrowser::MainWindowExtractOfflineRomFS() {
  350. extract_romfs_callback();
  351. }
  352. void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
  353. std::string last_url) {
  354. callback(exit_reason, last_url);
  355. }