GameProcess: Make it a persistent member of RareGame
The `GameProcess` class now acts as a persistent member of `RareGame` that can be re-used for launching games. Its signals are handled and repeated by `RareGame`. Implements launching directly into `RareGame` Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
This commit is contained in:
parent
08c7ae89e9
commit
0d4b75d399
|
@ -57,7 +57,7 @@ def main():
|
|||
launch_minimal_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='<wine pfx path>',
|
||||
default=os.environ.get('LGDRY_WINE_PREFIX', None),
|
||||
help='Set WINE prefix to use')
|
||||
launch_minimal_parser.add_argument("--ask-alyways-sync", help="Ask for cloud saves",
|
||||
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")
|
||||
|
|
|
@ -206,7 +206,6 @@ class GamesTab(QStackedWidget):
|
|||
self.icon_view.layout().addWidget(icon_widget)
|
||||
self.list_view.layout().addWidget(list_widget)
|
||||
rgame.set_pixmap()
|
||||
self.game_utils.check_running(rgame)
|
||||
self.filter_games(self.active_filter)
|
||||
|
||||
def add_library_widget(self, rgame: RareGame):
|
||||
|
|
|
@ -252,8 +252,8 @@ class GameWidget(LibraryWidget):
|
|||
|
||||
if self.rgame.game.supports_cloud_saves and not offline:
|
||||
self.syncing_cloud_saves = True
|
||||
self.game_utils.prepare_launch(
|
||||
self.rgame, offline, skip_version_check
|
||||
self.rgame.launch(
|
||||
offline=offline, skip_update_check=skip_version_check
|
||||
)
|
||||
|
||||
def sync_finished(self, app_name):
|
||||
|
|
|
@ -6,14 +6,17 @@ from enum import IntEnum
|
|||
from logging import getLogger
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, pyqtSlot, QProcess
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from legendary.models.game import Game, InstalledGame, SaveGameFile
|
||||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.install import InstallOptionsModel
|
||||
from rare.shared.image_manager import ImageManager
|
||||
from rare.shared.game_process import GameProcess
|
||||
from rare.utils.paths import data_dir
|
||||
from rare.utils.misc import get_rare_executable
|
||||
|
||||
|
||||
logger = getLogger("RareGame")
|
||||
|
||||
|
@ -92,7 +95,7 @@ class RareGame(QObject):
|
|||
|
||||
self.pixmap: QPixmap = QPixmap()
|
||||
self.metadata: RareGame.Metadata = RareGame.Metadata()
|
||||
self.load_metadata()
|
||||
self.__load_metadata()
|
||||
|
||||
self.owned_dlcs: List[RareGame] = []
|
||||
self.saves: List[SaveGameFile] = []
|
||||
|
@ -103,7 +106,29 @@ class RareGame(QObject):
|
|||
self.progress: int = 0
|
||||
self.active_worker: Optional[QRunnable] = None
|
||||
|
||||
self.game_running = False
|
||||
self.state = RareGame.State.IDLE
|
||||
|
||||
self.game_process = GameProcess(self)
|
||||
self.game_process.launched.connect(self.__game_launched)
|
||||
self.game_process.finished.connect(self.__game_finished)
|
||||
if self.is_installed and not self.is_dlc:
|
||||
self.game_process.connect(on_startup=True)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def __game_launched(self, code: int):
|
||||
self.state = RareGame.State.RUNNING
|
||||
if code == GameProcess.Code.ON_STARTUP:
|
||||
return
|
||||
self.metadata.last_played = datetime.now()
|
||||
self.__save_metadata()
|
||||
self.signals.game.launched.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def __game_finished(self, exit_code: int):
|
||||
self.state = RareGame.State.IDLE
|
||||
if exit_code == GameProcess.Code.ON_STARTUP:
|
||||
return
|
||||
self.signals.game.finished.emit()
|
||||
|
||||
__metadata_json: Optional[Dict] = None
|
||||
|
||||
|
@ -122,12 +147,12 @@ class RareGame(QObject):
|
|||
RareGame.__metadata_json = metadata
|
||||
return RareGame.__metadata_json
|
||||
|
||||
def load_metadata(self):
|
||||
def __load_metadata(self):
|
||||
metadata = self.__load_metadata_json()
|
||||
if self.app_name in metadata:
|
||||
self.metadata = RareGame.Metadata.from_dict(metadata[self.app_name])
|
||||
|
||||
def save_metadata(self):
|
||||
def __save_metadata(self):
|
||||
metadata = self.__load_metadata_json()
|
||||
metadata[self.app_name] = self.metadata.as_dict()
|
||||
with open(os.path.join(data_dir(), "game_meta.json"), "w") as metadata_json:
|
||||
|
@ -367,6 +392,15 @@ class RareGame(QObject):
|
|||
|
||||
@property
|
||||
def is_origin(self) -> bool:
|
||||
"""!
|
||||
@brief Property to report if a Game is an Origin game
|
||||
|
||||
Legendary and by extenstion Rare can't launch Origin games directly,
|
||||
it just launches the Origin client and thus requires a bit of a special
|
||||
handling to let the user know.
|
||||
|
||||
@return bool If the game is an Origin game
|
||||
"""
|
||||
return self.game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin"
|
||||
|
||||
@property
|
||||
|
@ -374,12 +408,10 @@ class RareGame(QObject):
|
|||
if self.is_installed:
|
||||
if self.is_non_asset:
|
||||
return True
|
||||
elif self.game_running or self.needs_verification:
|
||||
if self.state == RareGame.State.RUNNING or self.needs_verification:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_pixmap(self):
|
||||
self.pixmap = self.image_manager.get_pixmap(self.app_name, self.is_installed)
|
||||
|
@ -412,4 +444,33 @@ class RareGame(QObject):
|
|||
InstallOptionsModel(
|
||||
app_name=self.app_name, repair_mode=True, repair_and_update=repair_and_update, update=True
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def launch(
|
||||
self,
|
||||
offline: bool = False,
|
||||
skip_update_check: bool = False,
|
||||
wine_bin: str = None,
|
||||
wine_pfx: str = None,
|
||||
ask_sync_saves: bool = False,
|
||||
):
|
||||
executable = get_rare_executable()
|
||||
executable, args = executable[0], executable[1:]
|
||||
args.extend([
|
||||
"start", self.app_name
|
||||
])
|
||||
if offline:
|
||||
args.append("--offline")
|
||||
if skip_update_check:
|
||||
args.append("--skip-update-check")
|
||||
if wine_bin:
|
||||
args.extend(["--wine-bin", wine_bin])
|
||||
if wine_pfx:
|
||||
args.extend(["--wine-prefix", wine_pfx])
|
||||
if ask_sync_saves:
|
||||
args.extend("--ask-sync-saves")
|
||||
|
||||
# kill me, if I don't change it before commit
|
||||
QProcess.startDetached(executable, args)
|
||||
logger.info(f"Start new Process: ({executable} {' '.join(args)})")
|
||||
self.game_process.connect(on_startup=False)
|
||||
|
|
|
@ -1,62 +1,82 @@
|
|||
import datetime
|
||||
import json
|
||||
import logging
|
||||
from enum import IntEnum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, pyqtSlot
|
||||
from PyQt5.QtNetwork import QLocalSocket
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.launcher import ErrorModel, Actions, FinishedModel, StateChangedModel
|
||||
|
||||
logger = logging.getLogger("GameProcess")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rare.models.game import RareGame
|
||||
|
||||
|
||||
class GameProcess(QObject):
|
||||
# str: app_name, int: exit_code
|
||||
game_finished = pyqtSignal(RareGame, int)
|
||||
# str: app_name
|
||||
game_launched = pyqtSignal(RareGame)
|
||||
# int: code
|
||||
launched = pyqtSignal(int)
|
||||
# int: code
|
||||
finished = pyqtSignal(int)
|
||||
|
||||
def __init__(self, rgame: RareGame, on_startup=False, always_ask_sync: bool = False):
|
||||
class Code(IntEnum):
|
||||
SUCCESS = 0
|
||||
TIMEOUT = -1
|
||||
ON_STARTUP = -1234
|
||||
|
||||
def __init__(self, rgame: 'RareGame'):
|
||||
super(GameProcess, self).__init__()
|
||||
self.rgame = rgame
|
||||
self.on_startup = on_startup
|
||||
self.on_startup = False
|
||||
self.tried_connections = 0
|
||||
|
||||
self.timer = QTimer()
|
||||
self.timer.timeout.connect(self.__connect)
|
||||
self.socket = QLocalSocket()
|
||||
self.socket_name = f"rare_{self.rgame.app_name}"
|
||||
|
||||
self.socket.connected.connect(self._socket_connected)
|
||||
# if hasattr(self.socket, "errorOccured"):
|
||||
# self.socket.errorOccurred.connect(self._error_occurred)
|
||||
# else:
|
||||
# QTimer.singleShot(
|
||||
# 100,
|
||||
# lambda: self._error_occurred(QLocalSocket.UnknownSocketError) if self.socket.error() else None
|
||||
# )
|
||||
# logger.warning("Do not handle errors on QLocalSocket, because of an old qt version")
|
||||
try:
|
||||
self.socket.errorOccurred.connect(self._error_occurred)
|
||||
except AttributeError:
|
||||
QTimer.singleShot(100, lambda: self._error_occurred(QLocalSocket.UnknownSocketError) if self.socket.error() else None)
|
||||
# QTimer.singleShot(
|
||||
# 100,
|
||||
# lambda: self._error_occurred(QLocalSocket.UnknownSocketError) if self.socket.error() else None
|
||||
# )
|
||||
logger.warning("Do not handle errors on QLocalSocket, because of an old qt version")
|
||||
self.socket.readyRead.connect(self._message_available)
|
||||
self.always_ask_sync = always_ask_sync
|
||||
self.socket.disconnected.connect(self.__close)
|
||||
|
||||
def close_socket():
|
||||
try:
|
||||
self.socket.close()
|
||||
except RuntimeError:
|
||||
pass
|
||||
def connect(self, on_startup: bool):
|
||||
self.on_startup = on_startup
|
||||
self.timer.start(200)
|
||||
|
||||
self.socket.disconnected.connect(close_socket)
|
||||
self.timer = QTimer()
|
||||
if not on_startup:
|
||||
# wait a short time for process started
|
||||
self.timer.timeout.connect(self.connect_to_server)
|
||||
self.timer.start(200)
|
||||
else:
|
||||
# nothing happens, if no server available
|
||||
self.connect_to_server()
|
||||
@pyqtSlot()
|
||||
def __close(self):
|
||||
try:
|
||||
self.socket.close()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def connect_to_server(self):
|
||||
self.socket.connectToServer(f"rare_{self.rgame.app_name}")
|
||||
@pyqtSlot()
|
||||
def __connect(self):
|
||||
self.socket.connectToServer(self.socket_name)
|
||||
self.tried_connections += 1
|
||||
|
||||
if self.tried_connections > 50: # 10 seconds
|
||||
QMessageBox.warning(None, "Error", self.tr("Connection to game process failed (Timeout)"))
|
||||
self.timer.stop()
|
||||
self.game_finished.emit(self.rgame, 1)
|
||||
self.finished.emit(GameProcess.Code.TIMEOUT)
|
||||
|
||||
@pyqtSlot()
|
||||
def _message_available(self):
|
||||
|
@ -71,9 +91,9 @@ class GameProcess(QObject):
|
|||
logger.error("Could not load json data")
|
||||
return
|
||||
|
||||
action = data.get("action", -1)
|
||||
action = data.get("action", False)
|
||||
|
||||
if action == -1:
|
||||
if not action:
|
||||
logger.error("Got unexpected action")
|
||||
elif action == Actions.finished:
|
||||
logger.info(f"{self.rgame.app_name} {self.rgame.app_title} finished")
|
||||
|
@ -86,35 +106,29 @@ class GameProcess(QObject):
|
|||
self.socket.close()
|
||||
self._game_finished(1)
|
||||
QMessageBox.warning(None, "Error", self.tr(
|
||||
"Error in game {}: \n{}").format(self.rgame.app_title, model.error_string))
|
||||
"Error in game {}:\n{}").format(self.rgame.app_title, model.error_string))
|
||||
|
||||
elif action == Actions.state_update:
|
||||
model = StateChangedModel.from_json(data)
|
||||
if model.new_state == StateChangedModel.States.started:
|
||||
logger.info("Launched Game")
|
||||
self.game_launched.emit(self.rgame)
|
||||
meta_data = self.rgame.metadata
|
||||
meta_data.last_played = datetime.datetime.now()
|
||||
self.rgame.save_metadata()
|
||||
self.launched.emit(GameProcess.Code.SUCCESS)
|
||||
|
||||
@pyqtSlot()
|
||||
def _socket_connected(self):
|
||||
self.timer.stop()
|
||||
self.timer.deleteLater()
|
||||
logger.info(f"Connection established for {self.rgame.app_name} ({self.rgame.app_title})")
|
||||
if self.on_startup:
|
||||
logger.info(f"Found {self.rgame.app_name} ({self.rgame.app_title}) running at startup")
|
||||
|
||||
# FIXME run this after startup, widgets do not exist at this time
|
||||
QTimer.singleShot(1000, lambda: self.game_launched.emit(self.rgame))
|
||||
self.launched.emit(GameProcess.Code.ON_STARTUP)
|
||||
|
||||
@pyqtSlot(QLocalSocket.LocalSocketError)
|
||||
def _error_occurred(self, _: QLocalSocket.LocalSocketError):
|
||||
if self.on_startup:
|
||||
self.timer.stop()
|
||||
self.socket.close()
|
||||
self._game_finished(-1234) # 1234 is exit code for startup
|
||||
self._game_finished(GameProcess.Code.ON_STARTUP) # 1234 is exit code for startup
|
||||
logger.error(f"{self.rgame.app_name} ({self.rgame.app_title}): {self.socket.errorString()}")
|
||||
|
||||
def _game_finished(self, exit_code: int):
|
||||
self.deleteLater()
|
||||
self.game_finished.emit(self.rgame, exit_code)
|
||||
self.finished.emit(exit_code)
|
||||
|
|
|
@ -2,7 +2,7 @@ import os
|
|||
import platform
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, pyqtSlot
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, pyqtSlot
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtWidgets import QMessageBox, QPushButton
|
||||
|
@ -14,8 +14,7 @@ from rare.lgndr.glue.arguments import LgndrUninstallGameArgs
|
|||
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
|
||||
from rare.models.game import RareGame
|
||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||
from rare.shared.game_process import GameProcess
|
||||
from rare.utils import config_helper, misc
|
||||
from rare.utils import config_helper
|
||||
from .cloud_save_utils import CloudSaveUtils
|
||||
|
||||
logger = getLogger("GameUtils")
|
||||
|
@ -83,13 +82,6 @@ class GameUtils(QObject):
|
|||
self.cloud_save_utils = CloudSaveUtils()
|
||||
self.cloud_save_utils.sync_finished.connect(self.sync_finished)
|
||||
|
||||
def check_running(self, rgame: RareGame):
|
||||
if rgame.is_installed:
|
||||
game_process = GameProcess(rgame, on_startup=True)
|
||||
game_process.game_finished.connect(self.game_finished)
|
||||
game_process.game_launched.connect(self.game_launched.emit)
|
||||
self.running_games[rgame.app_name] = game_process
|
||||
|
||||
def uninstall_game(self, rgame: RareGame) -> bool:
|
||||
# returns if uninstalled
|
||||
if not os.path.exists(rgame.igame.install_path):
|
||||
|
@ -138,42 +130,9 @@ class GameUtils(QObject):
|
|||
self.sync_finished(rgame)
|
||||
|
||||
self.launch_game(
|
||||
rgame, offline, skip_update_check, ask_always_sync=dont_sync_after_finish
|
||||
rgame, offline, skip_update_check, ask_sync_saves=dont_sync_after_finish
|
||||
)
|
||||
|
||||
def launch_game(
|
||||
self,
|
||||
rgame: RareGame,
|
||||
offline: bool = False,
|
||||
skip_update_check: bool = False,
|
||||
wine_bin: str = None,
|
||||
wine_pfx: str = None,
|
||||
ask_always_sync: bool = False,
|
||||
):
|
||||
executable = misc.get_rare_executable()
|
||||
executable, args = executable[0], executable[1:]
|
||||
args.extend([
|
||||
"start", rgame.app_name
|
||||
])
|
||||
if offline:
|
||||
args.append("--offline")
|
||||
if skip_update_check:
|
||||
args.append("--skip-update-check")
|
||||
if wine_bin:
|
||||
args.extend(["--wine-bin", wine_bin])
|
||||
if wine_pfx:
|
||||
args.extend(["--wine-prefix", wine_pfx])
|
||||
if ask_always_sync:
|
||||
args.extend("--ask-always-sync")
|
||||
|
||||
# kill me, if I don't change it before commit
|
||||
QProcess.startDetached(executable, args)
|
||||
logger.info(f"Start new Process: ({executable} {' '.join(args)})")
|
||||
game_process = GameProcess(rgame, ask_always_sync)
|
||||
game_process.game_finished.connect(self.game_finished)
|
||||
game_process.game_launched.connect(self.game_launched.emit)
|
||||
self.running_games[rgame.app_name] = game_process
|
||||
|
||||
@pyqtSlot(RareGame, int)
|
||||
def game_finished(self, rgame: RareGame, exit_code):
|
||||
if self.running_games.get(rgame.app_name):
|
||||
|
|
|
@ -142,6 +142,7 @@ class RareCore(QObject):
|
|||
def add_game(self, rgame: RareGame) -> None:
|
||||
rgame.signals.game.install.connect(self._signals.game.install)
|
||||
rgame.signals.game.finished.connect(self._signals.application.update_tray)
|
||||
rgame.signals.game.finished.connect(lambda: self._signals.discord_rpc.set_title.emit(""))
|
||||
self.__games[rgame.app_name] = rgame
|
||||
|
||||
def __filter_games(self, condition: Callable[[RareGame], bool]) -> Iterator[RareGame]:
|
||||
|
|
Loading…
Reference in a new issue