From b4234878f0a4df75fdb1e3a6ab65d231fa952ef3 Mon Sep 17 00:00:00 2001 From: Mathieu Comandon Date: Tue, 16 Mar 2021 00:41:07 -0700 Subject: [PATCH] Launch games from local services directly --- lutris/config.py | 15 +++++++++-- lutris/database/games.py | 5 ++-- lutris/gui/application.py | 5 +++- lutris/gui/lutriswindow.py | 40 ++++++++++++++--------------- lutris/gui/widgets/game_bar.py | 3 +++ lutris/installer/installer.py | 17 ++---------- lutris/services/base.py | 26 ++++++++++++++++++- lutris/services/dolphin.py | 18 +++++++++---- lutris/services/xdg.py | 4 +++ lutris/util/dolphin/cache_reader.py | 12 +++++---- 10 files changed, 93 insertions(+), 52 deletions(-) diff --git a/lutris/config.py b/lutris/config.py index 6244e4f61..5c8ce74e6 100644 --- a/lutris/config.py +++ b/lutris/config.py @@ -1,10 +1,10 @@ """Handle the game, runner and global system configurations.""" -# Standard Library import os import time -# Lutris Modules +import yaml + from lutris import settings, sysoptions from lutris.runners import InvalidRunner, import_runner from lutris.util.log import logger @@ -17,6 +17,17 @@ def make_game_config_id(game_slug): return "{}-{}".format(game_slug, int(time.time())) +def write_game_config(game_slug, config): + """Writes a game config to disk""" + configpath = make_game_config_id(game_slug) + config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) + yaml_config = yaml.safe_dump(config, default_flow_style=False) + with open(config_filename, "w") as config_file: + logger.debug("Writing game config to %s", config_filename) + config_file.write(yaml_config) + return configpath + + class LutrisConfig: """Class where all the configuration handling happens. diff --git a/lutris/database/games.py b/lutris/database/games.py index f5ae3277b..7acec7036 100644 --- a/lutris/database/games.py +++ b/lutris/database/games.py @@ -130,12 +130,11 @@ def get_games_by_slug(slug): return sql.db_select(settings.PGA_DB, "games", condition=("slug", slug)) -def add_game(name, **game_data): +def add_game(**game_data): """Add a game to the PGA database.""" - game_data["name"] = name game_data["installed_at"] = int(time.time()) if "slug" not in game_data: - game_data["slug"] = slugify(name) + game_data["slug"] = slugify(game_data["name"]) return sql.db_insert(settings.PGA_DB, "games", game_data) diff --git a/lutris/gui/application.py b/lutris/gui/application.py index 11b6cace6..b273dd360 100644 --- a/lutris/gui/application.py +++ b/lutris/gui/application.py @@ -477,7 +477,10 @@ class Application(Gtk.Application): if game.service: service = get_services()[game.service]() db_game = ServiceGameCollection.get_game(service.id, game.appid) - service.install(db_game) + game_id = service.install(db_game) + if game_id: + game = Game(game_id) + game.launch() return True installers = get_installers(game_slug=game.slug) diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py index a9c6c4675..69369ba6a 100644 --- a/lutris/gui/lutriswindow.py +++ b/lutris/gui/lutriswindow.py @@ -792,26 +792,26 @@ class LutrisWindow(Gtk.ApplicationWindow): # pylint: disable=too-many-public-me def on_game_activated(self, view, game_id): """Handles view activations (double click, enter press)""" - if self.service and self.service.id != "lutris": - db_game = games_db.get_game_for_service(self.service.id, game_id) - if db_game: - game_id = db_game["id"] - else: - db_game = ServiceGameCollection.get_game(self.service.id, game_id) + if self.service: + if self.service.id != "lutris": + db_game = games_db.get_game_for_service(self.service.id, game_id) if db_game: - self.service.install(db_game) + game_id = db_game["id"] else: + db_game = ServiceGameCollection.get_game(self.service.id, game_id) + if db_game: + game_id = self.service.install(db_game) + else: + game_id = self.service.install(game_id) + else: + db_game = games_db.get_game_by_field(game_id) + if not db_game: self.service.install(game_id) - return - - if self.service and self.service.id == "lutris": - db_game = games_db.get_game_by_field(game_id) - if not db_game: - self.service.install(game_id) - return - if db_game["installed"] != 1: - self.service.install(game_id) - return - game_id = db_game["id"] - game = Game(game_id) - game.emit("game-launch") + return + if db_game["installed"] != 1: + self.service.install(game_id) + return + game_id = db_game["id"] + if game_id: + game = Game(game_id) + game.emit("game-launch") diff --git a/lutris/gui/widgets/game_bar.py b/lutris/gui/widgets/game_bar.py index 3bbc848d4..58bde8be9 100644 --- a/lutris/gui/widgets/game_bar.py +++ b/lutris/gui/widgets/game_bar.py @@ -182,6 +182,9 @@ class GameBar(Gtk.Fixed): button.set_label(_("Install")) button.connect("clicked", self.game_actions.on_install_clicked) if self.service: + if self.service.local: + # Local services don't show an install dialog, they can be launched directly + button.set_label(_("Play")) button.set_size_request(120, 32) return button button.set_size_request(84, 32) diff --git a/lutris/installer/installer.py b/lutris/installer/installer.py index e832a05de..76d8235a6 100644 --- a/lutris/installer/installer.py +++ b/lutris/installer/installer.py @@ -2,10 +2,7 @@ import json import os -import yaml - -from lutris import settings -from lutris.config import LutrisConfig, make_game_config_id +from lutris.config import LutrisConfig, write_game_config from lutris.database.games import add_or_update, get_game_by_field from lutris.game import Game from lutris.installer import AUTO_ELF_EXE, AUTO_WIN32_EXE @@ -215,16 +212,6 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes return config - def write_game_config(self): - configpath = make_game_config_id(self.slug) - config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) - config = self.get_game_config() - yaml_config = yaml.safe_dump(config, default_flow_style=False) - with open(config_filename, "w") as config_file: - logger.debug("Writing game config to %s", config_filename) - config_file.write(yaml_config) - return configpath - def save(self): """Write the game configuration in the DB and config file""" if self.extends: @@ -233,7 +220,7 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes self.extends, ) return - configpath = self.write_game_config() + configpath = write_game_config(self.slug, self.get_game_config()) runner_inst = import_runner(self.runner)() if self.service: service_id = self.service.id diff --git a/lutris/services/base.py b/lutris/services/base.py index d8561fbc7..e669a3268 100644 --- a/lutris/services/base.py +++ b/lutris/services/base.py @@ -5,8 +5,9 @@ import shutil from gi.repository import Gio, GObject from lutris import api, settings +from lutris.config import write_game_config from lutris.database import sql -from lutris.database.games import get_games +from lutris.database.games import add_game, get_games from lutris.database.services import ServiceGameCollection from lutris.game import Game from lutris.installer import fetch_script @@ -117,6 +118,8 @@ class BaseService(GObject.Object): """Install a service game""" appid = db_game["appid"] logger.debug("Installing %s from service %s", appid, self.id) + if self.local: + return self.simple_install(db_game) service_installers = self.get_installers_from_api(appid) # Check if the game is not already installed for service_installer in service_installers: @@ -137,6 +140,27 @@ class BaseService(GObject.Object): application = Gio.Application.get_default() application.show_installer_window(service_installers, service=self, appid=appid) + def simple_install(self, db_game): + """A simplified version of the install method for local services""" + installer = self.generate_installer(db_game) + configpath = write_game_config(db_game["slug"], installer["script"]) + game_id = add_game( + name=installer["name"], + runner=installer["runner"], + slug=installer["game_slug"], + directory=self.get_game_directory(installer), + installed=1, + installer_slug=installer["slug"], + configpath=configpath, + service=self.id, + service_id=db_game["appid"], + ) + return game_id + + def get_game_directory(self, _installer): + """Specific services should implement this""" + return "" + class OnlineService(BaseService): """Base class for online gaming services""" diff --git a/lutris/services/dolphin.py b/lutris/services/dolphin.py index 55855ecf0..6ca7d5023 100644 --- a/lutris/services/dolphin.py +++ b/lutris/services/dolphin.py @@ -1,5 +1,5 @@ -import os import json +import os from gettext import gettext as _ from lutris import settings @@ -50,10 +50,15 @@ class DolphinService(BaseService): "script": { "game": { "main_file": details["path"], + "platform": details["platform"] }, } } + def get_game_directory(self, installer): + """Pull install location from installer""" + return os.path.dirname(installer["script"]["game"]["main_file"]) + class DolphinGame(ServiceGame): """Game for the Dolphin emulator""" @@ -66,11 +71,14 @@ class DolphinGame(ServiceGame): def new_from_cache(cls, cache_entry): """Create a service game from an entry from the Dolphin cache""" service_game = cls() - service_game.name = cls.get_game_name(cache_entry) - service_game.appid = str(cache_entry["code_1"]) - service_game.slug = slugify(cls.get_game_name(cache_entry)) + service_game.name = cache_entry["real_name"] + service_game.appid = str(cache_entry["game_id"]) + service_game.slug = slugify(cache_entry["real_name"]) service_game.icon = "" - service_game.details = json.dumps({"path": cache_entry["path"]}) + service_game.details = json.dumps({ + "path": cache_entry["path"], + "platform": cache_entry["platform"][:-1] + }) return service_game @staticmethod diff --git a/lutris/services/xdg.py b/lutris/services/xdg.py index a270884a3..e08367129 100644 --- a/lutris/services/xdg.py +++ b/lutris/services/xdg.py @@ -118,6 +118,10 @@ class XDGService(BaseService): } } + def get_game_directory(self, installer): + """Pull install location from installer""" + return os.path.dirname(installer["script"]["game"]["exe"]) + class XDGGame(ServiceGame): """XDG game (Linux game with a desktop launcher)""" diff --git a/lutris/util/dolphin/cache_reader.py b/lutris/util/dolphin/cache_reader.py index 6ee2748d5..71ef53f0b 100644 --- a/lutris/util/dolphin/cache_reader.py +++ b/lutris/util/dolphin/cache_reader.py @@ -27,11 +27,12 @@ class DolphinCacheReader: 'maker_short': 'a', 'maker_long': 'a', 'description': 'a', - 'some_other_name': 's', - 'code_1': 's', - 'code_2': 's', - 'field_c': 32, - 'field_d': 1, + 'real_name': 's', + 'game_id': 's', + 'game_tdbid': 's', + 'field_c': 6, + 'platform': 1, + 'field_d': 26, 'rel_date': 's', 'field_e': 8, 'banner': 'i', @@ -83,6 +84,7 @@ class DolphinCacheReader: if has_image: image = self.cache_content[self.offset:self.offset + 12288] self.offset += 12288 + image = '' return image def get_raw(self, word_len):