Skip to content

Commit

Permalink
Merge pull request #408 from loathingKernel/develop
Browse files Browse the repository at this point in the history
Steam shortcuts, Discord RPC, MangoHud
  • Loading branch information
loathingKernel committed May 31, 2024
2 parents 7203126 + a33821e commit 8fc92a3
Show file tree
Hide file tree
Showing 50 changed files with 1,315 additions and 819 deletions.
103 changes: 65 additions & 38 deletions rare/commands/launcher/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from signal import signal, SIGINT, SIGTERM, strsignal
from typing import Optional

from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, QRunnable, QThreadPool, QSettings, Qt, pyqtSlot
from PyQt5 import sip
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, QRunnable, QThreadPool, QSettings, Qt, pyqtSlot, QTimer
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
from PyQt5.QtWidgets import QApplication
Expand Down Expand Up @@ -78,10 +79,13 @@ def prepare_launch(self, args: InitArgs) -> Optional[LaunchArgs]:
proc.setProcessEnvironment(launch_args.environment)
self.signals.pre_launch_command_started.emit()
pre_launch_command = shlex.split(launch_args.pre_launch_command)
# self.logger.debug("Executing prelaunch command %s, %s", pre_launch_command[0], pre_launch_command[1:])
proc.start(pre_launch_command[0], pre_launch_command[1:])
self.logger.debug("Running pre-launch command %s, %s", pre_launch_command[0], pre_launch_command[1:])
if launch_args.pre_launch_wait:
proc.start(pre_launch_command[0], pre_launch_command[1:])
self.logger.debug("Waiting for pre-launch command to finish")
proc.waitForFinished(-1)
else:
proc.startDetached(pre_launch_command[0], pre_launch_command[1:])
return launch_args


Expand Down Expand Up @@ -149,7 +153,10 @@ def __init__(self, args: InitArgs):
language = self.settings.value(*options.language)
self.load_translator(language)

if QSettings(self).value(*options.log_games):
if (
QSettings(self).value(*options.log_games)
or (game.app_name in DETACHED_APP_NAMES and platform.system() == "Windows")
):
self.console = ConsoleDialog(game.app_title)
self.console.show()

Expand All @@ -172,6 +179,10 @@ def __init__(self, args: InitArgs):
self.success = True
self.start_time = time.time()

# This launches the application after it has been instantiated.
# The timer's signal will be serviced once we call `exec()` on the application
QTimer.singleShot(0, self.start)

@pyqtSlot()
def __proc_log_stdout(self):
self.console.log_stdout(
Expand Down Expand Up @@ -281,9 +292,31 @@ def launch_game(self, args: LaunchArgs):
self.console.set_env(args.environment)
self.start_time = time.time()

if self.args.dry_run:
self.logger.info("Dry run %s (%s)", self.rgame.app_title, self.rgame.app_name)
self.logger.info("%s %s", args.executable, " ".join(args.arguments))
if self.console:
self.console.log(f"Dry run {self.rgame.app_title} ({self.rgame.app_name})")
self.console.log(f"{shlex.join([args.executable] + args.arguments)}")
self.console.accept_close = True
print(shlex.join([args.executable] + args.arguments))
self.stop()
return

if args.is_origin_game:
QDesktopServices.openUrl(QUrl(args.executable))
self.stop() # stop because it is no subprocess
self.stop() # stop because it is not a subprocess
return

self.logger.debug("Launch command %s, %s", args.executable, " ".join(args.arguments))
self.logger.debug("Working directory %s", args.working_directory)

if self.rgame.app_name in DETACHED_APP_NAMES and platform.system() == "Windows":
if self.console:
self.console.log(f"Launching as a detached process")
subprocess.Popen([args.executable] + args.arguments, cwd=args.working_directory,
env={i: args.environment.value(i) for i in args.environment.keys()})
self.stop() # stop because we do not attach to the output
return

if args.working_directory:
Expand All @@ -296,23 +329,6 @@ def launch_game(self, args: LaunchArgs):
new_state=StateChangedModel.States.started
)
))
if self.rgame.app_name in DETACHED_APP_NAMES and platform.system() == "Windows":
self.game_process.deleteLater()
if self.console:
self.console.log("Launching game as a detached process")
subprocess.Popen([args.executable] + args.arguments, cwd=args.working_directory,
env={i: args.environment.value(i) for i in args.environment.keys()})
self.stop()
return
if self.args.dry_run:
self.logger.info("Dry run activated")
if self.console:
self.console.log(f"{args.executable} {' '.join(args.arguments)}")
self.console.log(f"Do not start {self.rgame.app_name}")
self.console.accept_close = True
print(args.executable, " ".join(args.arguments))
self.stop()
return
# self.logger.debug("Executing prelaunch command %s, %s", args.executable, args.arguments)
self.game_process.start(args.executable, args.arguments)

Expand Down Expand Up @@ -359,23 +375,22 @@ def __sync_ready(self, action: CloudSyncDialogResult):
self.console.log("Uploading saves...")
self.start_prepare(action)

def start(self, args: InitArgs):
if not args.offline:
def start(self):
if not self.args.offline:
try:
if not self.core.login():
raise ValueError("You are not logged in")
except ValueError:
# automatically launch offline if available
self.logger.error("Not logged in. Trying to launch the game in offline mode")
args.offline = True
self.args.offline = True

if not args.offline and self.rgame.auto_sync_saves:
if not self.args.offline and self.rgame.auto_sync_saves:
self.logger.info("Start sync worker")
worker = SyncCheckWorker(self.core, self.rgame)
worker.signals.error_occurred.connect(self.error_occurred)
worker.signals.sync_state_ready.connect(self.sync_ready)
QThreadPool.globalInstance().start(worker)
return
else:
self.start_prepare()

Expand All @@ -388,20 +403,26 @@ def stop(self):
self.game_process.finished.disconnect()
if self.game_process.receivers(self.game_process.errorOccurred):
self.game_process.errorOccurred.disconnect()
except TypeError as e:
self.logger.error(f"Failed to disconnect process signals: {e}")
except (TypeError, RuntimeError) as e:
self.logger.error("Failed to disconnect process signals: %s", e)

if self.game_process.state() != QProcess.NotRunning:
self.game_process.kill()
exit_code = self.game_process.exitCode()
self.game_process.deleteLater()

self.logger.info("Stopping server")
self.logger.info("Stopping server %s", self.server.socketDescriptor())
try:
self.server.close()
self.server.deleteLater()
except RuntimeError:
pass
except RuntimeError as e:
self.logger.error("Error occured while stopping server: %s", e)

self.processEvents()
if not self.console:
self.exit()
self.exit(exit_code)
else:
self.console.on_process_exit(self.rgame.app_name, 0)
self.console.on_process_exit(self.rgame.app_title, exit_code)


def launch(args: Namespace) -> int:
Expand All @@ -416,7 +437,7 @@ def launch(args: Namespace) -> int:
# This prevents ghost QLocalSockets, which block the name, which makes it unable to start
# No handling for SIGKILL
def sighandler(s, frame):
app.logger.info(f"{strsignal(s)} received. Stopping")
app.logger.info("%s received. Stopping", strsignal(s))
app.stop()
app.exit(1)
signal(SIGINT, sighandler)
Expand All @@ -426,7 +447,13 @@ def sighandler(s, frame):
app.stop()
app.exit(1)
return 1
app.start(args)
# app.exit_app.connect(lambda: app.exit(0))

return app.exec_()
try:
exit_code = app.exec()
except Exception as e:
app.logger.error("Unhandled error %s", e)
exit_code = 1
finally:
if not sip.isdeleted(app.server):
app.server.close()
return exit_code
10 changes: 4 additions & 6 deletions rare/commands/launcher/lgd_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import platform
import shutil
from argparse import Namespace
from dataclasses import dataclass
from dataclasses import dataclass, field
from logging import getLogger
from typing import List

Expand Down Expand Up @@ -43,8 +43,8 @@ def from_argparse(cls, args):
@dataclass
class LaunchArgs:
executable: str = ""
arguments: List[str] = None
working_directory: str = None
arguments: List[str] = field(default_factory=list)
working_directory: str = ""
environment: QProcessEnvironment = None
pre_launch_command: str = ""
pre_launch_wait: bool = False
Expand Down Expand Up @@ -129,9 +129,7 @@ def get_game_params(rgame: RareGameSlim, args: InitArgs, launch_args: LaunchArgs
launch_args.environment.insert(name, value)

full_params.extend(params.launch_command)
full_params.append(
os.path.join(params.game_directory, params.game_executable)
)
full_params.append(os.path.join(params.game_directory, params.game_executable))
full_params.extend(params.game_parameters)
full_params.extend(params.egl_parameters)
full_params.extend(params.user_parameters)
Expand Down
4 changes: 2 additions & 2 deletions rare/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import shutil
from argparse import Namespace
from datetime import datetime, timezone
from datetime import datetime, timezone, UTC
from typing import Optional

import requests.exceptions
Expand Down Expand Up @@ -60,7 +60,7 @@ def __init__(self, args: Namespace):

def poke_timer(self):
dt_exp = datetime.fromisoformat(self.core.lgd.userdata['expires_at'][:-1]).replace(tzinfo=timezone.utc)
dt_now = datetime.utcnow().replace(tzinfo=timezone.utc)
dt_now = datetime.now(UTC)
td = abs(dt_exp - dt_now)
self.relogin_timer.start(int(td.total_seconds() - 60) * 1000)
self.logger.info(f"Renewed session expires at {self.core.lgd.userdata['expires_at']}")
Expand Down
4 changes: 2 additions & 2 deletions rare/components/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ def __init__(self, parent=None):

if not self.args.offline:
try:
from rare.utils.rpc import DiscordRPC
from rare.utils.discord_rpc import DiscordRPC

self.rpc = DiscordRPC()
self.discord_rpc = DiscordRPC()
except ModuleNotFoundError:
logger.warning("Discord RPC module not found")

Expand Down
4 changes: 2 additions & 2 deletions rare/components/tabs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rare.utils.misc import qta_icon, ExitCodes
from .account import AccountWidget
from .downloads import DownloadsTab
from .games import GamesTab
from .games import GamesLibrary
from .settings import SettingsTab
from .settings.debug import DebugSettings
from .store import StoreTab
Expand All @@ -28,7 +28,7 @@ def __init__(self, parent):
self.setTabBar(self.tab_bar)

# Generate Tabs
self.games_tab = GamesTab(self)
self.games_tab = GamesLibrary(self)
self.games_index = self.addTab(self.games_tab, self.tr("Games"))

# Downloads Tab after Games Tab to use populated RareCore games list
Expand Down
7 changes: 4 additions & 3 deletions rare/components/tabs/downloads/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from rare.components.dialogs.uninstall_dialog import UninstallDialog
from rare.lgndr.models.downloading import UIUpdate
from rare.models.game import RareGame
from rare.models.image import ImageSize
from rare.models.install import InstallOptionsModel, InstallQueueItemModel, UninstallOptionsModel
from rare.models.options import options
from rare.shared import RareCore
Expand Down Expand Up @@ -209,9 +210,9 @@ def __start_download(self, item: InstallQueueItemModel):
self.__thread = dl_thread
self.download_widget.ui.kill_button.setDisabled(False)
self.download_widget.ui.dl_name.setText(item.download.game.app_title)
self.download_widget.setPixmap(
RareCore.instance().image_manager().get_pixmap(rgame.app_name, True)
)
self.download_widget.setPixmap(self.rcore.image_manager().get_pixmap(
rgame.app_name, ImageSize.Wide, True
))

self.signals.application.notify.emit(
self.tr("Downloads"),
Expand Down
6 changes: 3 additions & 3 deletions rare/components/tabs/downloads/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, parent=None):

def prepare_pixmap(self, pixmap: QPixmap) -> QPixmap:
device: QImage = QImage(
pixmap.size().width() * 3,
pixmap.size().width() * 1,
int(self.sizeHint().height() * pixmap.devicePixelRatioF()) + 1,
QImage.Format_ARGB32_Premultiplied,
)
Expand All @@ -38,9 +38,9 @@ def prepare_pixmap(self, pixmap: QPixmap) -> QPixmap:
painter.fillRect(device.rect(), brush)
# the gradient could be cached and reused as it is expensive
gradient = QLinearGradient(0, 0, device.width(), 0)
gradient.setColorAt(0.15, Qt.transparent)
gradient.setColorAt(0.02, Qt.transparent)
gradient.setColorAt(0.5, Qt.black)
gradient.setColorAt(0.85, Qt.transparent)
gradient.setColorAt(0.98, Qt.transparent)
painter.setCompositionMode(QPainter.CompositionMode_DestinationIn)
painter.fillRect(device.rect(), gradient)
painter.end()
Expand Down
6 changes: 3 additions & 3 deletions rare/components/tabs/downloads/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(
self.image_manager = ImageManagerSingleton()

self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.Icon)
self.image.setFixedSize(ImageSize.LibraryIcon)
self.ui.image_layout.addWidget(self.image)

self.ui.queue_info_layout.setAlignment(Qt.AlignTop)
Expand All @@ -50,7 +50,7 @@ def __init__(

if old_igame:
self.ui.title.setText(old_igame.title)
self.image.setPixmap(self.image_manager.get_pixmap(old_igame.app_name, color=True))
self.image.setPixmap(self.image_manager.get_pixmap(old_igame.app_name, ImageSize.LibraryIcon))

def update_information(self, game, igame, analysis, old_igame):
self.ui.title.setText(game.app_title)
Expand All @@ -60,7 +60,7 @@ def update_information(self, game, igame, analysis, old_igame):
self.ui.local_version.setText(elide_text(self.ui.local_version, igame.version))
self.ui.dl_size.setText(format_size(analysis.dl_size) if analysis else "")
self.ui.install_size.setText(format_size(analysis.install_size) if analysis else "")
self.image.setPixmap(self.image_manager.get_pixmap(game.app_name, color=True))
self.image.setPixmap(self.image_manager.get_pixmap(game.app_name, ImageSize.LibraryIcon))


class UpdateWidget(QFrame):
Expand Down
11 changes: 6 additions & 5 deletions rare/components/tabs/games/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
from .game_widgets import LibraryWidgetController, LibraryFilter, LibraryOrder, LibraryView
from .game_widgets.icon_game_widget import IconGameWidget
from .game_widgets.list_game_widget import ListGameWidget
from .head_bar import GameListHeadBar
from .head_bar import LibraryHeadBar
from .integrations import IntegrationsTabs

logger = getLogger("GamesTab")
logger = getLogger("GamesLibrary")


class GamesTab(QStackedWidget):
class GamesLibrary(QStackedWidget):
def __init__(self, parent=None):
super(GamesTab, self).__init__(parent=parent)
super(GamesLibrary, self).__init__(parent=parent)
self.rcore = RareCore.instance()
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
Expand All @@ -37,7 +37,7 @@ def __init__(self, parent=None):
games_page_layout = QVBoxLayout(self.games_page)
self.addWidget(self.games_page)

self.head_bar = GameListHeadBar(parent=self.games_page)
self.head_bar = LibraryHeadBar(parent=self.games_page)
self.head_bar.goto_import.connect(self.show_import)
self.head_bar.goto_egl_sync.connect(self.show_egl_sync)
self.head_bar.goto_eos_ubisoft.connect(self.show_eos_ubisoft)
Expand Down Expand Up @@ -123,6 +123,7 @@ def setup_game_list(self):
logger.warning("Excluding %s from the game list", rgame.app_title)
continue
self.filter_games(self.head_bar.current_filter())
self.order_games(self.head_bar.current_order())
self.update_count_games_label()

def add_library_widget(self, rgame: RareGame):
Expand Down
4 changes: 2 additions & 2 deletions rare/components/tabs/games/game_info/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __init__(self, parent=None):
self.rgame: Optional[RareGame] = None

self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.Display)
self.image.setFixedSize(ImageSize.DisplayTall)
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop)

self.ui.install_button.clicked.connect(self.__on_install)
Expand Down Expand Up @@ -270,7 +270,7 @@ def __on_move_result(self, rgame: RareGame, dst_path: str):
@pyqtSlot()
def __update_widget(self):
""" React to state updates from RareGame """
self.image.setPixmap(self.rgame.get_pixmap(True))
self.image.setPixmap(self.rgame.get_pixmap(ImageSize.DisplayTall, True))

self.ui.lbl_version.setDisabled(self.rgame.is_non_asset)
self.ui.version.setDisabled(self.rgame.is_non_asset)
Expand Down
Loading

0 comments on commit 8fc92a3

Please sign in to comment.