web_browser.cpp 14 KB

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