diff --git a/lutris/exception_backstops.py b/lutris/exception_backstops.py index 8b16af5dd..23cc5d7df 100644 --- a/lutris/exception_backstops.py +++ b/lutris/exception_backstops.py @@ -30,14 +30,12 @@ def watch_game_errors(game_stop_result, game=None): try: result = function(*args, **kwargs) if game_stop_result is not None and result == game_stop_result and game.state != game.STATE_STOPPED: - game.state = game.STATE_STOPPED - game.emit("game-stop") + game.stop_game() return result except Exception as ex: logger.exception("%s has encountered an error: %s", game, ex, exc_info=ex) if game.state != game.STATE_STOPPED: - game.state = game.STATE_STOPPED - game.emit("game-stop") + game.stop_game() game.signal_error(ex) return game_stop_result diff --git a/lutris/game.py b/lutris/game.py index 76cc4c114..f8179db19 100644 --- a/lutris/game.py +++ b/lutris/game.py @@ -11,7 +11,7 @@ import time from gettext import gettext as _ from typing import cast -from gi.repository import GLib, GObject, Gtk, Gio +from gi.repository import Gio, GLib, GObject, Gtk from lutris import settings from lutris.command import MonitoredCommand @@ -59,7 +59,6 @@ class Game(GObject.Object): "game-unhandled-error": (GObject.SIGNAL_RUN_FIRST, None, (object,)), "game-start": (GObject.SIGNAL_RUN_FIRST, None, ()), "game-started": (GObject.SIGNAL_RUN_FIRST, None, ()), - "game-stop": (GObject.SIGNAL_RUN_FIRST, None, ()), "game-stopped": (GObject.SIGNAL_RUN_FIRST, None, ()), "game-updated": (GObject.SIGNAL_RUN_FIRST, None, ()), "game-install": (GObject.SIGNAL_RUN_FIRST, None, ()), @@ -673,10 +672,10 @@ class Game(GObject.Object): @watch_game_errors(game_stop_result=False) def launch(self, launch_ui_delegate=None): + """Request launching a game. The game may not be installed yet.""" if not launch_ui_delegate: launch_ui_delegate = Gio.Application.get_default().launch_ui_delegate - """Request launching a game. The game may not be installed yet.""" if not self.check_launchable(): logger.error("Game is not launchable") return False @@ -840,7 +839,7 @@ class Game(GObject.Object): # Inspect why it could have crashed self.state = self.STATE_STOPPED - self.emit("game-stop") + self.emit("game-stopped") if os.path.exists(self.now_playing_path): os.unlink(self.now_playing_path) if not self.timer.finished: diff --git a/lutris/gui/application.py b/lutris/gui/application.py index 87148975c..b12d0ac9e 100644 --- a/lutris/gui/application.py +++ b/lutris/gui/application.py @@ -80,7 +80,7 @@ class Application(Gtk.Application): init_exception_backstops() GObject.add_emission_hook(Game, "game-start", self.on_game_start) - GObject.add_emission_hook(Game, "game-stop", self.on_game_stop) + GObject.add_emission_hook(Game, "game-stopped", self.on_game_stopped) GObject.add_emission_hook(Game, "game-install", self.on_game_install) GObject.add_emission_hook(Game, "game-install-update", self.on_game_install_update) GObject.add_emission_hook(Game, "game-install-dlc", self.on_game_install_dlc) @@ -93,7 +93,7 @@ class Application(Gtk.Application): self.launch_ui_delegate = LaunchUIDelegate() self.install_ui_delegate = InstallUIDelegate() - self.running_games = [] + self._running_games = [] self.app_windows = {} self.tray = None @@ -784,30 +784,29 @@ class Application(Gtk.Application): return True def on_game_start(self, game): - self.running_games.append(game) + self._running_games.append(game) if settings.read_setting("hide_client_on_game_start") == "True": self.window.hide() # Hide launcher window return True - def on_game_stop(self, game): - """Callback to remove the game from the running games""" - ids = self.get_running_game_ids() - if game.id in ids: + def on_game_stopped(self, game): + """Callback to quit Lutris is last game stops while the window is hidden.""" + running_game_ids = [g.id for g in self._running_games] + if game.id in running_game_ids: logger.debug("Removing %s from running IDs", game.id) try: - del self.running_games[ids.index(game.id)] + del self._running_games[running_game_ids.index(game.id)] except ValueError: pass - elif ids: - logger.warning("%s not in %s", game.id, ids) + elif running_game_ids: + logger.warning("%s not in %s", game.id, running_game_ids) else: logger.debug("Game has already been removed from running IDs?") - game.emit("game-stopped") - if settings.read_setting("hide_client_on_game_start") == "True" and not self.quit_on_game_exit: + if settings.read_bool_setting("hide_client_on_game_start") and not self.quit_on_game_exit: self.window.show() # Show launcher window elif not self.window.is_visible(): - if not self.running_games: + if not self.has_running_games: if self.quit_on_game_exit or not self.has_tray_icon(): self.quit() return True @@ -880,22 +879,27 @@ class Application(Gtk.Application): def get_launch_ui_delegate(self): return self.launch_ui_delegate + def get_running_games(self) -> List[str]: + # This method reflects games that have stopped even if the 'game-stopped' signal + # has not been handled yet; that handler will still clean up the list though. + return [g for g in self._running_games if g.state != g.STATE_STOPPED] + + @property + def has_running_games(self): + return bool(self.get_running_games()) + def get_running_game_ids(self) -> List[str]: """Returns the ids of the games presently running.""" - return [game.id for game in self.running_games] + return [game.id for game in self.get_running_games()] def is_game_running_by_id(self, game_id: str) -> bool: """True if the ID is the ID of a game that is running.""" - if game_id: - for game in self.running_games: - if game.id == str(game_id): - return True - return False + return game_id and str(game_id) in self.get_running_game_ids() def get_game_by_id(self, game_id: str) -> Game: """Returns the game with the ID given; if it's running this is the running game instance, and if not it's a fresh copy off the database.""" - for game in self.running_games: + for game in self.get_running_games(): if game.id == str(game_id): return game diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py index acca6aed4..639d03d72 100644 --- a/lutris/gui/lutriswindow.py +++ b/lutris/gui/lutriswindow.py @@ -871,7 +871,7 @@ class LutrisWindow(Gtk.ApplicationWindow, def on_window_delete(self, *_args): app = self.application - if app.running_games: + if app.has_running_games: self.hide() return True if app.has_tray_icon(): diff --git a/lutris/gui/widgets/sidebar.py b/lutris/gui/widgets/sidebar.py index 49a39ab8f..aa59b3566 100644 --- a/lutris/gui/widgets/sidebar.py +++ b/lutris/gui/widgets/sidebar.py @@ -358,7 +358,7 @@ class LutrisSidebar(Gtk.ListBox): GObject.add_emission_hook(ScriptInterpreter, "runners-installed", self.update_rows) GObject.add_emission_hook(ServicesBox, "services-changed", self.update_rows) GObject.add_emission_hook(Game, "game-start", self.on_game_start) - GObject.add_emission_hook(Game, "game-stop", self.on_game_stop) + GObject.add_emission_hook(Game, "game-stopped", self.on_game_stopped) GObject.add_emission_hook(Game, "game-updated", self.update_rows) GObject.add_emission_hook(BaseService, "service-login", self.on_service_auth_changed) GObject.add_emission_hook(BaseService, "service-logout", self.on_service_auth_changed) @@ -593,9 +593,9 @@ class LutrisSidebar(Gtk.ListBox): self.running_row.show() return True - def on_game_stop(self, _game): + def on_game_stopped(self, _game): """Hide the "running" section when no games are running""" - if not self.application.running_games: + if not self.application.has_running_games: self.running_row.hide() if self.get_selected_row() == self.running_row: