Group delegate classes in the delegates module

This commit is contained in:
Mathieu Comandon 2023-06-14 00:49:10 -07:00
parent 27a3d71697
commit 41c300fefd
5 changed files with 104 additions and 104 deletions

View file

@ -18,7 +18,7 @@ from lutris.config import LutrisConfig
from lutris.database import categories as categories_db
from lutris.database import games as games_db
from lutris.database import sql
from lutris.exceptions import GameConfigError, UnavailableRunnerError, watch_game_errors
from lutris.exceptions import GameConfigError, watch_game_errors
from lutris.runner_interpreter import export_bash_script, get_launch_parameters
from lutris.runners import InvalidRunner, import_runner
from lutris.util import audio, discord, extract, jobs, linux, strings, system, xdgshortcuts
@ -67,38 +67,6 @@ class Game(GObject.Object):
"game-installed": (GObject.SIGNAL_RUN_FIRST, None, ()),
}
class LaunchUIDelegate:
"""These objects provide UI for the game while it is being launched;
one provided to the launch() method.
The default implementation provides no UI and makes default choices for
the user, but DialogLaunchUIDelegate implements this to show dialogs and ask the
user questions. Windows then inherit from DialogLaunchUIDelegate.
If these methods throw any errors are reported via tha game-error signal;
that is not part of this delegate because errors can be report outside of
the launch() method, where no delegate is available.
"""
def check_game_launchable(self, game):
"""See if the game can be launched. If there are adverse conditions,
this can warn the user and ask whether to launch. If this returs
False, the launch is cancelled. The default is to return True with no
actual checks.
"""
if not game.runner.is_installed():
raise UnavailableRunnerError("The required runner '%s' is not installed." % game.runner.name)
return True
def select_game_launch_config(self, game):
"""Prompt the user for which launch config to use. Returns None
if the user cancelled, an empty dict for the primary game configuration
and the launch_config as a dict if one is selected.
The default is the select the primary game.
"""
return {} # primary game
def __init__(self, game_id=None):
super().__init__()
self._id = game_id # pylint: disable=invalid-name
@ -126,6 +94,7 @@ class Game(GObject.Object):
self.service = game_data.get("service")
self.appid = game_data.get("service_id")
self.playtime = float(game_data.get("playtime") or 0.0)
self.discord_id = game_data.get('discord_id') # Discord App ID for RPC
self._config = None
self._runner = None
@ -146,9 +115,6 @@ class Game(GObject.Object):
self.timer = Timer()
self.screen_saver_inhibitor_cookie = None
# Adding Discord App ID for RPC
self.discord_id = game_data.get('discord_id')
@staticmethod
def create_empty_service_game(db_game, service):
"""Creates a Game from the database data from ServiceGameCollection, which is

View file

@ -41,6 +41,7 @@ from lutris.game import Game, export_game, import_game
from lutris.installer import get_installers
from lutris.gui.dialogs import ErrorDialog, InstallOrPlayDialog, NoticeDialog, LutrisInitDialog
from lutris.gui.dialogs.issue import IssueReportWindow
from lutris.gui.dialogs.delegates import LaunchUIDelegate, InstallUIDelegate, CommandLineUIDelegate
from lutris.gui.installerwindow import InstallerWindow, InstallationKind
from lutris.gui.widgets.status_icon import LutrisStatusIcon
from lutris.migrations import migrate
@ -53,7 +54,6 @@ from lutris.util.steam.appmanifest import AppManifest, get_appmanifests
from lutris.util.steam.config import get_steamapps_paths
from lutris.services import get_enabled_services
from lutris.database.services import ServiceGameCollection
from lutris.runners.runner import Runner
from .lutriswindow import LutrisWindow
@ -78,8 +78,8 @@ class Application(Gtk.Application):
self.force_updates = False
self.css_provider = Gtk.CssProvider.new()
self.window = None
self.launch_ui_delegate = Game.LaunchUIDelegate()
self.install_ui_delegate = Runner.InstallUIDelegate()
self.launch_ui_delegate = LaunchUIDelegate()
self.install_ui_delegate = InstallUIDelegate()
self.running_games = Gio.ListStore.new(Game)
self.app_windows = {}
@ -974,23 +974,3 @@ Also, check that the version specified is in the correct format.
def has_tray_icon(self):
return self.tray and self.tray.is_visible()
class CommandLineUIDelegate(Game.LaunchUIDelegate):
"""This delegate can provide user selections that were provided on the command line."""
def __init__(self, launch_config_name):
self.launch_config_name = launch_config_name
def select_game_launch_config(self, game):
if not self.launch_config_name:
return {}
game_config = game.config.game_level.get("game", {})
configs = game_config.get("launch_configs")
for config in configs:
if config.get("name") == self.launch_config_name:
return config
raise RuntimeError("The launch configuration '%s' could not be found." % self.launch_config_name)

View file

@ -2,15 +2,109 @@ from gettext import gettext as _
from gi.repository import Gdk, Gtk
from lutris.exceptions import UnavailableRunnerError
from lutris.game import Game
from lutris.gui import dialogs
from lutris.gui.dialogs.download import DownloadDialog
from lutris.runners import wine
from lutris.runners.runner import Runner
from lutris.util.downloader import Downloader
class DialogInstallUIDelegate(Runner.InstallUIDelegate):
class LaunchUIDelegate:
"""These objects provide UI for the game while it is being launched;
one provided to the launch() method.
The default implementation provides no UI and makes default choices for
the user, but DialogLaunchUIDelegate implements this to show dialogs and ask the
user questions. Windows then inherit from DialogLaunchUIDelegate.
If these methods throw any errors are reported via tha game-error signal;
that is not part of this delegate because errors can be report outside of
the launch() method, where no delegate is available.
"""
def check_game_launchable(self, game):
"""See if the game can be launched. If there are adverse conditions,
this can warn the user and ask whether to launch. If this returs
False, the launch is cancelled. The default is to return True with no
actual checks.
"""
if not game.runner.is_installed():
raise UnavailableRunnerError("The required runner '%s' is not installed." % game.runner.name)
return True
def select_game_launch_config(self, game):
"""Prompt the user for which launch config to use. Returns None
if the user cancelled, an empty dict for the primary game configuration
and the launch_config as a dict if one is selected.
The default is the select the primary game.
"""
return {} # primary game
class InstallUIDelegate:
"""These objects provide UI for a runner as it is installing itself.
One of these must be provided to the install() method.
The default implementation provides no UI and makes default choices for
the user, but DialogInstallUIDelegate implements this to show dialogs and
ask the user questions. Windows then inherit from DialogLaunchUIDelegate.
"""
def show_install_yesno_inquiry(self, question, title):
"""Called to ask the user a yes/no question.
The default is 'yes'."""
return True
def show_install_file_inquiry(self, question, title, message):
"""Called to ask the user for a file.
Lutris first asks the user the question given (showing the title);
if the user answers 'Yes', it asks for the file using the message.
Returns None if the user answers 'No' or cancels out. Returns the
file path if the user selected one.
The default is to return None always.
"""
return None
def download_install_file(self, url, destination):
"""Downloads a file from a URL to a destination, overwriting any
file at that path.
Returns True if sucessful, and False if the user cancels.
The default is to download with no UI, and no option to cancel.
"""
downloader = Downloader(url, destination, overwrite=True)
downloader.start()
return downloader.join()
class CommandLineUIDelegate(LaunchUIDelegate):
"""This delegate can provide user selections that were provided on the command line."""
def __init__(self, launch_config_name):
self.launch_config_name = launch_config_name
def select_game_launch_config(self, game):
if not self.launch_config_name:
return {}
game_config = game.config.game_level.get("game", {})
configs = game_config.get("launch_configs")
for config in configs:
if config.get("name") == self.launch_config_name:
return config
raise RuntimeError("The launch configuration '%s' could not be found." % self.launch_config_name)
class DialogInstallUIDelegate(InstallUIDelegate):
"""This provides UI for runner installation via dialogs."""
def show_install_yesno_inquiry(self, question, title):
@ -37,7 +131,7 @@ class DialogInstallUIDelegate(Runner.InstallUIDelegate):
return dialog.downloader.state == Downloader.COMPLETED
class DialogLaunchUIDelegate(Game.LaunchUIDelegate):
class DialogLaunchUIDelegate(LaunchUIDelegate):
"""This provides UI for game launch via dialogs."""
def check_game_launchable(self, game):

View file

@ -10,7 +10,6 @@ from lutris.database.games import get_game_by_field
from lutris.exceptions import GameConfigError, UnavailableLibrariesError
from lutris.runners import RunnerInstallationError
from lutris.util import flatpak, strings, system
from lutris.util.downloader import Downloader
from lutris.util.extract import ExtractFailure, extract_archive
from lutris.util.http import HTTPError, Request
from lutris.util.linux import LINUX_SYSTEM
@ -35,46 +34,6 @@ class Runner: # pylint: disable=too-many-public-methods
arch = None # If the runner is only available for an architecture that isn't x86_64
flatpak_id = None
class InstallUIDelegate:
"""These objects provide UI for a runner as it is installing itself.
One of these must be provided to the install() method.
The default implementation provides no UI and makes default choices for
the user, but DialogInstallUIDelegate implements this to show dialogs and
ask the user questions. Windows then inherit from DialogLaunchUIDelegate.
"""
def show_install_yesno_inquiry(self, question, title):
"""Called to ask the user a yes/no question.
The default is 'yes'."""
return True
def show_install_file_inquiry(self, question, title, message):
"""Called to ask the user for a file.
Lutris first asks the user the question given (showing the title);
if the user answers 'Yes', it asks for the file using the message.
Returns None if the user answers 'No' or cancels out. Returns the
file path if the user selected one.
The default is to return None always.
"""
return None
def download_install_file(self, url, destination):
"""Downloads a file from a URL to a destination, overwriting any
file at that path.
Returns True if sucessful, and False if the user cancels.
The default is to download with no UI, and no option to cancel.
"""
downloader = Downloader(url, destination, overwrite=True)
downloader.start()
return downloader.join()
def __init__(self, config=None):
"""Initialize runner."""
if config:
@ -222,6 +181,7 @@ class Runner: # pylint: disable=too-many-public-methods
return [exe]
if flatpak.is_app_installed(self.flatpak_id):
return flatpak.get_run_command(self.flatpak_id)
return []
def get_env(self, os_env=False, disable_runtime=False):
"""Return environment variables used for a game."""

View file

@ -50,7 +50,7 @@ class TestPCSX2Runner(unittest.TestCase):
mock_config.game_config = {'main_file': main_file}
mock_config.runner_config = {'nogui': True}
self.runner.config = mock_config
expected = {'command': [self.runner.get_executable(), '-nogui', main_file]}
expected = {'command': self.runner.get_command() + ['-nogui', main_file]}
self.assertEqual(self.runner.play(), expected)
@patch('lutris.util.system.path_exists')