From 88b6e91530932d40a88d69deb7c463f76a4411ea Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:47:29 +0200 Subject: [PATCH] BrowserLogin: Add dedicated application mode to Rare for the webview login page Add a sub-application to Rare to launch the webview for logging into EGS. The sub-application operates similatly to the `laucher` sub-application and it is autonomous. After a successful login in returns the exchange code to the standard output to be parsed and used by the login dialog. The reason this implementation was chosen is because when pywebview uses pyqtwebengine as the GUI library, we cannot launch it through Rare as it tries to spawn a QMainWindow inside an existing event loop, which is prohibited by Qt. Despite that, EGS login page doesn't work correctly with QtWebEngine, so on linux default to the GTK backend for pywebview, and this change helps keeping applications using different toolkits separate. At this moment, spawning the sub-application blocks the execution of the main application. This change should make it easier to authenticate through Rare inside a gamescope session, such as the steam deck. --- .../components/dialogs/login/browser_login.py | 20 ++++++++-- rare/main.py | 40 +++++++++++-------- rare/webview/__init__.py | 13 ++++++ requirements-webview.txt | 5 ++- 4 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 rare/webview/__init__.py diff --git a/rare/components/dialogs/login/browser_login.py b/rare/components/dialogs/login/browser_login.py index 8b10296f..136d1876 100644 --- a/rare/components/dialogs/login/browser_login.py +++ b/rare/components/dialogs/login/browser_login.py @@ -2,14 +2,15 @@ import json from logging import getLogger from typing import Tuple -from PyQt5.QtCore import pyqtSignal, QUrl +from PyQt5.QtCore import pyqtSignal, QUrl, QProcess, pyqtSlot from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QFrame, qApp, QFormLayout, QLineEdit -from legendary.core import LegendaryCore from legendary.utils import webview_login +from rare.lgndr.core import LegendaryCore from rare.ui.components.dialogs.login.browser_login import Ui_BrowserLogin from rare.utils.misc import icon +from rare.utils.paths import get_rare_executable from rare.widgets.indicator_edit import IndicatorLineEdit, IndicatorReasonsCommon logger = getLogger("BrowserLogin") @@ -43,6 +44,7 @@ class BrowserLogin(QFrame): self.ui.open_button.clicked.connect(self.open_browser) self.sid_edit.textChanged.connect(self.changed.emit) + @pyqtSlot() def copy_link(self): clipboard = qApp.clipboard() clipboard.setText(self.login_url) @@ -79,12 +81,24 @@ class BrowserLogin(QFrame): except Exception as e: logger.warning(e) + @pyqtSlot() def open_browser(self): if not webview_login.webview_available: logger.warning("You don't have webengine installed, you will need to manually copy the authorizationCode.") QDesktopServices.openUrl(QUrl(self.login_url)) else: - if webview_login.do_webview_login(callback_code=self.core.auth_ex_token): + cmd = get_rare_executable() + ["login", self.core.get_egl_version()] + proc = QProcess(self) + proc.start(cmd[0], cmd[1:]) + proc.waitForFinished(-1) + out, err = ( + proc.readAllStandardOutput().data().decode("utf-8", "ignore"), + proc.readAllStandardError().data().decode("utf-8", "ignore") + ) + proc.deleteLater() + + if out: + self.core.auth_ex_token(out) logger.info("Successfully logged in as %s", {self.core.lgd.userdata['displayName']}) self.success.emit() else: diff --git a/rare/main.py b/rare/main.py index 5263db28..ac173cfc 100755 --- a/rare/main.py +++ b/rare/main.py @@ -57,22 +57,26 @@ def main() -> int: ) subparsers = parser.add_subparsers(title="Commands", dest="subparser") - launch_minimal_parser = subparsers.add_parser("start", aliases=["launch"]) - launch_minimal_parser.add_argument("app_name", help="AppName of the game to launch", - metavar="", action="store") - launch_minimal_parser.add_argument("--dry-run", help="Print arguments and exit", action="store_true") - launch_minimal_parser.add_argument("--offline", help="Launch game offline", - action="store_true") - launch_minimal_parser.add_argument('--wine-bin', dest='wine_bin', action='store', metavar='', - default=os.environ.get('LGDRY_WINE_BINARY', None), - help='Set WINE binary to use to launch the app') - launch_minimal_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='', - default=os.environ.get('LGDRY_WINE_PREFIX', None), - help='Set WINE prefix to use') - launch_minimal_parser.add_argument("--ask-sync-saves", help="Ask to sync cloud saves", - action="store_true") - launch_minimal_parser.add_argument("--skip-update-check", help="Do not check for updates", - action="store_true") + launch_parser = subparsers.add_parser("start", aliases=["launch"]) + launch_parser.add_argument("app_name", help="AppName of the game to launch", + metavar="", action="store") + launch_parser.add_argument("--dry-run", help="Print arguments and exit", action="store_true") + launch_parser.add_argument("--offline", help="Launch game offline", + action="store_true") + launch_parser.add_argument('--wine-bin', dest='wine_bin', action='store', metavar='', + default=os.environ.get('LGDRY_WINE_BINARY', None), + help='Set WINE binary to use to launch the app') + launch_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='', + default=os.environ.get('LGDRY_WINE_PREFIX', None), + help='Set WINE prefix to use') + launch_parser.add_argument("--ask-sync-saves", help="Ask to sync cloud saves", + action="store_true") + launch_parser.add_argument("--skip-update-check", help="Do not check for updates", + action="store_true") + + login_parser = subparsers.add_parser("login", aliases=["auth"]) + login_parser.add_argument("egl_version", help="Epic Games Launcher User Agent version", + metavar="", action="store") args = parser.parse_args() @@ -93,6 +97,10 @@ def main() -> int: print(f"Rare {__version__} Codename: {__codename__}") return 0 + if args.subparser in {"login", "auth"}: + from rare.webview import launch + return launch(args) + if args.subparser in {"start", "launch"}: from rare.launcher import launch return launch(args) diff --git a/rare/webview/__init__.py b/rare/webview/__init__.py new file mode 100644 index 00000000..7ad586c4 --- /dev/null +++ b/rare/webview/__init__.py @@ -0,0 +1,13 @@ +import sys +from argparse import Namespace + +from legendary.utils import webview_login + + +def launch(args: Namespace) -> int: + if webview_login.do_webview_login( + callback_code=sys.stdout.write, user_agent=f'EpicGamesLauncher/{args.egl_version}' + ): + return 0 + else: + return 1 diff --git a/requirements-webview.txt b/requirements-webview.txt index 0ba5b7fc..27c76260 100644 --- a/requirements-webview.txt +++ b/requirements-webview.txt @@ -1,5 +1,6 @@ -pywebview[qt]; platform_system == "Linux" -pywebview[qt]; platform_system == "FreeBSD" +# pywebview with QtWebEngine backend has issues with EGS login form, so use GTK +pywebview[gtk]; platform_system == "Linux" +pywebview[gtk]; platform_system == "FreeBSD" pythonnet>=3.0.0rc4; platform_system == "Windows" cefpython3; platform_system == "Windows" pywebview[cef]; platform_system == "Windows"