qt_web_browser.cpp 15 KB

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