def on_steam_game_changed(self, operation, path): """Action taken when a Steam AppManifest file is updated""" appmanifest = steam.AppManifest(path) if self.running_game and 'steam' in self.running_game.runner_name: self.running_game.notify_steam_game_changed(appmanifest) runner_name = appmanifest.get_runner_name() games = pga.get_games_where(steamid=appmanifest.steamid) if operation == Gio.FileMonitorEvent.DELETED: for game in games: if game['runner'] == runner_name: steam.mark_as_uninstalled(game) self.view.set_uninstalled(Game(game['id'])) break elif operation in (Gio.FileMonitorEvent.CHANGED, Gio.FileMonitorEvent.CREATED): if not appmanifest.is_installed(): return if runner_name == 'winesteam': return game_info = None for game in games: if game['installed'] == 0: game_info = game else: # Game is already installed, don't do anything return if not game_info: game_info = { 'name': appmanifest.name, 'slug': appmanifest.slug, } if steam in get_services_synced_at_startup(): game_id = steam.mark_as_installed(appmanifest.steamid, runner_name, game_info) game_ids = [game['id'] for game in self.game_list] if game_id not in game_ids: self.add_game_to_view(game_id) else: self.view.set_installed(Game(game_id))
def fill_missing_platforms(): """Sets the platform on games where it's missing. This should never happen. """ pga_games = get_games(filters={"installed": 1}) for pga_game in pga_games: if pga_game.get("platform") or not pga_game["runner"]: continue game = Game(game_id=pga_game["id"]) game.set_platform_from_runner() if game.platform: logger.info("Platform for %s set to %s", game.name, game.platform) game.save(save_config=False)
def on_game_run(self, *args, game_id=None): """Launch a game, or install it if it is not""" if not game_id: game_id = self._get_current_game_id() if not game_id: return self.running_game = Game(game_id) if self.running_game.is_installed: self.running_game.play() else: game_slug = self.running_game.slug self.running_game = None InstallerDialog(game_slug=game_slug, parent=self)
def __init__(self, db_game, game_actions): """Create the game bar with a database row""" super().__init__(visible=True) GObject.add_emission_hook(Game, "game-start", self.on_game_state_changed) GObject.add_emission_hook(Game, "game-started", self.on_game_state_changed) GObject.add_emission_hook(Game, "game-stopped", self.on_game_state_changed) GObject.add_emission_hook(Game, "game-updated", self.on_game_state_changed) GObject.add_emission_hook(Game, "game-removed", self.on_game_state_changed) GObject.add_emission_hook(Game, "game-installed", self.on_game_state_changed) self.set_margin_bottom(12) self.game_actions = game_actions self.db_game = db_game if db_game.get("service"): self.service = services.get_services()[db_game["service"]]() else: self.service = None game_id = None if "service_id" in db_game: self.appid = db_game["service_id"] game_id = db_game["id"] elif self.service: self.appid = db_game["appid"] game = get_game_for_service(self.service.id, self.appid) if game: game_id = game["id"] self.game = Game(game_id) if game_id else Game() self.game_name = db_game["name"] self.game_slug = db_game["slug"] if self.game: game_actions.set_game(self.game) self.update_view()
def on_game_installed(self, view, game_id): """Callback to handle newly installed games""" if not isinstance(game_id, int): raise ValueError("game_id must be an int") if not self.view.has_game_id(game_id): logger.debug("Adding new installed game to view (%d)", game_id) self.add_game_to_view(game_id, is_async=False) game = Game(game_id) view.set_installed(game) self.sidebar_treeview.update() GLib.idle_add(resources.fetch_icons, [game.slug], self.on_image_downloaded)
def get_usage_stats(self): """Return the usage for each version""" runner_games = get_games_by_runner(self.runner) if self.runner == "wine": runner_games += get_games_by_runner("winesteam") version_usage = defaultdict(list) for db_game in runner_games: if not db_game["installed"]: continue game = Game(db_game["id"]) version = game.config.runner_config["version"] version_usage[version].append(db_game["id"]) return version_usage
def fill_missing_platforms(): """Sets the platform on games where it's missing. This should never happen. """ pga_games = pga.get_games(filter_installed=True) for pga_game in pga_games: if pga_game.get("platform") or not pga_game["runner"]: continue game = Game(game_id=pga_game["id"]) logger.error("Providing missing platorm for game %s", game.slug) game.set_platform_from_runner() if game.platform: game.save(metadata_only=True)
def platform(self): """Platform""" _platform = self._pga_data["platform"] if not _platform and self.installed: game_inst = Game(self._pga_data["id"]) if game_inst.platform: _platform = game_inst.platform else: logger.debug("Game %s has no platform", self) # Save the game, which will call `set_platform_from_runner` game_inst.save() _platform = game_inst.platform return _platform
def run_no_installer_dialog(self): """Open dialog for 'no script available' situation.""" dlg = NoInstallerDialog(self) if dlg.result == dlg.MANUAL_CONF: game_data = pga.get_game_by_field(self.game_slug, 'slug') game = Game(game_data['id']) AddGameDialog( self.parent, game=game, callback=lambda: self.notify_install_success(game_data['id']) ) elif dlg.result == dlg.NEW_INSTALLER: webbrowser.open(settings.GAME_URL % self.game_slug)
def on_edit_game_configuration(self, widget): """Edit game preferences.""" game = Game(self.view.selected_game) def on_dialog_saved(): game_id = dialog.game.id self.view.remove_game(game_id) self.view.add_game_by_id(game_id) self.view.set_selected_game(game_id) self.sidebar_treeview.update() if game.is_installed: dialog = EditGameConfigDialog(self, game, on_dialog_saved)
def __init__(self, game_id, callback, parent=None): super().__init__(parent=parent) self.set_size_request(640, 128) self.game = Game(game_id) self.callback = callback self.delete_files = False container = Gtk.VBox(visible=True) self.get_content_area().add(container) title_label = Gtk.Label(visible=True) title_label.set_line_wrap(True) title_label.set_alignment(0, 0.5) title_label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) title_label.set_markup( "<span font_desc='14'><b>Uninstall %s</b></span>" % gtk_safe(self.game.name)) container.pack_start(title_label, False, False, 4) self.folder_label = Gtk.Label(visible=True) self.folder_label.set_alignment(0, 0.5) self.folder_label.set_margin_bottom(30) self.delete_button = Gtk.Button(_("Uninstall"), visible=True) self.delete_button.connect("clicked", self.on_delete_clicked) if not self.game.directory: self.folder_label.set_markup("No file will be deleted") elif len(get_games(searches={"directory": self.game.directory})) > 1: self.folder_label.set_markup( "The folder %s is used by other games and will be kept." % self.game.directory) elif is_removeable(self.game.directory): self.delete_button.set_sensitive(False) self.folder_label.set_markup("<i>Calculating size…</i>") AsyncCall(get_disk_size, self.folder_size_cb, self.game.directory) else: self.folder_label.set_markup( "Content of %s are protected and will not be deleted." % reverse_expanduser(self.game.directory)) container.pack_start(self.folder_label, False, False, 4) button_box = Gtk.HBox(visible=True) style_context = button_box.get_style_context() style_context.add_class("linked") cancel_button = Gtk.Button(_("Cancel"), visible=True) cancel_button.connect("clicked", self.on_close) button_box.add(cancel_button) button_box.add(self.delete_button) container.pack_end(button_box, False, False, 0) self.show()
def install(self, db_game): appid = db_game["appid"] db_games = get_games(filters={"service_id": appid, "installed": "1", "service": "steam"}) existing_game = self.match_existing_game(db_games, appid) if existing_game: logger.debug("Found steam game: %s", existing_game) game = Game(existing_game.id) game.save() return service_installers = self.get_installers_from_api(appid) if not service_installers: service_installers = [self.generate_installer(db_game)] application = Gio.Application.get_default() application.show_installer_window(service_installers, service=self, appid=appid)
def on_game_updated(self, game): """Callback to refresh the view when a game is updated""" logger.debug("Updating game %s", game) if not game.is_installed: game = Game(game_id=game.id) self.game_selection_changed(None, None) game.load_config() try: self.game_store.update_game_by_id(game.id) except ValueError: self.game_store.add_game_by_id(game.id) self.game_panel.refresh() return True
def on_edit_game_configuration(self, _widget): """Edit game preferences""" init_dxvk_versions() game = Game(self.view.selected_game) def on_dialog_saved(): game_id = dialog.game.id self.view.remove_game(game_id) self.view.add_game_by_id(game_id) self.view.set_selected_game(game_id) self.sidebar_listbox.update() if game.is_installed: dialog = EditGameConfigDialog(self, game, on_dialog_saved)
def build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, "game", self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, "game", game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) game_sw.show() self.add_notebook_tab(game_sw, "Game configuration")
def add_game(self, game): name = game['name'].replace('&', '&').replace('<', '<').replace('>', '>') runner = None platform = '' runner_name = game['runner'] runner_human_name = '' if runner_name: game_inst = Game(game['id']) if not game_inst.is_installed: return if runner_name in self.runner_names: runner_human_name = self.runner_names[runner_name] else: try: runner = runners.import_runner(runner_name) except runners.InvalidRunner: game['installed'] = False else: runner_human_name = runner.human_name self.runner_names[runner_name] = runner_human_name platform = game_inst.platform if not platform: game_inst.set_platform_from_runner() platform = game_inst.platform lastplayed_text = '' if game['lastplayed']: lastplayed_text = time.strftime("%c", time.localtime(game['lastplayed'])) installed_at_text = '' if game['installed_at']: installed_at_text = time.strftime("%c", time.localtime(game['installed_at'])) pixbuf = get_pixbuf_for_game(game['slug'], self.icon_type, game['installed']) self.store.append(( game['id'], game['slug'], name, pixbuf, str(game['year'] or ''), runner_name, runner_human_name, platform, game['lastplayed'], lastplayed_text, game['installed'], game['installed_at'], installed_at_text ))
def manually_configure_game(self): game_data = pga.get_game_by_field(self.game_slug, "slug") if game_data and "slug" in game_data: # Game data already exist locally. game = Game(game_data["id"]) else: # Try to load game data from remote. games = api.get_api_games([self.game_slug]) if games and len(games) >= 1: remote_game = games[0] game_data = { "name": remote_game["name"], "slug": remote_game["slug"], "year": remote_game["year"], "updated": remote_game["updated"], "steamid": remote_game["steamid"], } game = Game(pga.add_game(**game_data)) else: game = None AddGameDialog(self.parent, game=game)
def on_steam_game_changed(self, operation, path): appmanifest = steam.AppManifest(path) runner_name = appmanifest.get_runner_name() games = pga.get_game_by_field(appmanifest.steamid, field='steamid', all=True) if operation == Gio.FileMonitorEvent.DELETED: for game in games: if game['runner'] == runner_name: steam.mark_as_uninstalled(game) self.view.set_uninstalled(Game(game['id'])) break elif operation in (Gio.FileMonitorEvent.CHANGED, Gio.FileMonitorEvent.CREATED): if not appmanifest.is_installed(): return if runner_name == 'winesteam': return game_info = None for game in games: if game['installed'] == 0: game_info = game else: # Game is already installed, don't do anything return if not game_info: game_info = { 'name': appmanifest.name, 'slug': appmanifest.slug, } game_id = steam.mark_as_installed(appmanifest.steamid, runner_name, game_info) game_ids = [game['id'] for game in self.game_list] if game_id not in game_ids: self.add_game_to_view(game_id) else: self.view.set_installed(Game(game_id))
def on_save(self, _button, callback=None): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game() year = None if self.year_entry.get_text(): year = int(self.year_entry.get_text()) if self.lutris_config.game_config_id == TEMP_CONFIG: self.lutris_config.game_config_id = self.get_config_id() # Delete the old copy of the game if the runner changes runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) if self.game.id and self.game.platform != runner.get_platform(): self.game.remove() self.game.runner_name = self.runner_name self.game.name = name self.game.slug = self.slug self.game.year = year self.game.config = self.lutris_config fps_limit = self.game.config.system_config.get("fps_limit", None) if fps_limit: try: int(fps_limit) except ValueError: ErrorDialog("Fps limit only accept numbers") return self.game.directory = runner.game_path self.game.is_installed = True if self.runner_name in ("steam", "winesteam"): self.game.steamid = self.lutris_config.game_config["appid"] self.game.set_platform_from_runner() self.game.save() self.destroy() self.saved = True if callback: callback()
def on_game_activated(self, view, game_id): """Handles view activations (double click, enter press)""" if self.service: 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) self.service.install(db_game) return game = Game(game_id) if game.is_installed: logger.info("Game is installed") game.emit("game-launch")
def get_runner_info(self, game): if not game["runner"]: return runner_human_name = self.runner_names.get(game["runner"], "") platform = game["platform"] if not platform and game["installed"]: game_inst = Game(game["id"]) platform = game_inst.platform if not platform: game_inst.set_platform_from_runner() platform = game_inst.platform logger.debug("Setting platform for %s: %s", game["name"], platform) return runner_human_name, platform
def on_game_clicked(self, *args): """Launch a game.""" # Wait two seconds to avoid running a game twice if time.time() - self.game_launch_time < 2: return self.game_launch_time = time.time() game_slug = self.view.selected_game if game_slug: self.running_game = Game(game_slug) if self.running_game.is_installed: self.stop_button.set_sensitive(True) self.running_game.play() else: InstallerDialog(game_slug, self)
def add_installed_games(self): """Syncs installed Steam games with Lutris""" installed_appids = [] for steamapps_path in self.steamapps_paths: for appmanifest_file in get_appmanifests(steamapps_path): app_manifest_path = os.path.join(steamapps_path, appmanifest_file) app_manifest = AppManifest(app_manifest_path) installed_appids.append(app_manifest.steamid) self.install_from_steam(app_manifest) db_games = get_games(filters={"runner": "steam"}) for db_game in db_games: steam_game = Game(db_game["id"]) try: appid = steam_game.config.game_level["game"]["appid"] except KeyError: logger.warning("Steam game %s has no AppID") continue if appid not in installed_appids: steam_game.remove(no_signal=True) db_appids = defaultdict(list) db_games = get_games(filters={"service": "steam"}) for db_game in db_games: db_appids[db_game["service_id"]].append(db_game["id"]) for appid in db_appids: game_ids = db_appids[appid] if len(game_ids) == 1: continue for game_id in game_ids: steam_game = Game(game_id) if not steam_game.playtime: steam_game.remove(no_signal=True) steam_game.delete()
def on_add_manually(self, _widget, *_args): """Callback that presents the Add game dialog""" def on_game_added(game): self.view.set_installed(game) GLib.idle_add(resources.fetch_icon, game.slug, self.on_image_downloaded) self.sidebar_listbox.update() game = Game(self.view.selected_game) AddGameDialog( self, game=game, runner=self.selected_runner, callback=lambda: on_game_added(game), )
def scan_directory(dirname): files = os.listdir(dirname) folder_extentions = {os.path.splitext(filename)[1] for filename in files} core_matches = {} for core in RECOMMENDED_CORES: for ext in RECOMMENDED_CORES[core].get("extensions", []): if ext in folder_extentions: core_matches[ext] = core added_games = [] for filename in files: name, ext = os.path.splitext(filename) if ext not in core_matches: continue for flag in ROM_FLAGS: name = name.replace(" (%s)" % flag, "") for flag in EXTRA_FLAGS: name = name.replace("[%s]" % flag, "") if ", The" in name: name = "The %s" % name.replace(", The", "") name = name.strip() print("Importing '%s'" % name) slug = slugify(name) core = core_matches[ext] config = { "game": { "core": core_matches[ext], "main_file": os.path.join(dirname, filename) } } installer_slug = "%s-libretro-%s" % (slug, core) existing_game = get_games(filters={ "installer_slug": installer_slug, "installed": "1" }) if existing_game: game = Game(existing_game[0]["id"]) game.remove() configpath = write_game_config(slug, config) game_id = add_game(name=name, runner="libretro", slug=slug, directory=dirname, installed=1, installer_slug=installer_slug, configpath=configpath) print("Imported %s" % name) added_games.append(game_id) return added_games
def migrate(): pcsxr_game_ids = sql.db_query(PGA_DB, "select id from games where runner='pcsxr'") for game_id in pcsxr_game_ids: game = Game(game_id["id"]) main_file = game.config.raw_game_config.get("iso") game.config.game_level = { "game": { "core": "pcsx_rearmed", "main_file": main_file } } game.config.save() sql.db_update(PGA_DB, "games", {"runner": "libretro"}, where=("runner", "pcsxr"))
def migrate(): pcsxr_game_ids = sql.db_query(PGA_DB, "select id from games where runner='pcsxr'") for game_id in pcsxr_game_ids: game = Game(game_id['id']) main_file = game.config.raw_game_config.get('iso') game.config.game_level = { 'game': { 'core': 'pcsx_rearmed', 'main_file': main_file } } game.config.save() sql.db_update(PGA_DB, 'games', {'runner': 'libretro'}, where=('runner', 'pcsxr'))
def launch_service_game(self, runner, installer_slug): """For services that allow it, add the game to Lutris and launch it""" config_id = self.game_slug + "-" + self.service.id game_id = add_or_update( name=self.game_name, runner=runner, slug=self.game_slug, installed=1, configpath=config_id, installer_slug=installer_slug, service=self.service.id, service_id=self.db_game["appid"], ) self.service.create_config(self.db_game, config_id) game = Game(game_id) game.emit("game-launch")
def on_game_run(self, *_args, game_id=None): """Launch a game, or install it if it is not""" if not game_id: game_id = self._get_current_game_id() if not game_id: return None self.running_game = Game(game_id) if self.running_game.is_installed: self.running_game.play() else: game_slug = self.running_game.slug logger.warning("%s is not available", game_slug) self.running_game = None InstallerWindow(game_slug=game_slug, parent=self, application=self.application)
def _build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name try: self.game.runner = runners.import_runner(self.runner_name) except runners.InvalidRunner: pass self.game_box = GameBox(self.lutris_config, self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(game_sw, "Game options")