diff -Nru lutris-0.5.11~ubuntu22.04.1/debian/changelog lutris-0.5.12~ubuntu22.04.1/debian/changelog --- lutris-0.5.11~ubuntu22.04.1/debian/changelog 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/debian/changelog 2022-12-03 12:25:26.000000000 +0000 @@ -1,8 +1,22 @@ -lutris (0.5.11~ubuntu22.04.1) jammy; urgency=low +lutris (0.5.12~ubuntu22.04.1) jammy; urgency=low * Auto build. - -- gogo Sun, 20 Nov 2022 10:53:21 +0000 + -- gogo Sat, 03 Dec 2022 12:25:26 +0000 + +lutris (0.5.12) jammy; urgency=medium + + * Add support for Xbox games with the xemu runner + * Fix authentication issue with Origin + * Fix authentication issue with EGS + * Fix authentication issue with Ubisoft Connect when 2FA is enabled + * Fix integration issue with GOG + * Add Discord Rich Presence integration + * Add ability to extract icons from Windows executables + * Allow setting custom cover art + * Re-style configuration dialogs + + -- Mathieu Comandon Tue, 18 Oct 2022 16:18:45 -0700 lutris (0.5.11) jammy; urgency=medium diff -Nru lutris-0.5.11~ubuntu22.04.1/debian/control lutris-0.5.12~ubuntu22.04.1/debian/control --- lutris-0.5.11~ubuntu22.04.1/debian/control 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/debian/control 2022-12-03 12:25:25.000000000 +0000 @@ -26,8 +26,7 @@ python3-distro, python3-dbus, gir1.2-gtk-3.0, - gir1.2-webkit2-4.0, - gir1.2-notify-0.7, + gir1.2-webkit2-4.1, psmisc, cabextract, unzip, diff -Nru lutris-0.5.11~ubuntu22.04.1/debian/git-build-recipe.manifest lutris-0.5.12~ubuntu22.04.1/debian/git-build-recipe.manifest --- lutris-0.5.11~ubuntu22.04.1/debian/git-build-recipe.manifest 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/debian/git-build-recipe.manifest 2022-12-03 12:25:26.000000000 +0000 @@ -1,2 +1,2 @@ -# git-build-recipe format 0.4 deb-version 0.5.11 -lp:~lutris-team/lutris/+git/lutris git-commit:98a4005a7babe7cc9aae47cb2e453c0978aedde0 +# git-build-recipe format 0.4 deb-version 0.5.12 +lp:~lutris-team/lutris/+git/lutris git-commit:10933ba9203a3f008646010005cdf19a6d97552c diff -Nru lutris-0.5.11~ubuntu22.04.1/INSTALL.rst lutris-0.5.12~ubuntu22.04.1/INSTALL.rst --- lutris-0.5.11~ubuntu22.04.1/INSTALL.rst 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/INSTALL.rst 2022-12-03 12:25:25.000000000 +0000 @@ -12,7 +12,7 @@ * Python >= 3.7 * PyGObject - * PyGObject bindings for: Gtk, Gdk, GnomeDesktop, Webkit2, Notify + * PyGObject bindings for: Gtk, Gdk, GnomeDesktop, Webkit2 * python3-requests * python3-pillow * python3-yaml @@ -41,8 +41,8 @@ on Ubuntu based systems, you can run:: sudo apt install python3-yaml python3-requests python3-pil python3-gi \ - gir1.2-gtk-3.0 gir1.2-gnomedesktop-3.0 gir1.2-webkit2-4.0 \ - gir1.2-notify-0.7 psmisc cabextract unzip p7zip curl fluid-soundfont-gs \ + gir1.2-gtk-3.0 gir1.2-gnomedesktop-3.0 gir1.2-webkit2-4.1 \ + psmisc cabextract unzip p7zip curl fluid-soundfont-gs \ x11-xserver-utils python3-evdev libc6-i386 lib32gcc1 libgirepository1.0-dev \ python3-setproctitle python3-distro diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/api.py lutris-0.5.12~ubuntu22.04.1/lutris/api.py --- lutris-0.5.11~ubuntu22.04.1/lutris/api.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/api.py 2022-12-03 12:25:25.000000000 +0000 @@ -91,10 +91,10 @@ return response.json() -def get_http_response(url, payload): +def get_http_post_response(url, payload): response = http.Request(url, headers={"Content-Type": "application/json"}) try: - response.get(data=payload) + response.post(data=payload) except http.HTTPError as ex: logger.error("Unable to get games from API: %s", ex) return None @@ -117,7 +117,7 @@ if not game_slugs: return [] payload = json.dumps({"games": game_slugs, "page": page}).encode("utf-8") - return get_http_response(url, payload) + return get_http_post_response(url, payload) def get_game_service_api_page(service, appids, page=1): @@ -128,7 +128,7 @@ if not appids: return [] payload = json.dumps({"appids": appids}).encode("utf-8") - return get_http_response(url, payload) + return get_http_post_response(url, payload) def get_api_games(game_slugs=None, page=1, service=None): @@ -183,6 +183,17 @@ return [normalize_installer(i) for i in installers] +def get_game_details(slug): + url = settings.SITE_URL + "/api/games/%s" % slug + request = http.Request(url) + try: + response = request.get() + except http.HTTPError as ex: + logger.debug("Unable to load %s: %s", slug, ex) + return {} + return response.json + + def normalize_installer(installer): """Adjusts an installer dict so it is in the correct form, with values of the expected types.""" diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/command.py lutris-0.5.12~ubuntu22.04.1/lutris/command.py --- lutris-0.5.11~ubuntu22.04.1/lutris/command.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/command.py 2022-12-03 12:25:25.000000000 +0000 @@ -167,7 +167,7 @@ """Add the line to the associated LogBuffer object""" self.log_buffer.insert(self.log_buffer.get_end_iter(), line, -1) - def log_handler_console_output(self, line): # pylint: disable=no-self-use + def log_handler_console_output(self, line): """Print the line to stdout""" with contextlib.suppress(BlockingIOError): sys.stdout.write(line) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/database/schema.py lutris-0.5.12~ubuntu22.04.1/lutris/database/schema.py --- lutris-0.5.11~ubuntu22.04.1/lutris/database/schema.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/database/schema.py 2022-12-03 12:25:25.000000000 +0000 @@ -74,6 +74,10 @@ "type": "INTEGER" }, { + "name": "has_custom_coverart_big", + "type": "INTEGER" + }, + { "name": "playtime", "type": "REAL" }, @@ -88,7 +92,11 @@ { "name": "service_id", "type": "TEXT" - } + }, + { + "name": "discord_id", + "type": "TEXT", + }, ], "service_games": [ { diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/discord.py lutris-0.5.12~ubuntu22.04.1/lutris/discord.py --- lutris-0.5.11~ubuntu22.04.1/lutris/discord.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/discord.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,99 +0,0 @@ -"""Discord integration""" -# Standard Library -import asyncio -import time - -# Lutris Modules -from lutris.util.log import logger - -try: - from pypresence import Presence as PyPresence - from pypresence.exceptions import PyPresenceException -except ImportError: - PyPresence = None - PyPresenceException = None - - -class DiscordPresence(object): - - """Provide rich presence integration with Discord for games""" - - def __init__(self): - self.available = bool(PyPresence) - self.game_name = "" - self.runner_name = "" - self.last_rpc = 0 - self.rpc_interval = 60 - self.presence_connected = False - self.rpc_client = None - self.client_id = None - - def connect(self): - """Make sure we are actually connected before trying to send requests""" - if not self.presence_connected: - self.rpc_client = PyPresence(self.client_id) - try: - self.rpc_client.connect() - self.presence_connected = True - except (ConnectionError, FileNotFoundError): - logger.error("Could not connect to Discord") - return self.presence_connected - - def disconnect(self): - """Ensure we are definitely disconnected and fix broken event loop from pypresence - That method is a huge mess of non-deterministic bs and should be nuked from orbit. - """ - if self.rpc_client: - try: - self.rpc_client.close() - except Exception as e: - logger.exception("Unable to close Discord RPC connection: %s", e) - if self.rpc_client.sock_writer is not None: - try: - self.rpc_client.sock_writer.close() - except Exception: - logger.exception("Sock writer could not be closed.") - try: - logger.debug("Forcefully closing event loop.") - self.rpc_client.loop.close() - except Exception: - logger.debug("Could not close event loop.") - try: - logger.debug("Forcefully replacing event loop.") - self.rpc_client.loop = None - asyncio.set_event_loop(asyncio.new_event_loop()) - except Exception as e: - logger.exception("Could not replace event loop: %s", e) - try: - logger.debug("Forcefully deleting RPC client.") - self.rpc_client = None - except Exception as ex: - logger.exception(ex) - self.rpc_client = None - self.presence_connected = False - - def update_discord_rich_presence(self): - """Dispatch a request to Discord to update presence""" - if int(time.time()) - self.rpc_interval < self.last_rpc: - logger.debug("Not enough time since last RPC") - return - - self.last_rpc = int(time.time()) - if not self.connect(): - return - try: - self.rpc_client.update(details="Playing %s" % self.game_name, - large_image="large_image", - large_text=self.game_name, - small_image="small_image") - except PyPresenceException as ex: - logger.error("Unable to update Discord: %s", ex) - - def clear_discord_rich_presence(self): - """Dispatch a request to Discord to clear presence""" - if self.connect(): - try: - self.rpc_client.clear() - except PyPresenceException as ex: - logger.error("Unable to clear Discord: %s", ex) - self.disconnect() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/exceptions.py lutris-0.5.12~ubuntu22.04.1/lutris/exceptions.py --- lutris-0.5.11~ubuntu22.04.1/lutris/exceptions.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/exceptions.py 2022-12-03 12:25:25.000000000 +0000 @@ -19,7 +19,7 @@ """ -class UnavailableLibraries(RuntimeError): +class UnavailableLibrariesError(RuntimeError): def __init__(self, libraries, arch=None): message = _( @@ -36,17 +36,34 @@ """Raised when authentication to a service fails""" -class UnavailableGame(Exception): +class UnavailableGameError(Exception): """Raised when a game is available from a service""" -class MultipleInstallerError(BaseException): +class UnavailableRunnerError(Exception): + """Raised when a runner is not installed or not installed fully.""" - """Current implementation doesn't know how to deal with multiple installers - Raise this if a game returns more than 1 installer.""" +def watch_errors(error_result=None): + """Decorator used to catch exceptions for GUI signal handlers. This + catches any exception from the decorated function and calls + on_watch_errors(error) on the first argument, which we presume to be self. + and then the method will return 'error_result'""" + + def inner_decorator(function): + @wraps(function) + def wrapper(*args, **kwargs): + myself = args[0] + try: + return function(*args, **kwargs) + except Exception as ex: + myself.on_watched_error(ex) + return error_result + return wrapper + return inner_decorator -def watch_lutris_errors(game_stop_result): + +def watch_game_errors(game_stop_result): """Decorator used to catch exceptions and send events instead of propagating them normally. If 'game_stop_result' is not None, and the decorated function returns that, this will send game-stop and make the game stopped as well. This simplifies handling cancellation. diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/game_actions.py lutris-0.5.12~ubuntu22.04.1/lutris/game_actions.py --- lutris-0.5.11~ubuntu22.04.1/lutris/game_actions.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/game_actions.py 2022-12-03 12:25:25.000000000 +0000 @@ -23,6 +23,7 @@ from lutris.util.steam import shortcut as steam_shortcut from lutris.util.strings import gtk_safe from lutris.util.system import path_exists +from lutris.util.wine.dxvk import update_shader_cache class GameActions: @@ -64,11 +65,11 @@ ("install_dlcs", "Install DLCs", self.on_install_dlc_clicked), ("show_logs", _("Show logs"), self.on_show_logs), ("add", _("Add installed game"), self.on_add_manually), - ("duplicate", _("Duplicate"), self.on_game_duplicate), ("configure", _("Configure"), self.on_edit_game_configuration), ("favorite", _("Add to favorites"), self.on_add_favorite_game), ("deletefavorite", _("Remove from favorites"), self.on_delete_favorite_game), ("execute-script", _("Execute script"), self.on_execute_script_clicked), + ("update-shader-cache", _("Update shader cache"), self.on_update_shader_cache), ("browse", _("Browse files"), self.on_browse_files), ( "desktop-shortcut", @@ -102,6 +103,7 @@ ), ("install_more", _("Install another version"), self.on_install_clicked), ("remove", _("Remove"), self.on_remove_game), + ("duplicate", _("Duplicate"), self.on_game_duplicate), ("view", _("View on Lutris.net"), self.on_view_game), ("hide", _("Hide game from library"), self.on_hide_game), ("unhide", _("Unhide game from library"), self.on_unhide_game), @@ -115,6 +117,7 @@ "install": not self.game.is_installed, "play": self.game.is_installed and not self.is_game_running, "update": self.game.is_updatable, + "update-shader-cache": self.game.is_cache_managed, "install_dlcs": self.game.is_updatable, "stop": self.is_game_running, "configure": bool(self.game.is_installed), @@ -200,6 +203,9 @@ def on_install_dlc_clicked(self, _widget): self.game.emit("game-install-dlc") + def on_update_shader_cache(self, _widget): + update_shader_cache(self.game) + def on_locate_installed_game(self, _button, game): """Show the user a dialog to import an existing install to a DRM free service diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/game.py lutris-0.5.12~ubuntu22.04.1/lutris/game.py --- lutris-0.5.11~ubuntu22.04.1/lutris/game.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/game.py 2022-12-03 12:25:25.000000000 +0000 @@ -18,7 +18,7 @@ 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, watch_lutris_errors +from lutris.exceptions import GameConfigError, watch_game_errors from lutris.gui import dialogs from lutris.runner_interpreter import export_bash_script, get_launch_parameters from lutris.runners import InvalidRunner, import_runner, wine @@ -51,6 +51,7 @@ __gsignals__ = { "game-error": (GObject.SIGNAL_RUN_FIRST, None, (object, )), + "game-notice": (GObject.SIGNAL_RUN_FIRST, None, (str, str)), "game-launch": (GObject.SIGNAL_RUN_FIRST, None, ()), "game-start": (GObject.SIGNAL_RUN_FIRST, None, ()), "game-started": (GObject.SIGNAL_RUN_FIRST, None, ()), @@ -83,8 +84,13 @@ self.platform = game_data.get("platform") or "" self.year = game_data.get("year") or "" self.lastplayed = game_data.get("lastplayed") or 0 - self.has_custom_banner = bool(game_data.get("has_custom_banner")) - self.has_custom_icon = bool(game_data.get("has_custom_icon")) + self.custom_images = set() + if game_data.get("has_custom_banner"): + self.custom_images.add("banner") + if game_data.get("has_custom_icon"): + self.custom_images.add("icon") + if game_data.get("has_custom_coverart_big"): + self.custom_images.add("coverart_big") self.service = game_data.get("service") self.appid = game_data.get("service_id") self.playtime = game_data.get("playtime") or 0.0 @@ -107,6 +113,9 @@ self.timer = Timer() self.screen_saver_inhibitor_cookie = None + # Adding Discord App ID for RPC + self.discord_id = game_data.get('discord_id') + def __repr__(self): return self.__str__() @@ -117,6 +126,14 @@ return value @property + def is_cache_managed(self): + """Is the DXVK cache receiving updates from lutris?""" + if self.runner: + env = self.runner.system_config.get("env", {}) + return "DXVK_STATE_CACHE_PATH" in env + return False + + @property def is_updatable(self): """Return whether the game can be upgraded""" return self.service == "gog" @@ -196,7 +213,16 @@ def get_browse_dir(self): """Return the path to open with the Browse Files action.""" - return self.runner.resolve_game_path() + return self.resolve_game_path() + + def resolve_game_path(self): + """Return the game's directory; if it is not known this will try to find + it. This can still return an empty string if it can't do that.""" + if self.directory: + return self.directory + if self.runner: + return self.runner.resolve_game_path() + return "" def _get_runner(self): """Return the runner instance for this game's configuration""" @@ -236,6 +262,8 @@ self.config.remove() xdgshortcuts.remove_launcher(self.slug, self.id, desktop=True, menu=True) if delete_files and self.runner: + # self.directory here, not self.resolve_game_path; no guessing at + # directories when we delete them self.runner.remove_game_data(app_id=self.appid, game_path=self.directory) self.is_installed = False self.runner = None @@ -243,11 +271,13 @@ return self.emit("game-removed") - def delete(self): + def delete(self, no_signal=False): """Completely remove a game from the library""" if self.is_installed: - raise RuntimeError("Uninstall the game before deleting") + raise RuntimeError(_("Uninstall the game before deleting")) games_db.delete_game(self.id) + if no_signal: + return self.emit("game-removed") def set_platform_from_runner(self): @@ -289,6 +319,10 @@ hidden=self.is_hidden, service=self.service, service_id=self.appid, + discord_id=self.discord_id, + has_custom_banner="banner" in self.custom_images, + has_custom_icon="icon" in self.custom_images, + has_custom_coverart_big="coverart_big" in self.custom_images ) self.emit("game-updated") @@ -307,7 +341,7 @@ if self.runner.use_runtime(): runtime_updater = runtime.RuntimeUpdater() if runtime_updater.is_updating(): - dialogs.ErrorDialog(_("Runtime currently updating"), _("Game might not work as expected")) + self.emit("game-notice", _("Runtime currently updating"), _("Game might not work as expected")) if ("wine" in self.runner_name and not wine.get_wine_version() and not LINUX_SYSTEM.is_flatpak): dialogs.WineNotInstalledWarning(parent=None) return True @@ -369,14 +403,14 @@ if wait_for_completion: logger.info("Prelauch command: %s, waiting for completion", prelaunch_command) # Monitor the prelaunch command and wait until it has finished - system.execute(command_array, env=env, cwd=self.directory) + system.execute(command_array, env=env, cwd=self.resolve_game_path()) else: logger.info("Prelaunch command %s launched in the background", prelaunch_command) self.prelaunch_executor = MonitoredCommand( command_array, include_processes=[os.path.basename(command_array[0])], env=env, - cwd=self.directory, + cwd=self.resolve_game_path(), ) self.prelaunch_executor.start() @@ -428,10 +462,12 @@ gameplay_info["command"] = [gameplay_info["command"][0], config["exe"]] if config.get("args"): gameplay_info["command"] += strings.split_arguments(config["args"]) + if config.get("working_dir"): + gameplay_info["working_dir"] = config["working_dir"] return gameplay_info - @watch_lutris_errors(game_stop_result=False) + @watch_game_errors(game_stop_result=False) def configure_game(self, _ignored, error=None): # noqa: C901 """Get the game ready to start, applying all the options This methods sets the game_runtime_config attribute. @@ -451,6 +487,9 @@ "exclude_processes": shlex.split(self.runner.system_config.get("exclude_processes", "")), } + if "working_dir" in gameplay_info: + self.game_runtime_config["working_dir"] = gameplay_info["working_dir"] + # Audio control if self.runner.system_config.get("reset_pulse"): audio.reset_pulse() @@ -494,14 +533,13 @@ self.start_game() return True - @watch_lutris_errors(game_stop_result=False) + @watch_game_errors(game_stop_result=False) def launch(self): """Request launching a game. The game may not be installed yet.""" if not self.is_launchable(): logger.error("Game is not launchable") return False - self.load_config() # Reload the config before launching it. saves = self.config.game_level["game"].get("saves") if saves: @@ -513,9 +551,7 @@ self.state = self.STATE_LAUNCHING self.prelaunch_pids = system.get_running_pid_list() - discord_token = settings.read_setting('discord_token') - if discord_token: - discord.set_discord_status(discord_token, "Playing %s" % self.name) + self.emit("game-start") jobs.AsyncCall(self.runner.prelaunch, self.configure_game) return True @@ -526,6 +562,7 @@ self.game_runtime_config["args"], title=self.name, runner=self.runner, + cwd=self.game_runtime_config.get("working_dir"), env=self.game_runtime_config["env"], term=self.game_runtime_config["terminal"], log_buffer=self.log_buffer, @@ -539,6 +576,15 @@ self.timer.start() self.state = self.STATE_RUNNING self.emit("game-started") + + print(f"Discord ID: {self.discord_id}") + # Game is running, let's update discord status + if settings.read_setting('discord_rpc') == 'True' and self.discord_id: + logger.info("Updating Discord RPC Status") + discord.client.update(self.discord_id) + else: + logger.info("Discord RPC Disabled or Discord APP ID Not Present") + self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat) with open(self.now_playing_path, "w", encoding="utf-8") as np_file: np_file.write(self.name) @@ -566,7 +612,7 @@ def death_watch_cb(all_died, error): """Called after the death watch to more firmly kill any survivors.""" if error: - dialogs.ErrorDialog(str(error)) + self.emit("game-error", error) elif not all_died: self.kill_processes(signal.SIGKILL) # If we still can't kill everything, we'll still say we stopped it. @@ -597,7 +643,7 @@ """Return a list of processes belonging to the Lutris game""" new_pids = self.get_new_pids() game_pids = [] - game_folder = self.runner.resolve_game_path() + game_folder = self.resolve_game_path() for pid in new_pids: cmdline = Process(pid).cmdline or "" # pressure-vessel: This could potentially pick up PIDs not started by lutris? @@ -628,7 +674,7 @@ self.timer.end() self.playtime += self.timer.duration / 3600 - @watch_lutris_errors(game_stop_result=False) + @watch_game_errors(game_stop_result=False) def beat(self): """Watch the game's process(es).""" if self.game_thread.error: @@ -658,7 +704,11 @@ logger.info("Stopping %s", self) if self.game_thread: - jobs.AsyncCall(self.game_thread.stop, None) + def stop_cb(result, error): + if error: + self.emit("game-error", error) + + jobs.AsyncCall(self.game_thread.stop, stop_cb) self.stop_game() def on_game_quit(self): @@ -668,6 +718,13 @@ logger.info("Stopping prelaunch script") self.prelaunch_executor.stop() + # We need to do some cleanup before we emit game-stop as this can + # trigger Lutris shutdown + + if self.screen_saver_inhibitor_cookie is not None: + SCREEN_SAVER_INHIBITOR.uninhibit(self.screen_saver_inhibitor_cookie) + self.screen_saver_inhibitor_cookie = None + self.heartbeat = None if self.state != self.STATE_STOPPED: logger.warning("Game still running (state: %s)", self.state) @@ -683,7 +740,7 @@ command_array, include_processes=[os.path.basename(postexit_command)], env=self.game_runtime_config["env"], - cwd=self.directory, + cwd=self.resolve_game_path(), ) postexit_thread.start() @@ -703,10 +760,6 @@ if self.compositor_disabled: self.set_desktop_compositing(True) - if self.screen_saver_inhibitor_cookie is not None: - SCREEN_SAVER_INHIBITOR.uninhibit(self.screen_saver_inhibitor_cookie) - self.screen_saver_inhibitor_cookie = None - if self.runner.system_config.get("use_us_layout"): with subprocess.Popen(["setxkbmap"], env=os.environ) as setxkbmap: setxkbmap.communicate() @@ -714,9 +767,10 @@ if self.runner.system_config.get("restore_gamma"): restore_gamma() - discord_token = settings.read_setting('discord_token') - if discord_token: - discord.set_discord_status(discord_token, "") + # Clear Discord Client Status + if settings.read_setting('discord_rpc') == 'True' and self.discord_id: + logger.debug("Clearing Discord RPC") + discord.client.clear() self.process_return_codes() @@ -830,7 +884,7 @@ with open(os.path.join(game_dir, game_config), encoding="utf-8") as config_file: lutris_config = json.load(config_file) old_dir = lutris_config["directory"] - with open(os.path.join(game_dir, game_config), 'r', encoding="utf-8") as config_file : + with open(os.path.join(game_dir, game_config), 'r', encoding="utf-8") as config_file: config_data = config_file.read() config_data = config_data.replace(old_dir, game_dir) with open(os.path.join(game_dir, game_config), 'w', encoding="utf-8") as config_file: diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/addgameswindow.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/addgameswindow.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/addgameswindow.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/addgameswindow.py 2022-12-03 12:25:25.000000000 +0000 @@ -157,7 +157,7 @@ def _on_folder_scanned(self, result, error): if error: - ErrorDialog(error) + ErrorDialog(str(error), parent=self) self.destroy() return for child in self.vbox.get_children(): @@ -211,7 +211,7 @@ def update_search_results_cb(self, api_games, error): if error: - ErrorDialog(error) + ErrorDialog(str(error), parent=self) return self.search_spinner.stop() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/application.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/application.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/application.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/application.py 2022-12-03 12:25:25.000000000 +0000 @@ -34,17 +34,18 @@ from lutris.runners import get_runner_names, import_runner, InvalidRunner, RunnerInstallationError from lutris import settings from lutris.api import parse_installer_url, get_runners +from lutris.exceptions import watch_errors from lutris.command import exec_command from lutris.database import games as games_db from lutris.game import Game, export_game, import_game from lutris.installer import get_installers from lutris.gui.dialogs.download import simple_downloader -from lutris.gui.dialogs import ErrorDialog, InstallOrPlayDialog, LutrisInitDialog +from lutris.gui.dialogs import ErrorDialog, InstallOrPlayDialog, NoticeDialog, LutrisInitDialog from lutris.gui.dialogs.issue import IssueReportWindow -from lutris.gui.installerwindow import InstallerWindow +from lutris.gui.installerwindow import InstallerWindow, InstallationKind from lutris.gui.widgets.status_icon import LutrisStatusIcon from lutris.migrations import migrate -from lutris.startup import init_lutris, run_all_checks, update_runtime +from lutris.startup import init_lutris, run_all_checks, StartupRuntimeUpdater from lutris.style_manager import StyleManager from lutris.util import datapath, log, system from lutris.util.http import HTTPError, Request @@ -85,7 +86,7 @@ self.style_manager = None if os.geteuid() == 0: - ErrorDialog(_("Running Lutris as root is not recommended and may cause unexpected issues")) + NoticeDialog(_("Running Lutris as root is not recommended and may cause unexpected issues")) try: self.css_provider.load_from_path(os.path.join(datapath.get(), "ui", "lutris.css")) @@ -107,7 +108,7 @@ "To install a game, add lutris:install/game-identifier." )) else: - logger.warning("GLib.set_option_context_summary missing, " "was added in GLib 2.56 (Released 2018-03-12)") + logger.warning("GLib.set_option_context_summary missing, was added in GLib 2.56 (Released 2018-03-12)") self.add_main_option( "version", ord("v"), @@ -285,7 +286,8 @@ if os.environ.get("LUTRIS_SKIP_INIT"): logger.debug("Skipping initialization") else: - init_dialog = LutrisInitDialog(update_runtime) + runtime_updater = StartupRuntimeUpdater(force=False) + init_dialog = LutrisInitDialog(runtime_updater) init_dialog.run() def get_window_key(self, **kwargs): @@ -327,13 +329,13 @@ window_inst.show() return window_inst - def show_installer_window(self, installers, service=None, appid=None, is_update=False): + def show_installer_window(self, installers, service=None, appid=None, installation_kind=InstallationKind.INSTALL): self.show_window( InstallerWindow, installers=installers, service=service, appid=appid, - is_update=is_update + installation_kind=installation_kind ) def on_app_window_destroyed(self, app_window, window_key): @@ -564,8 +566,8 @@ if db_game and db_game["installed"]: # Game found but no action provided, ask what to do dlg = InstallOrPlayDialog(db_game["name"]) - if not dlg.action_confirmed: - action = None + if not dlg.action: + action = "cancel" elif dlg.action == "play": action = "rungame" elif dlg.action == "install": @@ -582,6 +584,11 @@ service.install(service_game) return 0 + if action == "cancel": + if not self.window.is_visible(): + self.do_shutdown() + return 0 + if action == "install": installers = get_installers( game_slug=game_slug, @@ -590,7 +597,6 @@ ) if installers: self.show_installer_window(installers) - elif action in ("rungame", "rungameid"): if not db_game or not db_game["id"]: logger.warning("No game found in library") @@ -598,7 +604,7 @@ self.do_shutdown() return 0 game = Game(db_game["id"]) - self.on_game_launch(game) + game.launch() else: Application.show_update_runtime_dialog() self.window.present() @@ -606,16 +612,40 @@ self.quit_on_game_exit = False return 0 + @watch_errors(error_result=True) def on_game_launch(self, game): game.launch() return True # Return True to continue handling the emission hook + @watch_errors(error_result=True) def on_game_start(self, game): self.running_games.append(game) if settings.read_setting("hide_client_on_game_start") == "True": self.window.hide() # Hide launcher window return True + @watch_errors() + def on_game_stop(self, game): + """Callback to remove the game from the running games""" + ids = self.get_running_game_ids() + if str(game.id) in ids: + try: + self.running_games.remove(ids.index(str(game.id))) + except ValueError: + pass + else: + logger.warning("%s not in %s", game.id, ids) + + game.emit("game-stopped") + if settings.read_setting("hide_client_on_game_start") == "True" and not self.quit_on_game_exit: + self.window.show() # Show launcher window + elif not self.window.is_visible(): + if self.running_games.get_n_items() == 0: + if self.quit_on_game_exit or not self.has_tray_icon(): + self.do_shutdown() + return True + + @watch_errors(error_result=True) def on_game_install(self, game): """Request installation of a game""" if game.service and game.service != "lutris": @@ -644,24 +674,26 @@ ErrorDialog(_("There is no installer available for %s.") % game.name, parent=self.window) return True + @watch_errors(error_result=True) def on_game_install_update(self, game): service = get_enabled_services()[game.service]() db_game = games_db.get_game_by_field(game.id, "id") installers = service.get_update_installers(db_game) if installers: - self.show_installer_window(installers, service, game.appid, is_update=True) + self.show_installer_window(installers, service, game.appid, installation_kind=InstallationKind.UPDATE) else: - ErrorDialog(_("No updates found")) + ErrorDialog(_("No updates found"), parent=self.window) return True + @watch_errors(error_result=True) def on_game_install_dlc(self, game): service = get_enabled_services()[game.service]() db_game = games_db.get_game_by_field(game.id, "id") installers = service.get_dlc_installers_runner(db_game, db_game["runner"]) if installers: - self.show_installer_window(installers, service, game.appid) + self.show_installer_window(installers, service, game.appid, installation_kind=InstallationKind.DLC) else: - ErrorDialog(_("No DLC found")) + ErrorDialog(_("No DLC found"), parent=self.window) return True def get_running_game_ids(self): @@ -678,25 +710,9 @@ return game return None - def on_game_stop(self, game): - """Callback to remove the game from the running games""" - ids = self.get_running_game_ids() - if str(game.id) in ids: - try: - self.running_games.remove(ids.index(str(game.id))) - except ValueError: - pass - else: - logger.warning("%s not in %s", game.id, ids) - - game.emit("game-stopped") - if settings.read_setting("hide_client_on_game_start") == "True" and not self.quit_on_game_exit: - self.window.show() # Show launcher window - elif not self.window.is_visible(): - if self.running_games.get_n_items() == 0: - if self.quit_on_game_exit or not self.has_tray_icon(): - self.do_shutdown() - return True + def on_watched_error(self, error): + if self.window: + ErrorDialog(str(error), parent=self.window) @staticmethod def get_lutris_action(url): @@ -822,7 +838,6 @@ if os.path.isdir(runner_path): print(f"Wine version '{version}' is already installed.") else: - try: runner = import_runner("wine") runner().install(downloader=simple_downloader, version=version) @@ -849,16 +864,15 @@ install the runner provided in prepare_runner_cli() """ - runner_path = os.path.join(settings.RUNNER_DIR, runner_name) - if os.path.isdir(runner_path): - print(f"'{runner_name}' is already installed.") - else: - try: - runner = import_runner(runner_name) - runner().install(version=None, downloader=simple_downloader, callback=None) + try: + runner = import_runner(runner_name)() + if runner.is_installed(): + print(f"'{runner_name}' is already installed.") + else: + runner.install(version=None, downloader=simple_downloader, callback=None) print(f"'{runner_name}' has been installed") - except (InvalidRunner, RunnerInstallationError) as ex: - print(ex.message) + except (InvalidRunner, RunnerInstallationError) as ex: + print(ex.message) def uninstall_runner_cli(self, runner_name): """ diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/add_game.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/add_game.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/add_game.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/add_game.py 2022-12-03 12:25:25.000000000 +0000 @@ -26,5 +26,4 @@ self.build_tabs("game") self.build_action_area(self.on_save) self.name_entry.grab_focus() - self.connect("delete-event", self.on_cancel_clicked) self.show_all() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/boxes.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/boxes.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/boxes.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/boxes.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,6 +2,7 @@ # Standard Library # pylint: disable=no-member,too-many-public-methods import os +import urllib from gettext import gettext as _ # Third Party Libraries @@ -9,6 +10,7 @@ # Lutris Modules from lutris import settings, sysoptions +from lutris.gui.dialogs import ErrorDialog from lutris.gui.widgets.common import EditableGrid, FileChooserEntry, Label, VBox from lutris.gui.widgets.searchable_combobox import SearchableCombobox from lutris.runners import InvalidRunner, import_runner @@ -101,6 +103,8 @@ # Reset button reset_btn = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) + reset_btn.set_valign(Gtk.Align.CENTER) + reset_btn.set_margin_bottom(6) reset_btn.set_relief(Gtk.ReliefStyle.NONE) reset_btn.set_tooltip_text(_("Reset option to global or default config")) reset_btn.connect( @@ -263,7 +267,11 @@ else: self.option_changed(widget, option_name, widget.get_active()) - def _on_callback_finished(self, result, _error): + def _on_callback_finished(self, result, error): + if error: + ErrorDialog(str(error), parent=self.get_toplevel()) + return + widget, option, response = result if response: self.option_changed(widget, option["option"], widget.get_active()) @@ -416,10 +424,15 @@ file_chooser.entry.connect("changed", self._on_chooser_file_set, option_name) def _on_chooser_file_set(self, entry, option): - """Action triggered on file select dialog 'file-set' signal.""" - if not os.path.isabs(entry.get_text()): - entry.set_text(os.path.expanduser(entry.get_text())) - self.option_changed(entry.get_parent(), option, entry.get_text()) + """Action triggered when the field's content changes.""" + text = entry.get_text() + if text.startswith('file:///'): + text = urllib.parse.unquote(text[len('file://'):]) + if not os.path.isabs(text): + text = os.path.expanduser(text) + if text != entry.get_text(): + entry.set_text(text) + self.option_changed(entry.get_parent(), option, text) # Directory chooser def generate_directory_chooser(self, option, path=None): @@ -439,8 +452,13 @@ self.option_widget = directory_chooser def _on_chooser_dir_set(self, entry, option): - """Action triggered on file select dialog 'file-set' signal.""" - self.option_changed(entry.get_parent(), option, entry.get_text()) + """Action triggered when the field's content changes.""" + text = entry.get_text() + if text.startswith('file:///'): + text = urllib.parse.unquote(text[len('file://'):]) + if text != entry.get_text(): + entry.set_text(text) + self.option_changed(entry.get_parent(), option, text) # Editable grid def generate_editable_grid(self, option_name, label, value=None): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/common.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/common.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/common.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/common.py 2022-12-03 12:25:25.000000000 +0000 @@ -3,7 +3,7 @@ import os from gettext import gettext as _ -from gi.repository import Gdk, GLib, Gtk, Pango +from gi.repository import Gtk, Pango from lutris import runners, settings from lutris.config import LutrisConfig, make_game_config_id @@ -11,26 +11,26 @@ from lutris.gui import dialogs from lutris.gui.config import DIALOG_HEIGHT, DIALOG_WIDTH from lutris.gui.config.boxes import GameBox, RunnerBox, SystemBox -from lutris.gui.dialogs import Dialog, DirectoryDialog, ErrorDialog, QuestionDialog +from lutris.gui.dialogs import DirectoryDialog, ErrorDialog, ModelessDialog, QuestionDialog from lutris.gui.widgets.common import Label, NumberEntry, SlugEntry, VBox from lutris.gui.widgets.notifications import send_notification -from lutris.gui.widgets.utils import BANNER_SIZE, ICON_SIZE, get_pixbuf +from lutris.gui.widgets.utils import get_pixbuf from lutris.runners import import_runner -from lutris.services.lutris import LutrisBanner, LutrisIcon -from lutris.util import resources, system +from lutris.services.lutris import LutrisBanner, LutrisCoverart, LutrisIcon, download_lutris_media from lutris.util.log import logger from lutris.util.strings import slugify # pylint: disable=too-many-instance-attributes, no-member -class GameDialogCommon(Dialog): +class GameDialogCommon(ModelessDialog): """Base class for config dialogs""" no_runner_label = _("Select a runner in the Game Info tab") - def __init__(self, title, parent=None): - super().__init__(title, parent=parent) - self.set_type_hint(Gdk.WindowTypeHint.NORMAL) + def __init__(self, title, parent=None, use_header_bar=True): + super().__init__(title, parent=parent, border_width=0, use_header_bar=use_header_bar) self.set_default_size(DIALOG_WIDTH, DIALOG_HEIGHT) + self.vbox.set_border_width(0) + self.notebook = None self.name_entry = None self.runner_box = None @@ -44,27 +44,20 @@ self.year_entry = None self.slug_change_button = None self.runner_dropdown = None - self.banner_button = None - self.icon_button = None + self.image_buttons = {} + self.option_page_indices = set() + self.advanced_switch = None self.game_box = None self.system_box = None self.runner_name = None self.runner_index = None self.lutris_config = None + self.service_medias = {"icon": LutrisIcon(), "banner": LutrisBanner(), "coverart_big": LutrisCoverart()} + + self.accelerators = Gtk.AccelGroup() + self.add_accel_group(self.accelerators) - # These are independent windows, but start centered over - # a parent like a dialog. Not modal, not really transient, - # and does not share modality with other windows - so it - # needs its own window group. - Gtk.WindowGroup().add_window(self) - GLib.idle_add(self.clear_transient_for) - - def clear_transient_for(self): - # we need the parent set to be centered over the parent, but - # we don't want to be transient really- we want other windows - # able to come to the front. - self.set_transient_for(None) - return False + self.connect("response", self.on_response) @staticmethod def build_scrolled_window(widget): @@ -77,7 +70,9 @@ def build_notebook(self): self.notebook = Gtk.Notebook(visible=True) self.notebook.set_show_border(False) - self.vbox.pack_start(self.notebook, True, True, 10) + self.notebook.connect("switch-page", lambda _n, _p, index: + self.update_advanced_switch_visibilty(index)) + self.vbox.pack_start(self.notebook, True, True, 0) def build_tabs(self, config_level): """Build tabs (for game and runner levels)""" @@ -87,6 +82,12 @@ self._build_game_tab() self._build_runner_tab(config_level) self._build_system_tab(config_level) + self.update_advanced_switch_visibilty(self.notebook.get_current_page()) + + def update_advanced_switch_visibilty(self, current_page_index): + if self.advanced_switch and self.notebook: + show_switch = current_page_index in self.option_page_indices + self.advanced_switch.set_visible(show_switch) def _build_info_tab(self): info_box = VBox() @@ -168,30 +169,30 @@ label = Label("") banner_box.pack_start(label, False, False, 0) - self.banner_button = Gtk.Button() - self._set_image("banner") - self.banner_button.connect("clicked", self.on_custom_image_select, "banner") - banner_box.pack_start(self.banner_button, False, False, 0) - - reset_banner_button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) - reset_banner_button.set_relief(Gtk.ReliefStyle.NONE) - reset_banner_button.set_tooltip_text(_("Remove custom banner")) - reset_banner_button.connect("clicked", self.on_custom_image_reset_clicked, "banner") - banner_box.pack_start(reset_banner_button, False, False, 0) - - self.icon_button = Gtk.Button() - self._set_image("icon") - self.icon_button.connect("clicked", self.on_custom_image_select, "icon") - banner_box.pack_start(self.icon_button, False, False, 0) - - reset_icon_button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) - reset_icon_button.set_relief(Gtk.ReliefStyle.NONE) - reset_icon_button.set_tooltip_text(_("Remove custom icon")) - reset_icon_button.connect("clicked", self.on_custom_image_reset_clicked, "icon") - banner_box.pack_start(reset_icon_button, False, False, 0) + self._create_image_button(banner_box, "coverart_big", _("Set custom cover art"), _("Remove custom cover art")) + self._create_image_button(banner_box, "banner", _("Set custom banner"), _("Remove custom banner")) + self._create_image_button(banner_box, "icon", _("Set custom icon"), _("Remove custom icon")) return banner_box + def _create_image_button(self, banner_box, image_type, image_tooltip, reset_tooltip): + """This adds an image button and its reset button to the box given, + and adds the image button to self.image_buttons for future reference.""" + image_button = Gtk.Button() + self._set_image(image_type, image_button) + image_button.set_tooltip_text(image_tooltip) + image_button.connect("clicked", self.on_custom_image_select, image_type) + image_button.set_valign(Gtk.Align.CENTER) + banner_box.pack_start(image_button, False, False, 0) + + reset_button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) + reset_button.set_relief(Gtk.ReliefStyle.NONE) + reset_button.set_tooltip_text(reset_tooltip) + reset_button.connect("clicked", self.on_custom_image_reset_clicked, image_type) + reset_button.set_valign(Gtk.Align.CENTER) + banner_box.pack_start(reset_button, False, False, 0) + self.image_buttons[image_type] = image_button + def _get_year_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) @@ -205,15 +206,14 @@ return box - def _set_image(self, image_format): + def _set_image(self, image_format, image_button): + service_media = self.service_medias[image_format] image = Gtk.Image() - service_media = LutrisBanner() if image_format == "banner" else LutrisIcon() - game_slug = self.game.slug if self.game else "" - image.set_from_pixbuf(service_media.get_pixbuf_for_game(game_slug)) - if image_format == "banner": - self.banner_button.set_image(image) - else: - self.icon_button.set_image(image) + game_slug = self.slug or (self.game.slug if self.game else "") + + pixbuf = service_media.get_pixbuf_for_game(game_slug, service_media.config_ui_size) + image.set_from_pixbuf(pixbuf) + image_button.set_image(image) def _get_runner_dropdown(self): runner_liststore = self._get_runner_liststore() @@ -255,7 +255,12 @@ self.change_game_slug() def change_game_slug(self): - self.slug = self.slug_entry.get_text() + slug = self.slug_entry.get_text() + download_lutris_media(slug) + + self.slug = slug + for image_type, image_button in self.image_buttons.items(): + self._set_image(image_type, image_button) self.slug_entry.set_sensitive(False) self.slug_change_button.set_label(_("Change")) @@ -264,7 +269,7 @@ default_path=self.game.directory, parent=self) if not new_location.folder or new_location.folder == self.game.directory: return - move_dialog = dialogs.MoveDialog(self.game, new_location.folder) + move_dialog = dialogs.MoveDialog(self.game, new_location.folder, parent=self) move_dialog.connect("game-moved", self.on_game_moved) move_dialog.move() @@ -302,46 +307,72 @@ runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) - self._add_notebook_tab(runner_sw, _("Runner options")) + page_index = self._add_notebook_tab(runner_sw, _("Runner options")) + self.option_page_indices.add(page_index) def _build_system_tab(self, _config_level): if not self.lutris_config: raise RuntimeError("Lutris config not loaded yet") self.system_box = SystemBox(self.lutris_config) - self._add_notebook_tab( + page_index = self._add_notebook_tab( self.build_scrolled_window(self.system_box), _("System options") ) + self.option_page_indices.add(page_index) def _add_notebook_tab(self, widget, label): - self.notebook.append_page(widget, Gtk.Label(label=label)) + return self.notebook.append_page(widget, Gtk.Label(label=label)) def build_action_area(self, button_callback): - self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) - - # Advanced settings checkbox - checkbox = Gtk.CheckButton(label=_("Show advanced options")) - if settings.read_setting("show_advanced_options") == "True": - checkbox.set_active(True) - checkbox.connect("toggled", self.on_show_advanced_options_toggled) - self.action_area.pack_start(checkbox, False, False, 5) + self.action_area.set_layout(Gtk.ButtonBoxStyle.END) + self.action_area.set_border_width(10) # Buttons - hbox = Gtk.Box() - cancel_button = Gtk.Button(label=_("Cancel")) - cancel_button.connect("clicked", self.on_cancel_clicked) - hbox.pack_start(cancel_button, True, True, 10) + cancel_button = self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL) + cancel_button.set_valign(Gtk.Align.CENTER) - save_button = Gtk.Button(label=_("Save")) + save_button = self.add_styled_button(_("Save"), Gtk.ResponseType.NONE, css_class="suggested-action") + save_button.set_valign(Gtk.Align.CENTER) save_button.connect("clicked", button_callback) - hbox.pack_start(save_button, True, True, 0) - self.action_area.pack_start(hbox, True, True, 0) - def on_show_advanced_options_toggled(self, checkbox): - value = bool(checkbox.get_active()) - settings.write_setting("show_advanced_options", value) + key, mod = Gtk.accelerator_parse("s") + save_button.add_accelerator("clicked", self.accelerators, key, mod, Gtk.AccelFlags.VISIBLE) + + # Advanced settings toggle + + if self.props.use_header_bar: + switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, + spacing=5, + no_show_all=True) + switch_box.set_tooltip_text(_("Show advanced options")) + + switch_label = Gtk.Label(_("Advanced"), visible=True) + switch = Gtk.Switch(visible=True) + switch.set_state(settings.read_setting("show_advanced_options") == "True") + switch.connect("state_set", lambda _w, s: + self.on_show_advanced_options_toggled(bool(s))) + + switch_box.pack_start(switch_label, False, False, 0) + switch_box.pack_end(switch, False, False, 0) - self._set_advanced_options_visible(value) + header_bar = self.get_header_bar() + header_bar.pack_end(switch_box) + + self.advanced_switch = switch_box + self.update_advanced_switch_visibilty(self.notebook.get_current_page()) + else: + checkbox = Gtk.CheckButton(label=_("Show advanced options")) + checkbox.set_active(settings.read_setting("show_advanced_options") == "True") + checkbox.connect("toggled", lambda *x: + self.on_show_advanced_options_toggled(bool(checkbox.get_active()))) + checkbox.set_halign(Gtk.Align.START) + self.action_area.pack_start(checkbox, True, True, 0) + self.action_area.set_child_secondary(checkbox, True) + + def on_show_advanced_options_toggled(self, is_active): + settings.write_setting("show_advanced_options", is_active) + + self._set_advanced_options_visible(is_active) def _set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" @@ -405,16 +436,19 @@ def _rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) + self.option_page_indices.clear() self._build_game_tab() self._build_runner_tab("game") self._build_system_tab("game") self.show_all() - def on_cancel_clicked(self, _widget=None, _event=None): - """Dialog destroy callback.""" - if self.game: - self.game.load_config() - self.destroy() + def on_response(self, _widget, response): + if response in (Gtk.ResponseType.CANCEL, response == Gtk.ResponseType.DELETE_EVENT): + # Reload the config to clean out any changes we may have made + if self.game: + self.game.load_config() + if response != Gtk.ResponseType.NONE: + self.destroy() def is_valid(self): if not self.runner_name: @@ -469,6 +503,10 @@ runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) + # extract icon for wine games + if self.runner_name == "wine" and "icon" not in self.game.custom_images: + runner.extract_icon_exe(self.slug) + self.game.name = name self.game.slug = self.slug self.game.year = year @@ -498,35 +536,28 @@ response = dialog.run() if response == Gtk.ResponseType.ACCEPT: + slug = self.slug or self.game.slug image_path = dialog.get_filename() - if image_type == "banner": - self.game.has_custom_banner = True - dest_path = os.path.join(settings.BANNER_PATH, "%s.jpg" % self.game.slug) - size = BANNER_SIZE - file_format = "jpeg" - else: - self.game.has_custom_icon = True - dest_path = resources.get_icon_path(self.game.slug) - size = ICON_SIZE - file_format = "png" + service_media = self.service_medias[image_type] + self.game.custom_images.add(image_type) + dest_path = service_media.get_absolute_path(slug) + file_format = service_media.file_format + size = service_media.custom_media_storage_size pixbuf = get_pixbuf(image_path, size) - pixbuf.savev(dest_path, file_format, [], []) - self._set_image(image_type) - - if image_type == "icon": - system.update_desktop_icons() + # JPEG encoding looks rather better at high quality; + # PNG encoding just ignores this option. + pixbuf.savev(dest_path, file_format, ["quality"], ["100"]) + self._set_image(image_type, self.image_buttons[image_type]) + service_media.update_desktop() dialog.destroy() def on_custom_image_reset_clicked(self, _widget, image_type): - if image_type == "banner": - self.game.has_custom_banner = False - dest_path = os.path.join(settings.BANNER_PATH, "%s.jpg" % self.game.slug) - elif image_type == "icon": - self.game.has_custom_icon = False - dest_path = resources.get_icon_path(self.game.slug) - else: - raise ValueError("Unsupported image type %s" % image_type) + slug = self.slug or self.game.slug + service_media = self.service_medias[image_type] + dest_path = service_media.get_absolute_path(slug) + self.game.custom_images.discard(image_type) if os.path.isfile(dest_path): os.remove(dest_path) - self._set_image(image_type) + download_lutris_media(self.game.slug) + self._set_image(image_type, self.image_buttons[image_type]) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/edit_game.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/edit_game.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/edit_game.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/edit_game.py 2022-12-03 12:25:25.000000000 +0000 @@ -15,5 +15,4 @@ self.build_notebook() self.build_tabs("game") self.build_action_area(self.on_save) - self.connect("delete-event", self.on_cancel_clicked) self.show_all() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/preferences_box.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/preferences_box.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/preferences_box.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/preferences_box.py 2022-12-03 12:25:25.000000000 +0000 @@ -11,7 +11,8 @@ "hide_client_on_game_start": _("Minimize client when a game is launched"), "hide_text_under_icons": _("Hide text under icons (requires restart)"), "show_tray_icon": _("Show Tray Icon"), - "dark_theme": _("Use dark theme (requires dark theme variant for Gtk)") + "dark_theme": _("Use dark theme (requires dark theme variant for Gtk)"), + "discord_rpc": _("Enable Discord Rich Presence for Available Games"), } def _get_section_label(self, text): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/preferences_dialog.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/preferences_dialog.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/preferences_dialog.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/preferences_dialog.py 2022-12-03 12:25:25.000000000 +0000 @@ -15,7 +15,7 @@ # pylint: disable=no-member class PreferencesDialog(GameDialogCommon): def __init__(self, parent=None): - super().__init__(_("Lutris settings"), parent=parent) + super().__init__(_("Lutris settings"), parent=parent, use_header_bar=False) self.set_border_width(0) self.set_default_size(1010, 600) self.lutris_config = LutrisConfig() @@ -34,6 +34,7 @@ self.stack.set_interpolate_size(True) hbox.add(self.stack) self.vbox.pack_start(hbox, True, True, 0) + self.vbox.set_border_width(0) # keep everything flush with the window edge self.stack.add_named( self.build_scrolled_window(PreferencesBox()), "prefs-stack" @@ -57,10 +58,6 @@ "system-stack" ) self.build_action_area(self.on_save) - self.action_area.set_margin_bottom(12) - self.action_area.set_margin_right(12) - self.action_area.set_margin_left(12) - self.action_area.set_margin_top(12) def on_sidebar_activated(self, _listbox, row): if row.get_children()[0].stack_id == "system-stack": diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/runner_box.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/runner_box.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/runner_box.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/runner_box.py 2022-12-03 12:25:25.000000000 +0000 @@ -7,7 +7,7 @@ from lutris.gui.dialogs import ErrorDialog, QuestionDialog from lutris.gui.dialogs.download import simple_downloader from lutris.gui.dialogs.runner_install import RunnerInstallDialog -from lutris.gui.widgets.utils import ICON_SIZE, get_icon +from lutris.gui.widgets.utils import ICON_SIZE, get_runtime_icon from lutris.util.log import logger @@ -28,7 +28,7 @@ self.set_margin_left(12) self.set_margin_right(12) self.runner = runners.import_runner(runner_name)() - icon = get_icon(self.runner.name, icon_format='pixbuf', size=ICON_SIZE) + icon = get_runtime_icon(self.runner.name, icon_format='pixbuf', size=ICON_SIZE) if icon: runner_icon = Gtk.Image(visible=True) runner_icon.set_from_pixbuf(icon) @@ -74,10 +74,11 @@ _button.get_style_context().add_class("circular") _button.connect("clicked", self.on_versions_clicked) else: - if self.runner.is_installed(): + if self.runner.can_uninstall(): _button = Gtk.Button.new_from_icon_name("edit-delete-symbolic", Gtk.IconSize.BUTTON) _button.get_style_context().add_class("circular") _button.connect("clicked", self.on_remove_clicked) + _button.set_sensitive(self.runner.can_uninstall()) else: _button = Gtk.Button.new_from_icon_name("system-software-install-symbolic", Gtk.IconSize.BUTTON) _button.get_style_context().add_class("circular") @@ -86,12 +87,10 @@ return _button def on_versions_clicked(self, widget): - RunnerInstallDialog( - _("Manage %s versions") % self.runner.name, - None, - self.runner.name - ) - # connect a runner-installed signal from the above dialog? + window = self.get_toplevel() + application = window.get_application() + title = _("Manage %s versions") % self.runner.name + application.show_window(RunnerInstallDialog, title=title, runner=self.runner, parent=window) def on_install_clicked(self, widget): """Install a runner.""" @@ -103,7 +102,7 @@ runners.NonInstallableRunnerError, ) as ex: logger.error(ex) - ErrorDialog(ex.message) + ErrorDialog(ex.message, parent=self.get_toplevel()) return if self.runner.is_installed(): self.emit("runner-installed") @@ -118,6 +117,7 @@ def on_remove_clicked(self, widget): dialog = QuestionDialog( { + "parent": self.get_toplevel(), "title": _("Do you want to uninstall %s?") % self.runner.human_name, "question": _("This will remove %s and all associated data." % self.runner.human_name) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/services_box.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/services_box.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/config/services_box.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/config/services_box.py 2022-12-03 12:25:25.000000000 +0000 @@ -4,7 +4,7 @@ from lutris import settings from lutris.gui.config.base_config_box import BaseConfigBox -from lutris.gui.widgets.utils import ICON_SIZE, get_icon +from lutris.gui.widgets.utils import ICON_SIZE, get_runtime_icon, has_stock_icon from lutris.services import SERVICES @@ -42,12 +42,13 @@ visible=True, ) service = SERVICES[service_key] - pixbuf = get_icon(service.icon, icon_format="pixbuf", size=ICON_SIZE) + pixbuf = get_runtime_icon(service.icon, icon_format="pixbuf", size=ICON_SIZE) if pixbuf: icon = Gtk.Image(visible=True) icon.set_from_pixbuf(pixbuf) else: - icon = Gtk.Image.new_from_icon_name(service.id, Gtk.IconSize.DND) + icon_name = service.id if has_stock_icon(service.id) else "package-x-generic-symbolic" + icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DND) icon.show() box.pack_start(icon, False, False, 0) label = Gtk.Label(service.name, visible=True) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/cache.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/cache.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/cache.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/cache.py 2022-12-03 12:25:25.000000000 +0000 @@ -1,20 +1,36 @@ from gettext import gettext as _ -from gi.repository import GLib, Gtk +from gi.repository import Gtk from lutris.cache import get_cache_path, save_cache_path +from lutris.gui.dialogs import ModalDialog from lutris.gui.widgets.common import FileChooserEntry -class CacheConfigurationDialog(Gtk.Dialog): - def __init__(self): - Gtk.Dialog.__init__(self, _("Cache configuration")) +class CacheConfigurationDialog(ModalDialog): + def __init__(self, parent=None): + super().__init__( + _("Cache configuration"), + parent=parent, + flags=Gtk.DialogFlags.MODAL, + border_width=10 + ) self.timer_id = None self.set_size_request(480, 150) - self.set_border_width(12) + self.cache_path = get_cache_path() or "" self.get_content_area().add(self.get_cache_config()) + + self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) + self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) + self.show_all() + result = self.run() + + if result == Gtk.ResponseType.OK: + save_cache_path(self.cache_path) + + self.destroy() def get_cache_config(self): """Return the widgets for the cache configuration""" @@ -23,9 +39,11 @@ box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Gtk.Label(_("Cache path")) box.pack_start(label, False, False, 0) - cache_path = get_cache_path() path_chooser = FileChooserEntry( - title=_("Set the folder for the cache path"), action=Gtk.FileChooserAction.SELECT_FOLDER, path=cache_path + title=_("Set the folder for the cache path"), + action=Gtk.FileChooserAction.SELECT_FOLDER, + path=self.cache_path, + activates_default=True ) path_chooser.entry.connect("changed", self._on_cache_path_set) box.pack_start(path_chooser, True, True, 0) @@ -42,12 +60,4 @@ return prefs_box def _on_cache_path_set(self, entry): - if self.timer_id: - GLib.source_remove(self.timer_id) - self.timer_id = GLib.timeout_add(1000, self.save_cache_setting, entry.get_text()) - - def save_cache_setting(self, value): - save_cache_path(value) - GLib.source_remove(self.timer_id) - self.timer_id = None - return False + self.cache_path = entry.get_text() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/download.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/download.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/download.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/download.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,16 +2,16 @@ from gi.repository import Gtk +from lutris.gui.dialogs import ModalDialog from lutris.gui.widgets.download_progress_box import DownloadProgressBox -class DownloadDialog(Gtk.Dialog): +class DownloadDialog(ModalDialog): """Dialog showing a download in progress.""" - def __init__(self, url=None, dest=None, title=None, label=None, downloader=None): - Gtk.Dialog.__init__(self, title or _("Downloading file")) + def __init__(self, url=None, dest=None, title=None, label=None, downloader=None, parent=None): + super().__init__(title=title or _("Downloading file"), parent=parent, border_width=10) self.set_size_request(485, 104) - self.set_border_width(12) params = {"url": url, "dest": dest, "title": label or _("Downloading %s") % url} self.dialog_progress_box = DownloadProgressBox(params, downloader=downloader) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/__init__.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/__init__.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/__init__.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/__init__.py 2022-12-03 12:25:25.000000000 +0000 @@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0') -from gi.repository import GLib, GObject, Gtk +from gi.repository import Gdk, GLib, GObject, Gtk from lutris import api, settings from lutris.gui.widgets.log_text_view import LogTextView @@ -17,15 +17,62 @@ class Dialog(Gtk.Dialog): - def __init__(self, title=None, parent=None, flags=0, buttons=None): - super().__init__(title, parent, flags, buttons) - self.set_border_width(10) + def __init__(self, title=None, parent=None, flags=0, buttons=None, **kwargs): + super().__init__(title, parent, flags, buttons, **kwargs) self.connect("delete-event", self.on_destroy) self.set_destroy_with_parent(True) def on_destroy(self, _widget, _data=None): self.destroy() + def add_styled_button(self, button_text, response_id, css_class): + button = self.add_button(button_text, response_id) + if css_class: + style_context = button.get_style_context() + style_context.add_class(css_class) + return button + + def add_default_button(self, button_text, response_id, css_class="suggested-action"): + """Adds a button to the dialog with a particular response id, but + also makes it the default and styles it as the suggested action.""" + button = self.add_styled_button(button_text, response_id, css_class) + self.set_default_response(response_id) + return button + + +class ModalDialog(Dialog): + """A base class of moodal dialogs, which sets the flag for you.""" + + def __init__(self, title=None, parent=None, flags=0, buttons=None, **kwargs): + super().__init__(title, parent, flags | Gtk.DialogFlags.MODAL, buttons, **kwargs) + + +class ModelessDialog(Dialog): + """A base class for modeless dialogs. They have a parent only temporarily, so + they can be centered over it during creation. But each modeless dialog gets + its own window group, so it treats its own modal dialogs separately, and it resets + its transient-for after being created.""" + + def __init__(self, title=None, parent=None, flags=0, buttons=None, **kwargs): + super().__init__(title, parent, flags, buttons, **kwargs) + # These are not stuck above the 'main' window, but can be + # re-ordered freely. + self.set_type_hint(Gdk.WindowTypeHint.NORMAL) + + # These are independent windows, but start centered over + # a parent like a dialog. Not modal, not really transient, + # and does not share modality with other windows - so it + # needs its own window group. + Gtk.WindowGroup().add_window(self) + GLib.idle_add(self._clear_transient_for) + + def _clear_transient_for(self): + # we need the parent set to be centered over the parent, but + # we don't want to be transient really- we want other windows + # able to come to the front. + self.set_transient_for(None) + return False + class GtkBuilderDialog(GObject.Object): dialog_object = NotImplemented @@ -83,9 +130,11 @@ """Display a message to the user.""" - def __init__(self, message, parent=None): + def __init__(self, message, secondary=None, parent=None): super().__init__(buttons=Gtk.ButtonsType.OK, parent=parent) self.set_markup(message) + if secondary: + self.format_secondary_text(secondary[:256]) self.run() self.destroy() @@ -176,8 +225,10 @@ class LutrisInitDialog(Gtk.Dialog): - def __init__(self, init_lutris): + def __init__(self, runtime_updater): super().__init__() + self.runtime_updater = runtime_updater + self.set_size_request(320, 60) self.set_border_width(24) self.set_decorated(False) @@ -190,19 +241,22 @@ self.get_content_area().add(vbox) self.progress_timeout = GLib.timeout_add(125, self.show_progress) self.show_all() + + self.connect("response", self.on_response) self.connect("destroy", self.on_destroy) - AsyncCall(self.initialize, self.init_cb, init_lutris) + AsyncCall(runtime_updater.update_runtimes, self.init_cb) def show_progress(self): self.progress.pulse() return True - def initialize(self, init_lutris, *args): - init_lutris() - def init_cb(self, _result, error): if error: - ErrorDialog(str(error)) + ErrorDialog(str(error), parent=self) + self.destroy() + + def on_response(self, _widget, response): + self.runtime_updater.cancel() self.destroy() def on_destroy(self, window): @@ -210,16 +264,18 @@ return True -class InstallOrPlayDialog(Gtk.Dialog): +class InstallOrPlayDialog(ModalDialog): - def __init__(self, game_name): - Gtk.Dialog.__init__(self, _("%s is already installed") % game_name) - self.connect("delete-event", lambda *x: self.destroy()) + def __init__(self, game_name, parent=None): + super().__init__(title=_("%s is already installed") % game_name, parent=parent, border_width=10) self.action = "play" self.action_confirmed = False + self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) + self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) + self.connect("response", self.on_response) + self.set_size_request(320, 120) - self.set_border_width(12) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 6) self.get_content_area().add(vbox) play_button = Gtk.RadioButton.new_with_label_from_widget(None, _("Launch game")) @@ -230,31 +286,31 @@ install_button.connect("toggled", self.on_button_toggled, "install") vbox.pack_start(install_button, False, False, 0) - confirm_button = Gtk.Button(_("OK")) - confirm_button.connect("clicked", self.on_confirm) - vbox.pack_start(confirm_button, False, False, 0) - self.show_all() self.run() - def on_button_toggled(self, button, action): # pylint: disable=unused-argument + def on_button_toggled(self, _button, action): logger.debug("Action set to %s", action) self.action = action - def on_confirm(self, button): # pylint: disable=unused-argument - logger.debug("Action %s confirmed", self.action) - self.action_confirmed = True + def on_response(self, _widget, response): + logger.debug("Dialog response %s", response) + if response == Gtk.ResponseType.CANCEL: + self.action = None self.destroy() -class LaunchConfigSelectDialog(Gtk.Dialog): - def __init__(self, game, configs): - Gtk.Dialog.__init__(self, _("Select game to launch")) - self.connect("delete-event", lambda *x: self.destroy()) +class LaunchConfigSelectDialog(ModalDialog): + def __init__(self, game, configs, parent=None): + super().__init__(title=_("Select game to launch"), parent=parent, border_width=10) self.config_index = 0 self.confirmed = False + + self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) + self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) + self.connect("response", self.on_response) + self.set_size_request(320, 120) - self.set_border_width(12) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 6) self.get_content_area().add(vbox) @@ -267,29 +323,14 @@ _button.connect("toggled", self.on_button_toggled, i + 1) vbox.pack_start(_button, False, False, 0) - button_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6) - button_box.set_halign(Gtk.Align.END) - cancel_button = Gtk.Button(_("Cancel")) - cancel_button.connect("clicked", self.on_cancel) - button_box.pack_start(cancel_button, False, False, 0) - - confirm_button = Gtk.Button(_("OK")) - confirm_button.connect("clicked", self.on_confirm) - button_box.pack_start(confirm_button, False, False, 0) - vbox.pack_start(button_box, False, False, 0) - self.show_all() self.run() def on_button_toggled(self, _button, index): self.config_index = index - def on_cancel(self, _button): - self.confirmed = False - self.destroy() - - def on_confirm(self, _button): - self.confirmed = True + def on_response(self, _widget, response): + self.confirmed = response == Gtk.ResponseType.OK self.destroy() @@ -340,14 +381,17 @@ self.dialog.destroy() -class InstallerSourceDialog(Gtk.Dialog): +class InstallerSourceDialog(ModelessDialog): """Show install script source""" def __init__(self, code, name, parent): - Gtk.Dialog.__init__(self, _("Install script for {}").format(name), parent=parent) + super().__init__(title=_("Install script for {}").format(name), parent=parent, border_width=0) self.set_size_request(500, 350) - self.set_border_width(0) + + ok_button = self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) + ok_button.set_border_width(10) + self.connect("response", self.on_response) self.scrolled_window = Gtk.ScrolledWindow() self.scrolled_window.set_hexpand(True) @@ -358,16 +402,13 @@ source_box = LogTextView(source_buffer, autoscroll=False) + self.get_content_area().set_border_width(0) self.get_content_area().add(self.scrolled_window) self.scrolled_window.add(source_box) - close_button = Gtk.Button(_("OK")) - close_button.connect("clicked", self.on_close) - self.get_content_area().add(close_button) - self.show_all() - def on_close(self, *args): # pylint: disable=unused-argument + def on_response(self, *args): self.destroy() @@ -390,7 +431,7 @@ super().__init__(type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK, parent=parent) - self.set_border_width(12) + self.set_default_response(Gtk.ResponseType.OK) self.set_markup("%s" % message) if secondary_message: self.props.secondary_use_markup = True @@ -423,27 +464,26 @@ "Having Wine installed on your system guarantees that " "Wine builds from Lutris will have all required dependencies.\n\nPlease " "follow the instructions given in the Lutris Wiki to " + "href='https://github.com/lutris/docs/blob/master/WineDependencies.md'>Lutris Wiki to " "install Wine." ), parent=parent, ) -class MoveDialog(Gtk.Dialog): +class MoveDialog(ModelessDialog): __gsignals__ = { "game-moved": (GObject.SIGNAL_RUN_FIRST, None, ()), } - def __init__(self, game, destination): - super().__init__() + def __init__(self, game, destination, parent=None): + super().__init__(parent=parent, border_width=24) self.game = game self.destination = destination self.new_directory = None self.set_size_request(320, 60) - self.set_border_width(24) self.set_decorated(False) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 12) label = Gtk.Label(_("Moving %s to %s..." % (game, destination))) @@ -467,6 +507,6 @@ def on_game_moved(self, _result, error): if error: - ErrorDialog(str(error)) + ErrorDialog(str(error), parent=self) self.emit("game-moved") self.destroy() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/runner_install.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/runner_install.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/runner_install.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/runner_install.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,6 +2,7 @@ # pylint: disable=no-member import gettext import os +import re from collections import defaultdict from gettext import gettext as _ @@ -10,21 +11,19 @@ from lutris import api, settings from lutris.database.games import get_games_by_runner from lutris.game import Game -from lutris.gui.dialogs import Dialog, ErrorDialog, QuestionDialog +from lutris.gui.dialogs import ErrorDialog, ModalDialog, ModelessDialog, QuestionDialog +from lutris.gui.widgets.utils import has_stock_icon from lutris.util import jobs, system from lutris.util.downloader import Downloader from lutris.util.extract import extract_archive from lutris.util.log import logger -class ShowAppsDialog(Dialog): +class ShowAppsDialog(ModalDialog): def __init__(self, title, parent, runner_version, apps): - super().__init__(title, parent, Gtk.DialogFlags.MODAL) - self.add_buttons( - Gtk.STOCK_OK, Gtk.ResponseType.OK - ) - - self.set_default_size(400, 500) + super().__init__(title, parent, border_width=10) + self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) + self.set_default_size(600, 400) label = Gtk.Label.new(_("Showing games using %s") % runner_version) self.vbox.add(label) @@ -49,7 +48,7 @@ self.show_all() -class RunnerInstallDialog(Dialog): +class RunnerInstallDialog(ModelessDialog): """Dialog displaying available runner version and downloads them""" COL_VER = 0 COL_ARCH = 1 @@ -58,10 +57,14 @@ COL_PROGRESS = 4 COL_USAGE = 5 + INSTALLED_ICON_NAME = "software-installed-symbolic" \ + if has_stock_icon("software-installed-symbolic") else "wine-symbolic" + def __init__(self, title, parent, runner): - super().__init__(title, parent, 0) - self.add_buttons(_("_OK"), Gtk.ButtonsType.OK) - self.runner = runner + super().__init__(title, parent, 0, border_width=10) + self.add_default_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) + self.runner_name = runner.name + self.runner_directory = runner.directory self.runner_info = {} self.installing = {} self.set_default_size(640, 480) @@ -78,7 +81,7 @@ self.show_all() self.runner_store = Gtk.ListStore(str, str, str, bool, int, int) - jobs.AsyncCall(api.get_runners, self.runner_fetch_cb, self.runner) + jobs.AsyncCall(api.get_runners, self.runner_fetch_cb, self.runner_name) def runner_fetch_cb(self, runner_info, error): """Clear the box and display versions from runner_info""" @@ -128,11 +131,15 @@ row.runner = runner hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) row.hbox = hbox - chk_installed = Gtk.CheckButton() - chk_installed.set_sensitive(False) - chk_installed.set_active(runner[self.COL_INSTALLED]) - hbox.pack_start(chk_installed, False, True, 0) - row.chk_installed = chk_installed + + icon = Gtk.Image.new_from_icon_name(self.INSTALLED_ICON_NAME, Gtk.IconSize.MENU) + icon.set_visible(runner[self.COL_INSTALLED]) + icon_container = Gtk.Box() + icon_container.set_size_request(16, 16) + icon_container.pack_start(icon, False, False, 0) + + hbox.pack_start(icon_container, False, True, 0) + row.icon = icon lbl_version = Gtk.Label(runner[self.COL_VER]) lbl_version.set_max_width_chars(20) @@ -155,16 +162,18 @@ app_count = runner[self.COL_USAGE] or 0 if app_count > 0: usage_button_text = gettext.ngettext( - "_View %d game", - "_View %d games", + "View %d game", + "View %d games", app_count ) % app_count - usage_button = Gtk.Button.new_with_mnemonic(usage_button_text) - usage_button.connect("button_press_event", self.on_show_apps_usage, row) + usage_button = Gtk.LinkButton.new_with_label(usage_button_text) + usage_button.set_valign(Gtk.Align.CENTER) + usage_button.connect("clicked", self.on_show_apps_usage, row) hbox.pack_end(usage_button, False, True, 2) button = Gtk.Button() + button.set_size_request(100, -1) hbox.pack_end(button, False, True, 0) hbox.reorder_child(button, 0) row.install_uninstall_cancel_button = button @@ -177,30 +186,38 @@ def update_listboxrow(self, row): row.install_progress.set_visible(False) - row.chk_installed.set_active(row.runner[self.COL_INSTALLED]) + + runner = row.runner + icon = row.icon + icon.set_visible(runner[self.COL_INSTALLED]) + button = row.install_uninstall_cancel_button + style_context = button.get_style_context() if row.handler_id is not None: button.disconnect(row.handler_id) row.handler_id = None - if row.runner[self.COL_VER] in self.installing: + if runner[self.COL_VER] in self.installing: + style_context.remove_class("destructive-action") button.set_label(_("Cancel")) - handler_id = button.connect("button_press_event", self.on_cancel_install, row) + handler_id = button.connect("clicked", self.on_cancel_install, row) else: - if row.runner[self.COL_INSTALLED]: + if runner[self.COL_INSTALLED]: + style_context.add_class("destructive-action") button.set_label(_("Uninstall")) - handler_id = button.connect("button_press_event", self.on_uninstall_runner, row) + handler_id = button.connect("clicked", self.on_uninstall_runner, row) else: + style_context.remove_class("destructive-action") button.set_label(_("Install")) - handler_id = button.connect("button_press_event", self.on_install_runner, row) + handler_id = button.connect("clicked", self.on_install_runner, row) row.install_uninstall_cancel_button = button row.handler_id = handler_id - def on_show_apps_usage(self, _widget, _button, row): + def on_show_apps_usage(self, _button, row): """Return grid with games that uses this wine version""" runner = row.runner runner_version = "%s-%s" % (runner[self.COL_VER], runner[self.COL_ARCH]) - runner_games = get_games_by_runner(self.runner) + runner_games = get_games_by_runner(self.runner_name) apps = [] for db_game in runner_games: if not db_game["installed"]: @@ -219,7 +236,8 @@ def populate_store(self): """Return a ListStore populated with the runner versions""" version_usage = self.get_usage_stats() - for version_info in reversed(self.runner_info["versions"]): + ordered = sorted(self.runner_info["versions"], key=RunnerInstallDialog.get_version_sort_key) + for version_info in reversed(ordered): is_installed = os.path.exists(self.get_runner_path(version_info["version"], version_info["architecture"])) games_using = version_usage.get("%(version)s-%(architecture)s" % version_info) self.runner_store.append( @@ -229,20 +247,35 @@ ] ) + @staticmethod + def get_version_sort_key(version): + """Generate a sorting key that sorts first on the version number part of the version, + and which breaks the version number into its components, which are parsed as integers""" + raw_version = version["version"] + # Extract version numbers from the end of the version string. + # We look for things like xx-7.2 or xxx-4.3-2. A leading period + # will be part of the version, but a leading hyphen will not. + match = re.search(r"^(.*?)\-?(\d[.\-\d]*)$", raw_version) + if match: + version_parts = [int(p) for p in match.group(2).replace("-", ".").split(".") if p] + return version_parts, raw_version, version["architecture"] + + # If we fail to extract the version, we'll wind up sorting this one to the end. + return [], raw_version, version["architecture"] + def get_installed_versions(self): """List versions available locally""" - runner_path = os.path.join(settings.RUNNER_DIR, self.runner) - if not os.path.exists(runner_path): + if not os.path.exists(self.runner_directory): return set() return { tuple(p.rsplit("-", 1)) - for p in os.listdir(runner_path) + for p in os.listdir(self.runner_directory) if "-" in p } def get_runner_path(self, version, arch): """Return the local path where the runner is/will be installed""" - return os.path.join(settings.RUNNER_DIR, self.runner, "{}-{}".format(version, arch)) + return os.path.join(self.runner_directory, "{}-{}".format(version, arch)) def get_dest_path(self, row): """Return temporary path where the runners should be downloaded to""" @@ -264,7 +297,7 @@ else: self.install_runner(row) - def on_cancel_install(self, widget, button, row): + def on_cancel_install(self, widget, row): self.cancel_install(row) def cancel_install(self, row): @@ -277,7 +310,7 @@ self.update_listboxrow(row) row.install_progress.set_visible(False) - def on_uninstall_runner(self, widget, button, row): + def on_uninstall_runner(self, widget, row): self.uninstall_runner(row) def uninstall_runner(self, row): @@ -287,14 +320,14 @@ arch = runner[self.COL_ARCH] system.remove_folder(self.get_runner_path(version, arch)) runner[self.COL_INSTALLED] = False - if self.runner == "wine": + if self.runner_name == "wine": logger.debug("Clearing wine version cache") from lutris.util.wine.wine import get_wine_versions get_wine_versions.cache_clear() self.update_listboxrow(row) - def on_install_runner(self, _widget, _button, row): + def on_install_runner(self, _widget, row): self.install_runner(row) def install_runner(self, row): @@ -344,7 +377,7 @@ def get_usage_stats(self): """Return the usage for each version""" - runner_games = get_games_by_runner(self.runner) + runner_games = get_games_by_runner(self.runner_name) version_usage = defaultdict(list) for db_game in runner_games: if not db_game["installed"]: @@ -386,7 +419,7 @@ row.install_progress.set_fraction(0.0) row.install_progress.hide() self.update_listboxrow(row) - if self.runner == "wine": + if self.runner_name == "wine": logger.debug("Clearing wine version cache") from lutris.util.wine.wine import get_wine_versions get_wine_versions.cache_clear() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/uninstall_game.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/uninstall_game.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/uninstall_game.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/uninstall_game.py 2022-12-03 12:25:25.000000000 +0000 @@ -4,19 +4,25 @@ from lutris.database.games import get_games from lutris.game import Game -from lutris.gui.dialogs import Dialog, QuestionDialog +from lutris.gui.dialogs import ModalDialog, QuestionDialog from lutris.util.jobs import AsyncCall from lutris.util.log import logger from lutris.util.strings import gtk_safe, human_size from lutris.util.system import get_disk_size, is_removeable, reverse_expanduser -class UninstallGameDialog(Dialog): +class UninstallGameDialog(ModalDialog): def __init__(self, game_id, parent=None): - super().__init__(parent=parent) + super().__init__(parent=parent, border_width=10) self.set_size_request(640, 128) self.game = Game(game_id) self.delete_files = False + + self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) + self.delete_button = self.add_default_button(_("Uninstall"), Gtk.ResponseType.OK, + css_class="destructive-action") + self.connect("response", self.on_response) + container = Gtk.VBox(visible=True) self.get_content_area().add(container) @@ -31,9 +37,6 @@ self.folder_label = Gtk.Label(visible=True) self.folder_label.set_alignment(0, 0.5) - 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(filters={"directory": self.game.directory})) > 1: @@ -53,16 +56,6 @@ self.confirm_delete_button.set_active(True) container.pack_start(self.confirm_delete_button, False, False, 4) - button_box = Gtk.HBox(visible=True) - button_box.set_margin_top(30) - 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) - def folder_size_cb(self, folder_size, error): if error: logger.error(error) @@ -78,40 +71,45 @@ ) ) - def on_close(self, _button): - self.destroy() - - def on_delete_clicked(self, button): - button.set_sensitive(False) - if not self.confirm_delete_button.get_active(): - self.delete_files = False - if self.delete_files and not hasattr(self.game.runner, "no_game_remove_warning"): - dlg = QuestionDialog( - { - "parent": self, - "question": _( - "Please confirm.\nEverything under %s\n" - "will be deleted." - ) % gtk_safe(self.game.directory), - "title": _("Permanently delete files?"), - } - ) - if dlg.result != Gtk.ResponseType.YES: - button.set_sensitive(True) - return - if self.delete_files: - self.folder_label.set_markup(_("Uninstalling game and deleting files...")) - else: - self.folder_label.set_markup(_("Uninstalling game...")) - self.game.remove(self.delete_files) + def on_response(self, _widget, response): + if response == Gtk.ResponseType.OK: + self.delete_button.set_sensitive(False) + if not self.confirm_delete_button.get_active(): + self.delete_files = False + if self.delete_files and not hasattr(self.game.runner, "no_game_remove_warning"): + dlg = QuestionDialog( + { + "parent": self, + "question": _( + "Please confirm.\nEverything under %s\n" + "will be deleted." + ) % gtk_safe(self.game.directory), + "title": _("Permanently delete files?"), + } + ) + if dlg.result != Gtk.ResponseType.YES: + self.delete_button.set_sensitive(True) + self.stop_emission_by_name("response") + return + if self.delete_files: + self.folder_label.set_markup(_("Uninstalling game and deleting files...")) + else: + self.folder_label.set_markup(_("Uninstalling game...")) + self.game.remove(self.delete_files) self.destroy() -class RemoveGameDialog(Dialog): +class RemoveGameDialog(ModalDialog): def __init__(self, game_id, parent=None): - super().__init__(parent=parent) + super().__init__(parent=parent, border_width=10) self.set_size_request(640, 128) self.game = Game(game_id) + + self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) + self.remove_button = self.add_default_button(_("Remove"), Gtk.ResponseType.OK, + css_class="destructive-action") + self.connect("response", self.on_response) + container = Gtk.VBox(visible=True) self.get_content_area().add(container) @@ -128,24 +126,8 @@ _("Completely remove %s from the library?\nAll play time will be lost.") % self.game) container.pack_start(self.delete_label, False, False, 4) - button_box = Gtk.HBox(visible=True) - button_box.set_margin_top(30) - 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) - - self.remove_button = Gtk.Button(_("Remove"), visible=True) - self.remove_button.connect("clicked", self.on_remove_clicked) - - button_box.add(self.remove_button) - container.pack_end(button_box, False, False, 0) - - def on_close(self, _button): - self.destroy() - - def on_remove_clicked(self, button): - button.set_sensitive(False) - self.game.delete() + def on_response(self, _widget, response): + if response == Gtk.ResponseType.OK: + self.remove_button.set_sensitive(False) + self.game.delete() self.destroy() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/webconnect_dialog.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/webconnect_dialog.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/dialogs/webconnect_dialog.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/dialogs/webconnect_dialog.py 2022-12-03 12:25:25.000000000 +0000 @@ -3,15 +3,20 @@ from gettext import gettext as _ import gi -gi.require_version("WebKit2", "4.0") +try: + gi.require_version("WebKit2", "4.1") +except (ImportError, ValueError): + gi.require_version("WebKit2", "4.0") + from gi.repository import WebKit2 -from lutris.gui.dialogs import Dialog +from lutris.gui.dialogs import ModalDialog + DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0" -class WebConnectDialog(Dialog): +class WebConnectDialog(ModalDialog): """Login form for external services""" def __init__(self, service, parent=None): @@ -29,19 +34,19 @@ super().__init__(title=service.name, parent=parent) - self.set_border_width(0) self.set_default_size(self.service.login_window_width, self.service.login_window_height) self.webview = WebKit2.WebView.new_with_context(self.context) self.webview.load_uri(service.login_url) self.webview.connect("load-changed", self.on_navigation) self.webview.connect("create", self.on_webview_popup) + self.vbox.set_border_width(0) # pylint: disable=no-member self.vbox.pack_start(self.webview, True, True, 0) # pylint: disable=no-member webkit_settings = self.webview.get_settings() - # Set a default User Agent - webkit_settings.set_user_agent(DEFAULT_USER_AGENT) + # Set a User Agent + webkit_settings.set_user_agent(service.login_user_agent) # Allow popups (Doesn't work...) webkit_settings.set_allow_modal_dialogs(True) @@ -89,12 +94,11 @@ view = WebKit2.WebView.new_with_related_view(widget) view.load_uri(uri) popup_dialog = WebPopupDialog(view, parent=self) - popup_dialog.set_modal(True) - popup_dialog.show() + popup_dialog.run() return view -class WebPopupDialog(Dialog): +class WebPopupDialog(ModalDialog): """Dialog for handling web popups""" def __init__(self, webview, parent=None): @@ -107,7 +111,7 @@ self.webview.connect("create", self.on_new_webview_popup) self.webview.connect("close", self.on_webview_close) self.vbox.pack_start(self.webview, True, True, 0) - self.set_border_width(0) + self.vbox.set_border_width(0) self.set_default_size(390, 500) def on_ready_webview(self, webview): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/installerwindow.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/installerwindow.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/installerwindow.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/installerwindow.py 2022-12-03 12:25:25.000000000 +0000 @@ -5,16 +5,16 @@ from gi.repository import GLib, Gtk from lutris.config import LutrisConfig -from lutris.exceptions import UnavailableGame +from lutris.exceptions import UnavailableGameError, watch_errors from lutris.game import Game -from lutris.gui.dialogs import DirectoryDialog, InstallerSourceDialog, QuestionDialog +from lutris.gui.dialogs import DirectoryDialog, ErrorDialog, InstallerSourceDialog, QuestionDialog from lutris.gui.dialogs.cache import CacheConfigurationDialog from lutris.gui.installer.files_box import InstallerFilesBox from lutris.gui.installer.script_picker import InstallerPicker from lutris.gui.widgets.common import FileChooserEntry, InstallerLabel from lutris.gui.widgets.log_text_view import LogTextView from lutris.gui.widgets.window import BaseApplicationWindow -from lutris.installer import interpreter +from lutris.installer import InstallationKind, get_installers, interpreter from lutris.installer.errors import MissingGameDependency, ScriptingError from lutris.util import xdgshortcuts from lutris.util.log import logger @@ -32,7 +32,7 @@ service=None, appid=None, application=None, - is_update=False + installation_kind=InstallationKind.INSTALL ): super().__init__(application=application) self.set_default_size(540, 320) @@ -42,7 +42,7 @@ self.appid = appid self.install_in_progress = False self.interpreter = None - self.is_update = is_update + self.installation_kind = installation_kind self.log_buffer = None self.log_textview = None @@ -114,7 +114,7 @@ def validate_scripts(self): """Auto-fixes some script aspects and checks for mandatory fields""" if not self.installers: - raise ScriptingError("No installer available") + raise ScriptingError(_("No installer available")) for script in self.installers: for item in ["description", "notes"]: script[item] = script.get(item) or "" @@ -139,10 +139,12 @@ scrolledwindow.set_shadow_type(Gtk.ShadowType.ETCHED_IN) self.widget_box.pack_end(scrolledwindow, True, True, 10) + @watch_errors() def on_cache_clicked(self, _button): """Open the cache configuration dialog""" - CacheConfigurationDialog() + CacheConfigurationDialog(parent=self) + @watch_errors() def on_installer_selected(self, _widget, installer_version): """Sets the script interpreter to the correct script then proceed to install folder selection. @@ -150,30 +152,26 @@ If the installed game depends on another one and it's not installed, prompt the user to install it and quit this installer. """ - self.clean_widgets() try: script = None for _script in self.installers: if _script["version"] == installer_version: script = _script self.interpreter = interpreter.ScriptInterpreter(script, self) - except MissingGameDependency as ex: dlg = QuestionDialog( { + "parent": self, "question": _("This game requires %s. Do you want to install it?") % ex.slug, "title": _("Missing dependency"), } ) if dlg.result == Gtk.ResponseType.YES: - InstallerWindow( - installers=self.installers, - service=self.service, - appid=self.appid, - application=self.application, - ) - self.destroy() + installers = get_installers(game_slug=ex.slug) + self.application.show_installer_window(installers) return + + self.clean_widgets() self.title_label.set_markup(_("Installing {}").format(gtk_safe(self.interpreter.installer.game_name))) self.select_install_folder() @@ -193,7 +191,7 @@ def select_install_folder(self): """Stage where we select the install directory.""" if not self.interpreter.installer.creates_game_folder: - self.on_install_clicked(self.install_button) + self.start_install() return self.set_message(_("Select installation directory")) default_path = self.interpreter.get_default_target() @@ -204,18 +202,16 @@ self.source_button.show() self.install_button.grab_focus() self.install_button.show() - # self.manual_button.hide() + @watch_errors() def on_target_changed(self, text_entry, _data=None): """Set the installation target for the game.""" self.interpreter.target_path = os.path.expanduser(text_entry.get_text()) + @watch_errors() def on_install_clicked(self, button): """Let the interpreter take charge of the next stages.""" - button.hide() - self.source_button.hide() - self.interpreter.connect("runners-installed", self.on_runners_ready) - GLib.idle_add(self.interpreter.launch_install) + self.start_install() def set_install_destination(self, default_path=None): """Display the destination chooser.""" @@ -232,8 +228,27 @@ location_entry.entry.connect("changed", self.on_target_changed) self.widget_box.pack_start(location_entry, False, False, 0) + def start_install(self): + self.install_button.hide() + self.source_button.hide() + self.interpreter.connect("runners-installed", self.on_runners_ready) + GLib.idle_add(self.launch_install) + + @watch_errors() + def launch_install(self): + # This is a shim method to allow exceptions from + # the interpret to be reported via watch_errors(). + self.interpreter.launch_install() + def ask_for_disc(self, message, callback, requires): """Ask the user to do insert a CD-ROM.""" + + def wrapped_callback(*args, **kwargs): + try: + callback(*args, **kwargs) + except Exception as err: + ErrorDialog(str(err), parent=self) + self.clean_widgets() label = InstallerLabel(message) label.show() @@ -246,17 +261,18 @@ self.widget_box.add(buttons_box) autodetect_button = Gtk.Button(label=_("Autodetect")) - autodetect_button.connect("clicked", callback, requires) + autodetect_button.connect("clicked", wrapped_callback, requires) autodetect_button.grab_focus() autodetect_button.show() buttons_box.pack_start(autodetect_button, True, True, 40) browse_button = Gtk.Button(label=_("Browse…")) - callback_data = {"callback": callback, "requires": requires} + callback_data = {"callback": wrapped_callback, "requires": requires} browse_button.connect("clicked", self.on_browse_clicked, callback_data) browse_button.show() buttons_box.pack_start(browse_button, True, True, 40) + @watch_errors() def on_browse_clicked(self, widget, callback_data): dialog = DirectoryDialog(_("Select the folder where the disc is mounted"), parent=self) folder = dialog.folder @@ -264,6 +280,7 @@ requires = callback_data["requires"] callback(widget, requires, folder) + @watch_errors() def on_eject_clicked(self, _widget, data=None): self.interpreter.eject_wine_disc() @@ -297,27 +314,32 @@ """Enable continue button if a non-empty choice is selected""" self.continue_button.set_sensitive(bool(widget.get_active_id())) + @watch_errors() def on_runners_ready(self, _widget=None): """The runners are ready, proceed with file selection""" + self.show_installer_files_screen() + + def show_installer_files_screen(self): + """Show installer screen with the file picker / downloader""" if self.interpreter.extras is None: extras = self.interpreter.get_extras() if extras: self.show_extras(extras) return try: - patch_version = self.interpreter.installer.version if self.is_update else None + if self.installation_kind == InstallationKind.UPDATE: + patch_version = self.interpreter.installer.version + else: + patch_version = None self.interpreter.installer.prepare_game_files(patch_version) - except UnavailableGame as ex: + except UnavailableGameError as ex: raise ScriptingError(str(ex)) from ex if not self.interpreter.installer.files: logger.debug("Installer doesn't require files") self.interpreter.launch_installer_commands() return - self.show_installer_files_screen() - def show_installer_files_screen(self): - """Show installer screen with the file picker / downloader""" self.clean_widgets() self.set_status(_("Please review the files needed for the installation then click 'Continue'")) installer_files_box = InstallerFilesBox(self.interpreter.installer, self) @@ -401,6 +423,7 @@ self.continue_button.disconnect(self.continue_handler) self.continue_handler = self.continue_button.connect("clicked", self.on_extras_confirmed, extra_treestore) + @watch_errors() def on_extra_toggled(self, _widget, path, model): toggled_row = model[path] toggled_row_iter = model.get_iter(path) @@ -430,6 +453,7 @@ heading_row[0] = all_extras_active heading_row[1] = any_extras_active + @watch_errors() def on_extras_confirmed(self, _button, extra_store): """Resume install when user has selected extras to download""" selected_extras = [] @@ -441,17 +465,19 @@ extra_store.foreach(save_extra) self.interpreter.extras = selected_extras - GLib.idle_add(self.on_runners_ready) + GLib.idle_add(self.show_installer_files_screen) def on_files_ready(self, _widget, files_ready): """Toggle state of continue button based on ready state""" self.continue_button.set_sensitive(files_ready) + @watch_errors() def on_files_confirmed(self, _button, file_box): """Call this when the user confirms the install files This will start the downloads. """ self.set_status("") + self.cache_button.set_sensitive(False) self.continue_button.set_sensitive(False) try: file_box.start_all() @@ -460,6 +486,7 @@ self.continue_button.set_sensitive(True) raise ScriptingError(_("Unable to get files: %s") % ex) from ex + @watch_errors() def on_files_available(self, widget): """All files are available, continue the install""" logger.info("All files are available, continuing install") @@ -469,7 +496,7 @@ self.clean_widgets() self.interpreter.launch_installer_commands() - def on_install_finished(self, game_id): + def finish_install(self, game_id): self.clean_widgets() if self.config.get("create_desktop_shortcut"): @@ -506,7 +533,7 @@ """Remove urgency hint (flashing indicator) when window receives focus""" self.set_urgency_hint(False) - def on_install_error(self, message): + def show_install_error_message(self, message): self.clean_widgets() self.set_status(message) self.cancel_button.grab_focus() @@ -531,14 +558,14 @@ self.interpreter.cleanup() self.destroy() - def on_create_desktop_shortcut_clicked(self, _widget): - self.config["create_desktop_shortcut"] = True + def on_create_desktop_shortcut_clicked(self, checkbutton): + self.config["create_desktop_shortcut"] = checkbutton.get_active() - def on_create_menu_shortcut_clicked(self, _widget): - self.config["create_menu_shortcut"] = True + def on_create_menu_shortcut_clicked(self, checkbutton): + self.config["create_menu_shortcut"] = checkbutton.get_active() - def on_create_steam_shortcut_clicked(self, _widget): - self.config["create_steam_shortcut"] = True + def on_create_steam_shortcut_clicked(self, checkbutton): + self.config["create_steam_shortcut"] = checkbutton.get_active() def create_shortcut(self, desktop=False): """Create desktop or global menu shortcuts.""" @@ -557,6 +584,7 @@ remove_checkbox = Gtk.CheckButton.new_with_label(_("Remove game files")) if self.interpreter and self.interpreter.target_path and \ + self.installation_kind == InstallationKind.INSTALL and \ is_removeable(self.interpreter.target_path, LutrisConfig().system_config): remove_checkbox.set_active(self.interpreter.game_dir_created) remove_checkbox.show() @@ -580,6 +608,7 @@ self.interpreter.cleanup() # still remove temporary downloads in any case self.destroy() + @watch_errors() def on_source_clicked(self, _button): InstallerSourceDialog( self.interpreter.installer.script_pretty, @@ -587,6 +616,9 @@ self ) + def on_watched_error(self, error): + ErrorDialog(str(error), parent=self) + def clean_widgets(self): """Cleanup before displaying the next stage.""" for child_widget in self.widget_box.get_children(): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/lutriswindow.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/lutriswindow.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/lutriswindow.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/lutriswindow.py 2022-12-03 12:25:25.000000000 +0000 @@ -9,6 +9,7 @@ from lutris.database import categories as categories_db from lutris.database import games as games_db from lutris.database.services import ServiceGameCollection +from lutris.exceptions import watch_errors from lutris.game import Game from lutris.game_actions import GameActions from lutris.gui import dialogs @@ -122,6 +123,7 @@ GObject.add_emission_hook(Game, "game-stopped", self.on_game_stopped) GObject.add_emission_hook(Game, "game-removed", self.on_game_collection_changed) GObject.add_emission_hook(Game, "game-error", self.on_game_error) + GObject.add_emission_hook(Game, "game-notice", self.on_game_notice) def _init_actions(self): Action = namedtuple("Action", ("callback", "type", "enabled", "default", "accel")) @@ -625,9 +627,13 @@ self.move(int(self.window_x), int(self.window_y)) def on_service_login(self, service): - AsyncCall(service.reload, None) + AsyncCall(service.reload, self._service_login_cb) return True + def _service_login_cb(self, _result, error): + if error: + dialogs.ErrorDialog(str(error), parent=self) + def on_service_logout(self, service): if self.service and service.id == self.service.id: self.emit("view-updated") @@ -713,6 +719,10 @@ dialogs.ErrorDialog(str(error), parent=self) return True + def on_game_notice(self, game, message, secondary): + """Called when a game has sent the 'game-notice' signal""" + dialogs.NoticeDialog(message, secondary=secondary, parent=self) + @GtkTemplate.Callback def on_add_game_button_clicked(self, *_args): """Add a new game manually with the AddGameDialog.""" @@ -840,6 +850,7 @@ self.emit("view-updated") return True + @watch_errors() def on_game_activated(self, view, game_id): """Handles view activations (double click, enter press)""" if self.service: @@ -865,3 +876,6 @@ game.emit("game-launch") else: game.emit("game-install") + + def on_watched_error(self, error): + dialogs.ErrorDialog(str(error), parent=self) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/views/store_item.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/views/store_item.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/views/store_item.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/views/store_item.py 2022-12-03 12:25:25.000000000 +0000 @@ -107,7 +107,7 @@ return get_pixbuf(image_path, self.service_media.size, is_installed=self.installed) return self.service_media.get_pixbuf_for_game( self._game_data["slug"], - self.installed + is_installed=self.installed ) @property diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/views/store.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/views/store.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/views/store.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/views/store.py 2022-12-03 12:25:25.000000000 +0000 @@ -132,15 +132,15 @@ return False row[COL_ID] = str(store_item.id) row[COL_SLUG] = store_item.slug - row[COL_NAME] = gtk_safe(store_item.name) + row[COL_NAME] = store_item.name if settings.SHOW_MEDIA: row[COL_ICON] = store_item.get_pixbuf() else: row[COL_ICON] = None row[COL_YEAR] = store_item.year row[COL_RUNNER] = store_item.runner - row[COL_RUNNER_HUMAN_NAME] = gtk_safe(store_item.runner_text) - row[COL_PLATFORM] = gtk_safe(store_item.platform) + row[COL_RUNNER_HUMAN_NAME] = store_item.runner_text + row[COL_PLATFORM] = store_item.platform row[COL_LASTPLAYED] = store_item.lastplayed row[COL_LASTPLAYED_TEXT] = store_item.lastplayed_text row[COL_INSTALLED] = store_item.installed diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/common.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/common.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/common.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/common.py 2022-12-03 12:25:25.000000000 +0000 @@ -47,7 +47,8 @@ path=None, default_path=None, warn_if_non_empty=False, - warn_if_ntfs=False + warn_if_ntfs=False, + activates_default=False, ): super().__init__( orientation=Gtk.Orientation.VERTICAL, @@ -64,6 +65,7 @@ self.path_completion = Gtk.ListStore(str) self.entry = Gtk.Entry(visible=True) + self.entry.set_activates_default(activates_default) self.entry.set_completion(self.get_completion()) self.entry.connect("changed", self.on_entry_changed) if path: @@ -95,10 +97,9 @@ def get_filechooser_dialog(self): """Return an instance of a FileChooserNative configured for this widget""" - dialog = Gtk.FileChooserNative.new(self.title, None, self.action, _("_OK"), _("_Cancel")) + dialog = Gtk.FileChooserNative.new(self.title, self.get_toplevel(), self.action, _("_OK"), _("_Cancel")) dialog.set_create_folders(True) dialog.set_current_folder(self.get_default_folder()) - dialog.connect("response", self.on_select_file, dialog) return dialog def get_default_folder(self): @@ -115,7 +116,14 @@ def on_browse_clicked(self, _widget): """Browse button click callback""" file_chooser_dialog = self.get_filechooser_dialog() - file_chooser_dialog.show() + response = file_chooser_dialog.run() + + if response == Gtk.ResponseType.ACCEPT: + target_path = file_chooser_dialog.get_filename() + if target_path: + self.entry.set_text(system.reverse_expanduser(target_path)) + + file_chooser_dialog.destroy() def on_entry_changed(self, widget): """Entry changed callback""" @@ -154,15 +162,6 @@ )) self.pack_end(non_writable_destination_label, False, False, 10) - def on_select_file(self, dialog, response, _dialog): - """FileChooserDialog response callback""" - if response == Gtk.ResponseType.ACCEPT: - target_path = dialog.get_filename() - if target_path: - dialog.set_current_folder(target_path) - self.entry.set_text(system.reverse_expanduser(target_path)) - dialog.destroy() - def update_completion(self, current_path): """Update the auto-completion widget with the current path""" self.path_completion.clear() @@ -230,8 +229,8 @@ super().__init__() self.set_column_homogeneous(True) self.set_row_homogeneous(True) - self.set_row_spacing(10) - self.set_column_spacing(10) + self.set_row_spacing(6) + self.set_column_spacing(6) self.liststore = Gtk.ListStore(str, str) for item in data: @@ -262,6 +261,7 @@ self.scrollable_treelist = Gtk.ScrolledWindow() self.scrollable_treelist.set_vexpand(True) self.scrollable_treelist.add(self.treeview) + self.scrollable_treelist.set_shadow_type(Gtk.ShadowType.IN) self.attach(self.scrollable_treelist, 0, 0, 5, 5) self.attach(self.add_button, 5 - len(self.buttons), 6, 1, 1) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/gi_composites.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/gi_composites.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/gi_composites.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/gi_composites.py 2022-12-03 12:25:25.000000000 +0000 @@ -122,7 +122,7 @@ # TODO: could disallow using a metaclass.. but this is good enough # .. if you disagree, feel free to fix it and issue a PR :) if self.__class__ is not cls: - raise TypeError("Inheritance from classes with @GtkTemplate decorators " "is not allowed at this time") + raise TypeError("Inheritance from classes with @GtkTemplate decorators is not allowed at this time") connected_signals = set() self.__connected_template_signals__ = connected_signals diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/searchable_combobox.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/searchable_combobox.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/searchable_combobox.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/searchable_combobox.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,6 +2,7 @@ # pylint: disable=unsubscriptable-object from gi.repository import GLib, GObject, Gtk +from lutris.gui.dialogs import ErrorDialog from lutris.util.jobs import AsyncCall @@ -64,7 +65,7 @@ self.combobox.set_active_id(model[_iter][1]) def _populate_combobox_choices(self, choice_func): - AsyncCall(self._do_populate_combobox_choices, None, choice_func) + AsyncCall(self._do_populate_combobox_choices, self._populate_combobox_choices_cb, choice_func) def _do_populate_combobox_choices(self, choice_func): for choice in choice_func(): @@ -73,6 +74,10 @@ entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, None) self.combobox.set_active_id(self.initial) + def _populate_combobox_choices_cb(self, _result, error): + if error: + ErrorDialog(str(error), parent=self.get_toplevel()) + @staticmethod def _on_combobox_scroll(combobox, _event): """Prevents users from accidentally changing configuration values diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/sidebar.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/sidebar.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/sidebar.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/sidebar.py 2022-12-03 12:25:25.000000000 +0000 @@ -144,9 +144,9 @@ if error: if isinstance(error, AuthTokenExpired): self.service.logout() - self.service.login() + self.service.login(parent=self.get_toplevel()) else: - ErrorDialog(str(error)) + ErrorDialog(str(error), parent=self.get_toplevel()) GLib.timeout_add(2000, self.enable_refresh_button) def enable_refresh_button(self): @@ -179,7 +179,7 @@ if self.service.is_authenticated(): self.service.logout() else: - self.service.login() + self.service.login(parent=self.get_toplevel()) self.create_button_box() @@ -212,7 +212,8 @@ def on_manage_versions(self, *_args): """Manage runner versions""" dlg_title = _("Manage %s versions") % self.runner.name - RunnerInstallDialog(dlg_title, self.get_toplevel(), self.runner.name) + self.application.show_window(RunnerInstallDialog, title=dlg_title, + runner=self.runner, parent=self.get_toplevel()) class SidebarHeader(Gtk.Box): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/utils.py lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/utils.py --- lutris-0.5.11~ubuntu22.04.1/lutris/gui/widgets/utils.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/gui/widgets/utils.py 2022-12-03 12:25:25.000000000 +0000 @@ -31,7 +31,7 @@ def open_uri(uri): """Opens a local or remote URI with the default application""" - system.execute(["xdg-open", uri]) + system.spawn(["xdg-open", uri]) def get_pixbuf(image, size, fallback=None, is_installed=True): @@ -87,29 +87,40 @@ return None -def get_icon(icon_name, icon_format="image", size=None, icon_type="runner"): - """Return an icon based on the given name, format, size and type. +def get_runtime_icon(icon_name, icon_format="image", size=None): + """Return an icon based on the given name, format, size and type. Only + the icons installed in Lutris's runtime directory are searched. Keyword arguments: icon_name -- The name of the icon to retrieve format -- The format of the icon, which should be either 'image' or 'pixbuf' (default 'image') size -- The size for the desired image (default None) - icon_type -- Retrieve either a 'runner' or 'platform' icon (default 'runner') """ - filename = icon_name.lower().replace(" ", "") + ".png" - icon_path = os.path.join(settings.RUNTIME_DIR, "icons/hicolor/64x64/apps", filename) - if not os.path.exists(icon_path): - return None - if icon_format == "image": - icon = Gtk.Image() - if size: - icon.set_from_pixbuf(get_pixbuf(icon_path, size)) - else: - icon.set_from_file(icon_path) - return icon - if icon_format == "pixbuf" and size: - return get_pixbuf(icon_path, size) - raise ValueError("Invalid arguments") + filename = icon_name.lower().replace(" ", "") + # We prefer bitmaps over SVG, because we've got some SVG icons with the + # wrong size (oops) and this avoids them. + search_directories = [ + "icons/hicolor/64x64/apps", + "icons/hicolor/24x24/apps", + "icons", + "icons/hicolor/scalable/apps", + "icons/hicolor/symbolic/apps"] + extensions = [".png", ".svg"] + for search_dir in search_directories: + for ext in extensions: + icon_path = os.path.join(settings.RUNTIME_DIR, search_dir, filename + ext) + if os.path.exists(icon_path): + if icon_format == "image": + icon = Gtk.Image() + if size: + icon.set_from_pixbuf(get_pixbuf(icon_path, size)) + else: + icon.set_from_file(icon_path) + return icon + if icon_format == "pixbuf" and size: + return get_pixbuf(icon_path, size) + raise ValueError("Invalid arguments") + return None def get_overlay(overlay_path, size): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/__init__.py lutris-0.5.12~ubuntu22.04.1/lutris/__init__.py --- lutris-0.5.11~ubuntu22.04.1/lutris/__init__.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/__init__.py 2022-12-03 12:25:25.000000000 +0000 @@ -1,3 +1,3 @@ """Main Lutris package""" -__version__ = "0.5.11" +__version__ = "0.5.12" diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/installer/commands.py lutris-0.5.12~ubuntu22.04.1/lutris/installer/commands.py --- lutris-0.5.11~ubuntu22.04.1/lutris/installer/commands.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/installer/commands.py 2022-12-03 12:25:25.000000000 +0000 @@ -14,6 +14,7 @@ from lutris.cache import get_cache_path from lutris.command import MonitoredCommand from lutris.database.games import get_game_by_field +from lutris.exceptions import UnavailableRunnerError from lutris.game import Game from lutris.installer.errors import ScriptingError from lutris.runners import import_task @@ -387,8 +388,12 @@ return runner_name, task_name def get_wine_path(self): - """Return absolute path of wine version used during the install""" - return get_wine_version_exe(self._get_runner_version()) + """Return absolute path of wine version used during the install, but + None if the wine exe can't be located.""" + try: + return get_wine_version_exe(self._get_runner_version()) + except UnavailableRunnerError: + return None def task(self, data): """Directive triggering another function specific to a runner. diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/installer/__init__.py lutris-0.5.12~ubuntu22.04.1/lutris/installer/__init__.py --- lutris-0.5.11~ubuntu22.04.1/lutris/installer/__init__.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/installer/__init__.py 2022-12-03 12:25:25.000000000 +0000 @@ -1,4 +1,6 @@ """Install script interpreter package.""" +import enum + import yaml from lutris.api import get_game_installers, normalize_installer @@ -10,6 +12,12 @@ AUTO_WIN32_EXE = AUTO_EXE_PREFIX + "WIN32_xXx_" +class InstallationKind(enum.Enum): + INSTALL = 0 + UPDATE = 1 + DLC = 2 + + def read_script(filename): """Return scripts from a local file""" logger.debug("Loading script(s) from %s", filename) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/installer/installer.py lutris-0.5.12~ubuntu22.04.1/lutris/installer/installer.py --- lutris-0.5.11~ubuntu22.04.1/lutris/installer/installer.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/installer/installer.py 2022-12-03 12:25:25.000000000 +0000 @@ -42,6 +42,7 @@ self.extends = self.script.get("extends") self.game_id = self.get_game_id() self.is_gog = False + self.discord_id = installer.get('discord_id') def get_service(self, initial=None): if initial: @@ -281,6 +282,7 @@ service=service_id, service_id=self.service_appid, id=self.game_id, + discord_id=self.discord_id, ) return self.game_id diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/installer/interpreter.py lutris-0.5.12~ubuntu22.04.1/lutris/installer/interpreter.py --- lutris-0.5.11~ubuntu22.04.1/lutris/installer/interpreter.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/installer/interpreter.py 2022-12-03 12:25:25.000000000 +0000 @@ -305,7 +305,7 @@ commands = self.installer.script.get("installer", []) if exception: logger.error("Last install command failed, show error") - self.parent.on_install_error(repr(exception)) + self.parent.show_install_error_message(repr(exception)) elif self.current_command < len(commands): try: command = commands[self.current_command] @@ -373,7 +373,7 @@ install_complete_text = (self.installer.script.get("install_complete_text") or _("Installation completed!")) self.parent.set_status(install_complete_text) download_lutris_media(self.installer.game_slug) - self.parent.on_install_finished(game_id) + self.parent.finish_install(game_id) def cleanup(self): """Clean up install dir after a successful install""" diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/migrations/__init__.py lutris-0.5.12~ubuntu22.04.1/lutris/migrations/__init__.py --- lutris-0.5.11~ubuntu22.04.1/lutris/migrations/__init__.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/migrations/__init__.py 2022-12-03 12:25:25.000000000 +0000 @@ -3,7 +3,7 @@ from lutris import settings from lutris.util.log import logger -MIGRATION_VERSION = 11 # Never decrease this number +MIGRATION_VERSION = 12 # Never decrease this number # Replace deprecated migrations with empty lists MIGRATIONS = [ @@ -11,7 +11,8 @@ ["mess_to_mame"], ["migrate_hidden_ids"], ["migrate_steam_appids"], - ["migrate_banners"] + ["migrate_banners"], + ["retrieve_discord_appids"], ] diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/migrations/retrieve_discord_appids.py lutris-0.5.12~ubuntu22.04.1/lutris/migrations/retrieve_discord_appids.py --- lutris-0.5.11~ubuntu22.04.1/lutris/migrations/retrieve_discord_appids.py 1970-01-01 00:00:00.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/migrations/retrieve_discord_appids.py 2022-12-03 12:25:25.000000000 +0000 @@ -0,0 +1,26 @@ +from lutris import settings +from lutris.api import get_api_games +from lutris.database.games import get_games, sql +from lutris.util.log import logger + + +def migrate(): + """ + Update Games that does not have a Discord ID + """ + logger.info("Updating Games Discord APP ID's") + # Get Slugs from all games + slugs_to_update = [game['slug'] for game in get_games()] + # Retrieve game data + games = get_api_games(slugs_to_update) + for game in games: + if not game['discord_id']: + continue + + sql.db_update( + settings.PGA_DB, + "games", + {"discord_id": game['discord_id']}, + {"slug": game['slug']} + ) + logger.info("Updated %s", game['name']) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/commands/wine.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/commands/wine.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/commands/wine.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/commands/wine.py 2022-12-03 12:25:25.000000000 +0000 @@ -3,9 +3,11 @@ import os import shlex import time +from gettext import gettext as _ from lutris import runtime, settings from lutris.command import MonitoredCommand +from lutris.exceptions import UnavailableRunnerError from lutris.runners import import_runner from lutris.util import linux, system from lutris.util.log import logger @@ -146,7 +148,7 @@ if loop_index == 60: logger.warning("Wine prefix creation is taking longer than expected...") if not os.path.exists(os.path.join(prefix, "user.reg")): - logger.error("No user.reg found after prefix creation. " "Prefix might not be valid") + logger.error("No user.reg found after prefix creation. Prefix might not be valid") return logger.info("%s Prefix created in %s", arch, prefix) prefix_manager = WinePrefixManager(prefix) @@ -250,7 +252,7 @@ if not wine_path: wine_path = runner.get_executable() if not wine_path: - raise RuntimeError("Wine is not installed") + raise UnavailableRunnerError(_("Wine is not installed")) if not working_dir: if os.path.isfile(executable): @@ -340,8 +342,21 @@ winetricks_path = os.path.join(settings.RUNTIME_DIR, "winetricks/winetricks") if system_winetricks or not system.path_exists(winetricks_path): winetricks_path = system.find_executable("winetricks") + working_dir = None if not winetricks_path: raise RuntimeError("No installation of winetricks found") + else: + # We will use our own zentiy if available, which is here and it + # also needs a data file in this directory. We have to set the + # working_dir so it will find the data file. + working_dir = os.path.join(settings.RUNTIME_DIR, "winetricks") + + if not env: + env = {} + + path = env.get("PATH", os.environ["PATH"]) + env["PATH"] = "%s:%s" % (working_dir, path) + if wine_path: winetricks_wine = wine_path else: @@ -353,11 +368,13 @@ args = app if str(silent).lower() in ("yes", "on", "true"): args = "--unattended " + args + return wineexec( None, prefix=prefix, winetricks_wine=winetricks_wine, wine_path=winetricks_path, + working_dir=working_dir, arch=arch, args=args, config=config, diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/flatpak.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/flatpak.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/flatpak.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/flatpak.py 2022-12-03 12:25:25.000000000 +0000 @@ -4,6 +4,7 @@ from pathlib import Path from lutris.command import MonitoredCommand +from lutris.runners import NonInstallableRunnerError from lutris.runners.runner import Runner from lutris.util.strings import split_arguments @@ -19,7 +20,6 @@ human_name = _("Flatpak") runnable_alone = False system_options_override = [{"option": "disable_runtime", "default": True}] - # runner_executable = "flatpak" install_locations = { "system": "var/lib/flatpak/app/", "user": f"{Path.home()}/.local/share/flatpak/app/" @@ -86,10 +86,18 @@ ] def is_installed(self): - return shutil.which("flatpak") + if shutil.which("flatpak-spawn"): + return True + return bool(shutil.which("flatpak")) def get_executable(self): - return shutil.which("flatpak") + return shutil.which("flatpak-spawn") or shutil.which("flatpak") + + def install(self, version=None, downloader=None, callback=None): + raise NonInstallableRunnerError( + _("Flatpak installation is not handled by Lutris.\n" + "Install Flatpak with the package provided by your distribution.") + ) def can_uninstall(self): return False @@ -99,8 +107,11 @@ @property def game_path(self): - install_type, application, arch, branch = (self.game_data[key] for key in - ("install_type", "application", "arch", "branch")) + if shutil.which("flatpak-spawn"): + return "/" + install_type, application, arch, branch = ( + self.game_data[key] for key in ("install_type", "application", "arch", "branch") + ) return os.path.join(self.install_locations[install_type], application, arch, branch) def remove_game_data(self, app_id=None, game_path=None, **kwargs): @@ -118,7 +129,7 @@ arch = self.game_config.get("arch", "") branch = self.game_config.get("branch", "") args = self.game_config.get("args", "") - app_id = self.game_config.get("application", "") + app_id = self.game_config.get("appid", "") if not app_id: return {"error": "CUSTOM", "text": "No application specified."} @@ -130,7 +141,13 @@ if any(x in app_id for x in ("--", "/")): return {"error": "CUSTOM", "text": "Application ID field must not contain options or arguments."} - command = [self.get_executable(), "run"] + command = [self.get_executable()] + + if shutil.which("flatpak-spawn"): + command.extend(["--host", "flatpak", "run"]) + else: + command.append("run") + if arch: command.append(f"--arch={arch}") if branch: diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/fsuae.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/fsuae.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/fsuae.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/fsuae.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,6 +2,7 @@ from collections import defaultdict from gettext import gettext as _ +from lutris import settings from lutris.runners.runner import Runner from lutris.util import system from lutris.util.display import DISPLAY_MANAGER @@ -399,6 +400,10 @@ }, ] + @property + def directory(self): + return os.path.join(settings.RUNNER_DIR, "fs-uae") + def get_platform(self): model = self.runner_config.get("model") if model: diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/__init__.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/__init__.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/__init__.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/__init__.py 2022-12-03 12:25:25.000000000 +0000 @@ -10,6 +10,7 @@ # Microsoft based "wine", "dosbox", + "xemu", # Multi-system "easyrpg", "mame", diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/libretro.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/libretro.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/libretro.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/libretro.py 2022-12-03 12:25:25.000000000 +0000 @@ -26,7 +26,11 @@ # Get core identifiers from info dir info_path = get_default_config_path("info") if not os.path.exists(info_path): - req = requests.get("http://buildbot.libretro.com/assets/frontend/info.zip", allow_redirects=True) + req = requests.get( + "http://buildbot.libretro.com/assets/frontend/info.zip", + allow_redirects=True, + timeout=5 + ) if req.status_code == requests.codes.ok: # pylint: disable=no-member with open(get_default_config_path('info.zip'), 'wb') as info_zip: info_zip.write(req.content) @@ -133,7 +137,7 @@ return system.path_exists(self.get_executable()) def is_installed(self, core=None): - if not core and self.config and self.game_config.get("core"): + if not core and self.has_explicit_config and self.game_config.get("core"): core = self.game_config["core"] if not core or self.runner_config.get("runner_executable"): return self.is_retroarch_installed() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/redream.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/redream.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/redream.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/redream.py 2022-12-03 12:25:25.000000000 +0000 @@ -18,7 +18,7 @@ "option": "main_file", "type": "file", "label": _("Disc image file"), - "help": _("Game data file\n" "Supported formats: GDI, CDI, CHD"), + "help": _("Game data file\nSupported formats: GDI, CDI, CHD"), } ] runner_options = [ diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/runner.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/runner.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/runner.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/runner.py 2022-12-03 12:25:25.000000000 +0000 @@ -9,7 +9,7 @@ from lutris.command import MonitoredCommand from lutris.config import LutrisConfig from lutris.database.games import get_game_by_field -from lutris.exceptions import UnavailableLibraries +from lutris.exceptions import UnavailableLibrariesError from lutris.gui import dialogs from lutris.runners import RunnerInstallationError from lutris.util import system @@ -38,10 +38,13 @@ def __init__(self, config=None): """Initialize runner.""" - self.config = config if config: - self.game_data = get_game_by_field(self.config.game_config_id, "configpath") + self.has_explicit_config = True + self._config = config + self.game_data = get_game_by_field(config.game_config_id, "configpath") else: + self.has_explicit_config = False + self._config = None self.game_data = {} def __lt__(self, other): @@ -62,30 +65,37 @@ return self.__class__.__name__ @property - def default_config(self): - return LutrisConfig(runner_slug=self.name) + def directory(self): + return os.path.join(settings.RUNNER_DIR, self.name) + + @property + def config(self): + if not self._config: + self._config = LutrisConfig(runner_slug=self.name) + return self._config + + @config.setter + def config(self, new_config): + self._config = new_config + self.has_explicit_config = new_config is not None @property def game_config(self): """Return the cascaded game config as a dict.""" - if self.config: - return self.config.game_config - logger.warning("Accessing game config while runner wasn't given one.") - return {} + if not self.has_explicit_config: + logger.warning("Accessing game config while runner wasn't given one.") + + return self.config.game_config @property def runner_config(self): """Return the cascaded runner config as a dict.""" - if self.config: - return self.config.runner_config - return self.default_config.runner_config + return self.config.runner_config @property def system_config(self): """Return the cascaded system config as a dict.""" - if self.config: - return self.config.system_config - return self.default_config.system_config + return self.config.system_config @property def default_path(self): @@ -99,10 +109,11 @@ if game_path: return game_path - # Default to the directory where the entry point is located. - entry_point = self.game_config.get(self.entry_point_option) - if entry_point: - return os.path.dirname(os.path.expanduser(entry_point)) + if self.has_explicit_config: + # Default to the directory where the entry point is located. + entry_point = self.game_config.get(self.entry_point_option) + if entry_point: + return os.path.dirname(os.path.expanduser(entry_point)) return "" def resolve_game_path(self): @@ -253,7 +264,7 @@ available_libs.add(lib) unavailable_libs = set(self.require_libs) - available_libs if unavailable_libs: - raise UnavailableLibraries(unavailable_libs, self.arch) + raise UnavailableLibrariesError(unavailable_libs, self.arch) def get_run_data(self): """Return dict with command (exe & args list) and env vars (dict). @@ -385,18 +396,18 @@ ) opts = {"downloader": downloader, "callback": callback} if self.download_url: - opts["dest"] = os.path.join(settings.RUNNER_DIR, self.name) + opts["dest"] = self.directory return self.download_and_extract(self.download_url, **opts) runner = self.get_runner_version(version) if not runner: - raise RunnerInstallationError("Failed to retrieve {} ({}) information".format(self.name, version)) + raise RunnerInstallationError(_("Failed to retrieve {} ({}) information").format(self.name, version)) if not downloader: raise RuntimeError("Missing mandatory downloader for runner %s" % self) if "wine" in self.name: opts["merge_single"] = True opts["dest"] = os.path.join( - settings.RUNNER_DIR, self.name, "{}-{}".format(runner["version"], runner["architecture"]) + self.directory, "{}-{}".format(runner["version"], runner["architecture"]) ) if self.name == "libretro" and version: @@ -423,12 +434,12 @@ def extract(self, archive=None, dest=None, merge_single=None, callback=None): if not system.path_exists(archive): - raise RunnerInstallationError("Failed to extract {}".format(archive)) + raise RunnerInstallationError(_("Failed to extract {}").format(archive)) try: extract_archive(archive, dest, merge_single=merge_single) except ExtractFailure as ex: logger.error("Failed to extract the archive %s file may be corrupt", archive) - raise RunnerInstallationError("Failed to extract {}: {}".format(archive, ex)) from ex + raise RunnerInstallationError(_("Failed to extract {}: {}").format(archive, ex)) from ex os.remove(archive) if self.name == "wine": @@ -448,11 +459,10 @@ system.remove_folder(game_path) def can_uninstall(self): - runner_path = os.path.join(settings.RUNNER_DIR, self.name) - return os.path.isdir(runner_path) + return os.path.isdir(self.directory) def uninstall(self): - runner_path = os.path.join(settings.RUNNER_DIR, self.name) + runner_path = self.directory if os.path.isdir(runner_path): system.remove_folder(runner_path) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/steam.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/steam.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/steam.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/steam.py 2022-12-03 12:25:25.000000000 +0000 @@ -4,6 +4,7 @@ from gettext import gettext as _ from lutris.command import MonitoredCommand +from lutris.exceptions import UnavailableRunnerError from lutris.runners import NonInstallableRunnerError from lutris.runners.runner import Runner from lutris.util import linux, system @@ -238,11 +239,11 @@ return steamapps_paths[0] def install(self, version=None, downloader=None, callback=None): - raise NonInstallableRunnerError( + raise NonInstallableRunnerError(_( "Steam for Linux installation is not handled by Lutris.\n" "Please go to " "http://steampowered.com" - " or install Steam with the package provided by your distribution." + " or install Steam with the package provided by your distribution.") ) def install_game(self, appid, generate_acf=False): @@ -252,7 +253,7 @@ acf_content = to_vdf(acf_data) steamapps_path = self.get_default_steamapps_path() if not steamapps_path: - raise RuntimeError("Could not find Steam path, is Steam installed?") + raise UnavailableRunnerError(_("Could not find Steam path, is Steam installed?")) acf_path = os.path.join(steamapps_path, "appmanifest_%s.acf" % appid) with open(acf_path, "w", encoding='utf-8') as acf_file: acf_file.write(acf_content) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/wine.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/wine.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/wine.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/wine.py 2022-12-03 12:25:25.000000000 +0000 @@ -21,6 +21,7 @@ from lutris.util.wine.dgvoodoo2 import dgvoodoo2Manager from lutris.util.wine.dxvk import DXVKManager from lutris.util.wine.dxvk_nvapi import DXVKNVAPIManager +from lutris.util.wine.extract_icon import PEFILE_AVAILABLE, ExtractIcon from lutris.util.wine.prefix import DEFAULT_DLL_OVERRIDES, WinePrefixManager, find_prefix from lutris.util.wine.vkd3d import VKD3DManager from lutris.util.wine.wine import ( @@ -1017,3 +1018,36 @@ # Relative path return path + + def extract_icon_exe(self, game_slug): + """Extracts the 128*128 icon from EXE and saves it, if not resizes the biggest icon found. + returns true if an icon is saved, false if not""" + try: + wantedsize = (128, 128) + pathtoicon = settings.ICON_PATH + "/lutris_" + game_slug + ".png" + if not self.game_exe or os.path.exists(pathtoicon) or not PEFILE_AVAILABLE: + return False + + extractor = ExtractIcon(self.game_exe) + groups = extractor.get_group_icons() + + icons = [] + biggestsize = (0, 0) + biggesticon = -1 + for i in range(len(groups[0])): + icons.append(extractor.export(groups[0], i)) + if icons[i].size > biggestsize: + biggesticon = i + biggestsize = icons[i].size + elif icons[i].size == wantedsize: + icons[i].save(pathtoicon) + return True + + if biggesticon >= 0: + resized = icons[biggesticon].resize(wantedsize) + resized.save(pathtoicon) + return True + except Exception as err: + logger.exception("Failed to extract exe icon: %s", err) + + return False diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/xemu.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/xemu.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/xemu.py 1970-01-01 00:00:00.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/xemu.py 2022-12-03 12:25:25.000000000 +0000 @@ -0,0 +1,42 @@ +from gettext import gettext as _ + +from lutris.runners.runner import Runner +from lutris.util import system + + +class xemu(Runner): + human_name = _("xemu") + platforms = [_("Xbox")] + description = _("Xbox emulator") + runnable_alone = True + runner_executable = "xemu/xemu" + game_options = [ + { + "option": "main_file", + "type": "file", + "label": _("ISO file"), + "help": _("DVD image in iso format"), + } + ] + runner_options = [ + { + "option": "fullscreen", + "label": _("Fullscreen"), + "type": "bool", + "default": True, + }, + ] + + def play(self): + """Run the game.""" + arguments = [self.get_executable()] + + fullscreen = self.runner_config.get("fullscreen") + if fullscreen: + arguments.append("-full-screen") + + iso = self.game_config.get("main_file") or "" + if not system.path_exists(iso): + return {"error": "FILE_NOT_FOUND", "file": iso} + arguments += ["-dvd_path", iso] + return {"command": arguments} diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runners/yuzu.py lutris-0.5.12~ubuntu22.04.1/lutris/runners/yuzu.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runners/yuzu.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runners/yuzu.py 2022-12-03 12:25:25.000000000 +0000 @@ -35,7 +35,12 @@ "label": _("Title keys"), "type": "file", "help": _("File containing the title keys."), - } + }, { + "option": "fullscreen", + "label": _("Fullscreen"), + "type": "bool", + "default": True, + }, ] @property @@ -50,10 +55,15 @@ def play(self): """Run the game.""" arguments = [self.get_executable()] + + fullscreen = self.runner_config.get("fullscreen") + if fullscreen: + arguments.append("-f") + rom = self.game_config.get("main_file") or "" if not system.path_exists(rom): return {"error": "FILE_NOT_FOUND", "file": rom} - arguments.append(rom) + arguments += ["-g", rom] return {"command": arguments} def _update_key(self, key_type): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/runtime.py lutris-0.5.12~ubuntu22.04.1/lutris/runtime.py --- lutris-0.5.11~ubuntu22.04.1/lutris/runtime.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/runtime.py 2022-12-03 12:25:25.000000000 +0000 @@ -6,7 +6,7 @@ from gi.repository import GLib from lutris import settings -from lutris.util import http, jobs, system +from lutris.util import http, jobs, system, update_cache from lutris.util.downloader import Downloader from lutris.util.extract import extract_archive from lutris.util.linux import LINUX_SYSTEM @@ -187,12 +187,28 @@ class RuntimeUpdater: """Class handling the runtime updates""" - current_updates = 0 + cancelled = False status_updater = None + downloaders = {} + + def __init__(self, force=False): + self.force = force + + def update_runtimes(self): + """Update runtime components""" + runtime_call = update_cache.get_last_call("runtime") + if self.force or not runtime_call or runtime_call > 3600 * 12: + components_to_update = self.update() + if components_to_update: + while self.downloaders: + time.sleep(0.3) + if self.cancelled: + return + update_cache.write_date_to_cache("runtime") def is_updating(self): """Return True if the update process is running""" - return self.current_updates > 0 + return bool(self.downloaders) def update(self): """Launch the update process""" @@ -204,8 +220,13 @@ runtime = Runtime(remote_runtime["name"], self) downloader = runtime.download(remote_runtime) if downloader: - self.current_updates += 1 - return self.current_updates + self.downloaders[runtime] = downloader + return len(self.downloaders) + + def cancel(self): + self.cancelled = True + for downloader in self.downloaders: + downloader.cancel() @staticmethod def _iter_remote_runtimes(): @@ -244,8 +265,8 @@ def notify_finish(self, runtime): """A runtime has finished downloading""" logger.debug("Runtime %s is now updated and available", runtime.name) - self.current_updates -= 1 - if self.current_updates == 0: + del self.downloaders[runtime] + if not self.downloaders: logger.info("Runtime is fully updated.") diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/amazon.py lutris-0.5.12~ubuntu22.04.1/lutris/services/amazon.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/amazon.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/amazon.py 2022-12-03 12:25:25.000000000 +0000 @@ -12,7 +12,7 @@ from urllib.parse import parse_qs, urlencode, urlparse from lutris import settings -from lutris.exceptions import AuthenticationError, UnavailableGame +from lutris.exceptions import AuthenticationError, UnavailableGameError from lutris.services.base import OnlineService from lutris.services.service_game import ServiceGame from lutris.services.service_media import ServiceMedia @@ -29,6 +29,7 @@ size = (200, 112) dest_path = os.path.join(settings.CACHE_DIR, "amazon/banners") file_pattern = "%s.jpg" + file_format = "jpeg" api_field = "image" url_pattern = "%s" @@ -176,7 +177,7 @@ user_data = None if not os.path.exists(self.user_path): - raise AuthenticationError("No Amazon user data available, please log in again") + raise AuthenticationError(_("No Amazon user data available, please log in again")) with open(self.user_path, "r", encoding='utf-8') as user_file: user_data = json.load(user_file) @@ -241,7 +242,7 @@ request.post(json.dumps(data).encode()) except HTTPError as ex: logger.error("Failed http request %s", url) - raise AuthenticationError("Unable to register device, please log in again") from ex + raise AuthenticationError(_("Unable to register device, please log in again")) from ex res_json = request.json logger.info("Succesfully registered a device") @@ -256,7 +257,7 @@ expires_in = user_data["tokens"]["bearer"]["expires_in"] if not token_obtain_time or not expires_in: - raise AuthenticationError("Invalid token info found, please log in again") + raise AuthenticationError(_("Invalid token info found, please log in again")) return time.time() > token_obtain_time + int(expires_in) @@ -290,7 +291,7 @@ request.post(json.dumps(request_data).encode()) except HTTPError as ex: logger.error("Failed http request %s", url) - raise AuthenticationError("Unable to refresh token, please log in again") from ex + raise AuthenticationError(_("Unable to refresh token, please log in again")) from ex res_json = request.json @@ -397,7 +398,6 @@ "X-Amz-Target": target, "x-amzn-token": token, "User-Agent": self.user_agent, - "UserAgent": self.user_agent, "Content-Type": "application/json", "Content-Encoding": "amz-1.0", } @@ -407,9 +407,9 @@ try: request.post(json.dumps(body).encode()) - except HTTPError: + except HTTPError as ex: # Do not raise exception here, should be managed from the caller - logger.error("Failed http request %s", url) + logger.error("Failed http request %s: %s", url, ex) return return request.json @@ -435,8 +435,8 @@ if not response: logger.error("There was an error getting game manifest: %s", game_id) - raise UnavailableGame( - "Unable to get game manifest info, please check your Amazon credentials and internet connectivity") + raise UnavailableGameError(_( + "Unable to get game manifest info, please check your Amazon credentials and internet connectivity")) return response @@ -444,7 +444,6 @@ """Get a game manifest""" headers = { "User-Agent": self.user_agent, - "UserAgent": self.user_agent } url = manifest_info["downloadUrls"][0] @@ -454,8 +453,8 @@ request.get() except HTTPError as ex: logger.error("Failed http request %s", url) - raise UnavailableGame( - "Unable to get game manifest, please check your Amazon credentials and internet connectivity") from ex + raise UnavailableGameError(_( + "Unable to get game manifest, please check your Amazon credentials and internet connectivity")) from ex content = request.content @@ -470,9 +469,9 @@ raw_manifest = lzma.decompress(content[4 + header_size:]) else: logger.error("Unknown compression algorithm found in manifest") - raise UnavailableGame( + raise UnavailableGameError(_( "Unknown compression algorithm found in manifest, " - "please check your Amazon credentials and internet connectivity") + "please check your Amazon credentials and internet connectivity")) manifest = Manifest() manifest.decode(raw_manifest) @@ -501,9 +500,9 @@ if not response: logger.error("There was an error getting patches: %s", game_id) - raise UnavailableGame( + raise UnavailableGameError(_( "Unable to get the patches of game, " - "please check your Amazon credentials and internet connectivity", game_id) + "please check your Amazon credentials and internet connectivity"), game_id) return response["patches"] @@ -551,7 +550,6 @@ """Get and parse the fuel.json file""" headers = { "User-Agent": self.user_agent, - "UserAgent": self.user_agent } request = Request(fuel_url, headers=headers) @@ -560,8 +558,8 @@ request.get() except HTTPError as ex: logger.error("Failed http request %s", fuel_url) - raise UnavailableGame( - "Unable to get fuel.json file, please check your Amazon credentials and internet connectivity") from ex + raise UnavailableGameError(_( + "Unable to get fuel.json file, please check your Amazon credentials and internet connectivity")) from ex res_json = request.json diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/base.py lutris-0.5.12~ubuntu22.04.1/lutris/services/base.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/base.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/base.py 2022-12-03 12:25:25.000000000 +0000 @@ -13,8 +13,9 @@ from lutris.database.services import ServiceGameCollection from lutris.game import Game from lutris.gui.dialogs import NoticeDialog -from lutris.gui.dialogs.webconnect_dialog import WebConnectDialog +from lutris.gui.dialogs.webconnect_dialog import DEFAULT_USER_AGENT, WebConnectDialog from lutris.gui.views.media_loader import download_media +from lutris.gui.widgets.utils import BANNER_SIZE, ICON_SIZE from lutris.installer import get_installers from lutris.services.service_media import ServiceMedia from lutris.util import system @@ -30,26 +31,40 @@ class LutrisBanner(ServiceMedia): service = 'lutris' - size = (184, 69) + size = BANNER_SIZE dest_path = settings.BANNER_PATH file_pattern = "%s.jpg" + file_format = "jpeg" api_field = 'banner_url' class LutrisIcon(LutrisBanner): - size = (32, 32) + size = ICON_SIZE dest_path = settings.ICON_PATH file_pattern = "lutris_%s.png" + file_format = "png" api_field = 'icon_url' + @property + def custom_media_storage_size(self): + return (128, 128) + + def update_desktop(self): + system.update_desktop_icons() + class LutrisCoverart(ServiceMedia): service = 'lutris' size = (264, 352) file_pattern = "%s.jpg" + file_format = "jpeg" dest_path = settings.COVERART_PATH api_field = 'coverart' + @property + def config_ui_size(self): + return (66, 88) + class LutrisCoverartMedium(LutrisCoverart): size = (176, 234) @@ -298,6 +313,7 @@ login_window_width = 390 login_window_height = 500 + login_user_agent = DEFAULT_USER_AGENT @property def credential_files(self): @@ -315,8 +331,7 @@ return logger.debug("Connecting to %s", self.name) dialog = WebConnectDialog(self, parent) - dialog.set_modal(True) - dialog.show() + dialog.run() def is_authenticated(self): """Return whether the service is authenticated""" diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/dolphin.py lutris-0.5.12~ubuntu22.04.1/lutris/services/dolphin.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/dolphin.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/dolphin.py 2022-12-03 12:25:25.000000000 +0000 @@ -18,6 +18,7 @@ source = "local" size = (96, 32) file_pattern = "%s.png" + file_format = "jpeg" dest_path = os.path.join(settings.CACHE_DIR, "dolphin/banners/small") diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/egs.py lutris-0.5.12~ubuntu22.04.1/lutris/services/egs.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/egs.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/egs.py 2022-12-03 12:25:25.000000000 +0000 @@ -34,6 +34,7 @@ service = "egs" remote_size = (200, 267) file_pattern = "%s.jpg" + file_format = "jpeg" min_logo_x = 300 min_logo_y = 150 @@ -107,6 +108,7 @@ size = (200, 100) remote_size = size file_pattern = "%s.png" + file_format = "png" visible = False dest_path = os.path.join(settings.CACHE_DIR, "egs/game_logo") api_field = "DieselGameBoxLogo" @@ -150,7 +152,9 @@ cookies_path = os.path.join(settings.CACHE_DIR, ".egs.auth") token_path = os.path.join(settings.CACHE_DIR, ".egs.token") cache_path = os.path.join(settings.CACHE_DIR, "egs-library.json") - login_url = "https://www.epicgames.com/id/login?redirectUrl=https://www.epicgames.com/id/api/redirect" + login_url = ("https://www.epicgames.com/id/login?redirectUrl=" + "https%3A//www.epicgames.com/id/api/redirect%3F" + "clientId%3D34a02cf8f4414e29b15921876da36f9a%26responseType%3Dcode") redirect_uri = "https://www.epicgames.com/id/api/redirect" oauth_url = 'https://account-public-service-prod03.ol.epicgames.com' catalog_url = 'https://catalog-public-service-prod06.ol.epicgames.com' @@ -200,28 +204,9 @@ logger.debug("Login to EGS successful") logger.debug(content) content_json = json.loads(content.decode()) - session_id = content_json["sid"] - _session = requests.session() - _session.headers.update({ - 'X-Epic-Event-Action': 'login', - 'X-Epic-Event-Category': 'login', - 'X-Epic-Strategy-Flags': '', - 'X-Requested-With': 'XMLHttpRequest', - 'User-Agent': self.user_agent - }) - - _session.get('https://www.epicgames.com/id/api/set-sid', params={'sid': session_id}) - _session.get('https://www.epicgames.com/id/api/csrf') - response = _session.post( - 'https://www.epicgames.com/id/api/exchange/generate', - headers={'X-XSRF-TOKEN': _session.cookies['XSRF-TOKEN']} - ) - - if response.status_code != 200: - logger.error("Failed to connec to EGS (Status %s): %s", response.status_code, response.json()) - return + session_id = content_json["authorizationCode"] - self.start_session(response.json()['code']) + self.start_session(authorization_code=session_id) self.emit("service-login") def resume_session(self): @@ -235,21 +220,24 @@ raise RuntimeError(response_content) return response_content - def start_session(self, exchange_code=None): + def start_session(self, exchange_code=None, authorization_code=None): if exchange_code: - grant_type = 'exchange_code' - token = exchange_code + params = dict(grant_type='exchange_code', + exchange_code=exchange_code, + token_type='eg1') + elif authorization_code: + params = dict(grant_type='authorization_code', + code=authorization_code, + token_type='eg1') else: - grant_type = 'refresh_token' - token = self.session_data["refresh_token"] + params = dict(grant_type='refresh_token', + refresh_token=self.session_data["refresh_token"], + + token_type='eg1') response = self.session.post( 'https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token', - data={ - 'grant_type': grant_type, - grant_type: token, - 'token_type': 'eg1' - }, + data=params, auth=self.http_basic_auth ) if response.status_code >= 500: diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/flathub.py lutris-0.5.12~ubuntu22.04.1/lutris/services/flathub.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/flathub.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/flathub.py 2022-12-03 12:25:25.000000000 +0000 @@ -22,6 +22,7 @@ size = (128, 128) dest_path = os.path.join(settings.CACHE_DIR, "flathub/banners") file_pattern = "%s.png" + file_format = "png" url_field = 'iconDesktopUrl' def get_media_url(self, details): @@ -86,7 +87,7 @@ logger.warning("Flathub games are already loading") return self.is_loading = True - response = requests.get(self.api_url) + response = requests.get(self.api_url, timeout=5) entries = response.json() # seen = set() flathub_games = [] diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/gog.py lutris-0.5.12~ubuntu22.04.1/lutris/services/gog.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/gog.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/gog.py 2022-12-03 12:25:25.000000000 +0000 @@ -9,7 +9,7 @@ from lxml import etree from lutris import settings -from lutris.exceptions import AuthenticationError, UnavailableGame +from lutris.exceptions import AuthenticationError, UnavailableGameError from lutris.installer import AUTO_ELF_EXE, AUTO_WIN32_EXE from lutris.installer.installer_file import InstallerFile from lutris.services.base import OnlineService @@ -27,6 +27,7 @@ size = (100, 60) dest_path = os.path.join(settings.CACHE_DIR, "gog/banners/small") file_pattern = "%s.jpg" + file_format = "jpeg" api_field = "image" url_pattern = "https:%s_prof_game_100x60.jpg" @@ -177,7 +178,8 @@ request = Request(url) try: request.get() - except HTTPError: + except HTTPError as http_error: + logger.error(http_error) logger.error("Failed to get token, check your GOG credentials.") logger.warning("Clearing existing credentials") self.logout() @@ -300,9 +302,9 @@ response = self.make_api_request(downlink) except HTTPError as ex: logger.error("HTTP error: %s", ex) - raise UnavailableGame("The download of '%s' failed." % downlink) from ex + raise UnavailableGameError(_("The download of '%s' failed.") % downlink) from ex if not response: - raise UnavailableGame("The download of '%s' failed." % downlink) + raise UnavailableGameError(_("The download of '%s' failed.") % downlink) for field in ("checksum", "downlink"): field_url = response[field] parsed = urlparse(field_url) @@ -430,7 +432,7 @@ _installer = gog_installers[0] return self.query_download_links(_installer) except HTTPError as err: - raise UnavailableGame("Couldn't load the download links for this game") from err + raise UnavailableGameError(_("Couldn't load the download links for this game")) from err def get_patch_files(self, installer, installer_file_id): logger.debug("Getting patches for %s", installer.version) @@ -476,14 +478,14 @@ "checksum_url": installer_file.get("checksum_url") })) if not file_id_provided: - raise UnavailableGame("Unable to determine correct file to launch installer") + raise UnavailableGameError(_("Unable to determine correct file to launch installer")) return files def get_installer_files(self, installer, installer_file_id): try: downloads = self.get_downloads(installer.service_appid) except HTTPError as err: - raise UnavailableGame("Couldn't load the downloads for this game") from err + raise UnavailableGameError(_("Couldn't load the downloads for this game")) from err links = self._get_installer_links(installer, downloads) if links: files = self._format_links(installer, installer_file_id, links) @@ -625,7 +627,7 @@ patch_installers = [] for version in patch_versions: patch = patch_versions[version] - size = human_size(sum([part["total_size"] for part in patch])) + size = human_size(sum(part["total_size"] for part in patch)) patch_id = "gogpatch-%s" % slugify(patch[0]["version"]) installer = { "name": db_game["name"], diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/humblebundle.py lutris-0.5.12~ubuntu22.04.1/lutris/services/humblebundle.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/humblebundle.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/humblebundle.py 2022-12-03 12:25:25.000000000 +0000 @@ -5,7 +5,7 @@ from gettext import gettext as _ from lutris import settings -from lutris.exceptions import UnavailableGame +from lutris.exceptions import UnavailableGameError from lutris.installer import AUTO_ELF_EXE, AUTO_WIN32_EXE from lutris.installer.installer_file import InstallerFile from lutris.services.base import OnlineService @@ -23,6 +23,7 @@ size = (70, 70) dest_path = os.path.join(settings.CACHE_DIR, "humblebundle/icons") file_pattern = "%s.png" + file_format = "png" api_field = "icon" @@ -220,9 +221,9 @@ link = get_humble_download_link(installer.service_appid, installer.runner) except Exception as ex: logger.exception("Failed to get Humble Bundle game: %s", ex) - raise UnavailableGame("The download URL for the game could not be determined.") from ex + raise UnavailableGameError(_("The download URL for the game could not be determined.")) from ex if not link: - raise UnavailableGame("No game found on Humble Bundle") + raise UnavailableGameError(_("No game found on Humble Bundle")) filename = link.split("?")[0].split("/")[-1] return [ InstallerFile(installer.game_slug, installer_file_id, { diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/origin.py lutris-0.5.12~ubuntu22.04.1/lutris/services/origin.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/origin.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/origin.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,11 +2,13 @@ import json import os import random +import ssl import urllib.parse from gettext import gettext as _ from xml.etree import ElementTree import requests +import urllib3 from gi.repository import Gio from lutris import settings @@ -21,6 +23,8 @@ from lutris.util.log import logger from lutris.util.strings import slugify +SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION = 1 << 18 + class OriginLauncher: manifests_paths = "ProgramData/Origin/LocalContent" @@ -47,6 +51,7 @@ class OriginPackArtSmall(ServiceMedia): service = "origin" file_pattern = "%s.jpg" + file_format = "jpeg" size = (63, 89) dest_path = os.path.join(settings.CACHE_DIR, "origin/pack-art-small") api_field = "packArtSmall" @@ -80,6 +85,43 @@ return origin_game +class LegacyRenegotiationHTTPAdapter(requests.adapters.HTTPAdapter): + """Allow insecure SSL/TLS protocol renegotiation in an HTTP request. + + By default, OpenSSL v3 expects that servers support RFC 5746. Unfortunately, + accounts.ea.com does not support this TLS extension (from 2010!), causing + OpenSSL to refuse to connect. This `requests` HTTP Adapter configures + OpenSSL to allow "unsafe legacy renegotiation", allowing EA Origin to + connect. This is only intended as a temporary workaround, and should be + removed as soon as accounts.ea.com is updated to support RFC 5746. + + Using this adapter will reduce the security of the connection. However, the + impact should be relatively minimal this is only used to connect to EA + services. See CVE-2009-3555 for more details. + + See #4235 for more information. + """ + + def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs): + """Override the default PoolManager to allow insecure renegotiation.""" + # Based off of the default function from `requests`. + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + ssl_context = ssl.create_default_context() + ssl_context.options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + + self.poolmanager = urllib3.PoolManager( + num_pools=connections, + maxsize=maxsize, + block=block, + strict=True, + ssl_context=ssl_context, + **pool_kwargs, + ) + + class OriginService(OnlineService): """Service class for EA Origin""" @@ -105,12 +147,14 @@ "&locale=en_US&release_type=prod" "&redirect_uri=%s" ) % redirect_uri + login_user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0 QtWebEngine/5.8.0" is_loading = False def __init__(self): super().__init__() self.session = requests.session() + self.session.mount("https://", LegacyRenegotiationHTTPAdapter()) self.access_token = self.load_access_token() @property diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/service_media.py lutris-0.5.12~ubuntu22.04.1/lutris/services/service_media.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/service_media.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/service_media.py 2022-12-03 12:25:25.000000000 +0000 @@ -23,6 +23,7 @@ small_size = None dest_path = None file_pattern = NotImplemented + file_format = NotImplemented api_field = NotImplemented url_pattern = "%s" @@ -41,9 +42,12 @@ """Whether the icon for the specified slug exists locally""" return system.path_exists(self.get_absolute_path(slug)) - def get_pixbuf_for_game(self, slug, is_installed=True): + def get_pixbuf_for_game(self, slug, size=None, is_installed=True): + if not size: + size = self.size + image_abspath = self.get_absolute_path(slug) - return get_pixbuf(image_abspath, self.size, fallback=get_default_icon(self.size), is_installed=is_installed) + return get_pixbuf(image_abspath, size, fallback=get_default_icon(size), is_installed=is_installed) def get_media_url(self, details): if self.api_field not in details: @@ -87,5 +91,19 @@ except HTTPError as ex: logger.error("Failed to download %s: %s", url, ex) + @property + def custom_media_storage_size(self): + """The size this media is stored in when customized; we accept + whatever we get when we download the media, however.""" + return self.size + + @property + def config_ui_size(self): + """The size this media should be shown at when in the configuration UI.""" + return self.size + + def update_desktop(self): + """Update the desktop, if this media type appears there. Most don't.""" + def render(self): """Used if the media requires extra processing""" diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/steam.py lutris-0.5.12~ubuntu22.04.1/lutris/services/steam.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/steam.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/steam.py 2022-12-03 12:25:25.000000000 +0000 @@ -26,6 +26,7 @@ size = (184, 69) dest_path = os.path.join(settings.CACHE_DIR, "steam/banners") file_pattern = "%s.jpg" + file_format = "jpeg" api_field = "appid" url_pattern = "http://cdn.akamai.steamstatic.com/steam/apps/%s/capsule_184x69.jpg" @@ -35,6 +36,7 @@ size = (200, 300) dest_path = os.path.join(settings.CACHE_DIR, "steam/covers") file_pattern = "%s.jpg" + file_format = "jpeg" api_field = "appid" url_pattern = "http://cdn.steamstatic.com/steam/apps/%s/library_600x900.jpg" @@ -44,6 +46,7 @@ size = (460, 215) dest_path = os.path.join(settings.CACHE_DIR, "steam/header") file_pattern = "%s.jpg" + file_format = "jpeg" api_field = "appid" url_pattern = "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/header.jpg" @@ -197,7 +200,7 @@ steam_game = Game(game_id) if not steam_game.playtime: steam_game.remove(no_signal=True) - steam_game.delete() + steam_game.delete(no_signal=True) stats["deduped"] += 1 logger.debug("%s Steam games deduplicated", stats["deduped"]) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/ubisoft.py lutris-0.5.12~ubuntu22.04.1/lutris/services/ubisoft.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/ubisoft.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/ubisoft.py 2022-12-03 12:25:25.000000000 +0000 @@ -31,6 +31,7 @@ size = (160, 186) dest_path = os.path.join(settings.CACHE_DIR, "ubisoft/covers") file_pattern = "%s.jpg" + file_format = "jpeg" api_field = "id" url_pattern = "https://ubiservices.cdn.ubi.com/%s/spaceCardAsset/boxArt_mobile.jpg?imwidth=320" diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/services/xdg.py lutris-0.5.12~ubuntu22.04.1/lutris/services/xdg.py --- lutris-0.5.11~ubuntu22.04.1/lutris/services/xdg.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/services/xdg.py 2022-12-03 12:25:25.000000000 +0000 @@ -36,6 +36,7 @@ size = (64, 64) dest_path = os.path.join(settings.CACHE_DIR, "xdg/icons") file_pattern = "%s.png" + file_format = "png" class XDGService(BaseService): diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/settings.py lutris-0.5.12~ubuntu22.04.1/lutris/settings.py --- lutris-0.5.11~ubuntu22.04.1/lutris/settings.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/settings.py 2022-12-03 12:25:25.000000000 +0000 @@ -47,7 +47,6 @@ RUNTIME_URL = SITE_URL + "/api/runtimes" STEAM_API_KEY = sio.read_setting("steam_api_key") or "34C9698CEB394AB4401D65927C6B3752" -DISCORD_CLIENT_ID = sio.read_setting("discord_client_id") or "618290412402114570" SHOW_MEDIA = os.environ.get("LUTRIS_HIDE_MEDIA") != "1" and sio.read_setting("hide_media") != 'True' diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/startup.py lutris-0.5.12~ubuntu22.04.1/lutris/startup.py --- lutris-0.5.11~ubuntu22.04.1/lutris/startup.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/startup.py 2022-12-03 12:25:25.000000000 +0000 @@ -1,7 +1,6 @@ """Check to run at program start""" import os import sqlite3 -import time from gettext import gettext as _ from lutris import runners, settings @@ -181,7 +180,7 @@ syncdb() except sqlite3.DatabaseError as err: raise RuntimeError( - "Failed to open database file in %s. Try renaming this file and relaunch Lutris" % + _("Failed to open database file in %s. Try renaming this file and relaunch Lutris") % settings.PGA_DB ) from err for service in DEFAULT_SERVICES: @@ -190,26 +189,30 @@ cleanup_games() -def update_runtime(force=False): - """Update runtime components""" - runtime_call = update_cache.get_last_call("runtime") - if force or not runtime_call or runtime_call > 3600 * 12: - runtime_updater = RuntimeUpdater() - components_to_update = runtime_updater.update() - if components_to_update: - while runtime_updater.current_updates: - time.sleep(0.3) - update_cache.write_date_to_cache("runtime") - for dll_manager_class in (DXVKManager, DXVKNVAPIManager, VKD3DManager, D3DExtrasManager, dgvoodoo2Manager): - key = dll_manager_class.__name__ - key_call = update_cache.get_last_call(key) - if force or not key_call or key_call > 3600 * 6: - dll_manager = dll_manager_class() - dll_manager.upgrade() - update_cache.write_date_to_cache(key) - media_call = update_cache.get_last_call("media") - if force or not media_call or media_call > 3600 * 24: - sync_media() - update_all_artwork() - update_cache.write_date_to_cache("media") - logger.info("Startup complete") +class StartupRuntimeUpdater(RuntimeUpdater): + """Due to circular reference problems, we need to keep all these interesting + references here, out of runtime.py""" + dll_manager_classes = [DXVKManager, DXVKNVAPIManager, VKD3DManager, D3DExtrasManager, dgvoodoo2Manager] + + def update_runtimes(self): + super().update_runtimes() + for dll_manager_class in self.dll_manager_classes: + if self.cancelled: + break + key = dll_manager_class.__name__ + key_call = update_cache.get_last_call(key) + if self.force or not key_call or key_call > 3600 * 6: + dll_manager = dll_manager_class() + dll_manager.upgrade() + update_cache.write_date_to_cache(key) + + if not self.cancelled: + media_call = update_cache.get_last_call("media") + if self.force or not media_call or media_call > 3600 * 24: + sync_media() + update_all_artwork() + update_cache.write_date_to_cache("media") + + if self.cancelled: + logger.info("Runtime update cancelled") + logger.info("Startup complete") diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/sysoptions.py lutris-0.5.12~ubuntu22.04.1/lutris/sysoptions.py --- lutris-0.5.11~ubuntu22.04.1/lutris/sysoptions.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/sysoptions.py 2022-12-03 12:25:25.000000000 +0000 @@ -98,7 +98,6 @@ nvidia = [] amdvlk = [] amdvlkpro = [] - choices = [(_("Auto: WARNING -- No Vulkan Loader detected!"), "")] icd_files = defaultdict(list) # Add loaders for data_dir in VULKAN_DATA_DIRS: @@ -124,30 +123,53 @@ amdvlk_files = ":".join(amdvlk) amdvlkpro_files = ":".join(amdvlkpro) - intel_name = _("Auto: Intel Open Source (MESA: ANV)") - amdradv_name = _("Auto: AMD RADV Open Source (MESA: RADV)") - nvidia_name = _("Auto: Nvidia Proprietary") - - glxinfocmd = get_gpu_vendor_cmd(bool(nvidia_files)) - with subprocess.Popen(glxinfocmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as glxvendorget: - glxvendor = glxvendorget.communicate()[0].decode("utf-8") - default_gpu = glxvendor - - if "Intel" in default_gpu: - choices = [(intel_name, intel_files)] - elif "AMD" in default_gpu: - choices = [(amdradv_name, amdradv_files)] - elif "NVIDIA" in default_gpu: - choices = [(nvidia_name, nvidia_files)] - elif USE_DRI_PRIME: - # We have multiple video chipsets, pick something that is instlaled if possible; - # we prefer NVIDIA and AMD over Intel, because don't we all? - if bool(nvidia_files) and has_graphic_adapter_description("NVIDIA"): - choices = [(nvidia_name, nvidia_files)] - elif bool(amdradv_files) and has_graphic_adapter_description("AMD"): - choices = [(amdradv_name, amdradv_files)] - elif bool(intel_files) and has_graphic_adapter_description("Intel"): - choices = [(intel_name, intel_files)] + # Start the 'choices' with an 'auto' choice. But which one? + auto_intel_name = _("Auto: Intel Open Source (MESA: ANV)") + auto_amdradv_name = _("Auto: AMD RADV Open Source (MESA: RADV)") + auto_nvidia_name = _("Auto: Nvidia Proprietary") + + vk_icd_filenames = os.getenv("VK_ICD_FILENAMES") + if vk_icd_filenames: + # VK_ICD_FILENAMES is what we are going to set in the end, so + # if it starts out set, the 'Auto' choice should always leave it + # alone- but we do want to pick a nice name for it. + # + # Note that when the choice is "", we just leave VK_ICD_FILENAMES + # alone and do not overwrite it. + if "intel" in vk_icd_filenames: + choices = [(auto_intel_name, "")] + elif "radeon" in vk_icd_filenames or "amd" in vk_icd_filenames or "pro" in vk_icd_filenames: + choices = [(auto_amdradv_name, "")] + elif "nvidia" in vk_icd_filenames: + choices = [(auto_nvidia_name, "")] + else: + choices = [(_("Auto: WARNING -- No Vulkan Loader detected!"), "")] + else: + # Without VK_ICD_FILENAMES, we'll try to figure out what GPU the + # user has installed and which has ICD files. If that fails, we'll + # just use blank and hope for the best. + choices = [(_("Auto: WARNING -- No Vulkan Loader detected!"), "")] + + glxinfocmd = get_gpu_vendor_cmd(bool(nvidia_files)) + with subprocess.Popen(glxinfocmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as glxvendorget: + glxvendor = glxvendorget.communicate()[0].decode("utf-8") + default_gpu = glxvendor + + if "Intel" in default_gpu and intel_files: + choices = [(auto_intel_name, intel_files)] + elif "AMD" in default_gpu and amdradv_files: + choices = [(auto_amdradv_name, amdradv_files)] + elif "NVIDIA" in default_gpu and intel_files: + choices = [(auto_nvidia_name, nvidia_files)] + elif USE_DRI_PRIME: + # We have multiple video chipsets, pick something that is instlaled if possible; + # we prefer NVIDIA and AMD over Intel, because don't we all? + if nvidia_files and has_graphic_adapter_description("NVIDIA"): + choices = [(auto_nvidia_name, nvidia_files)] + elif amdradv_files and has_graphic_adapter_description("AMD"): + choices = [(auto_amdradv_name, amdradv_files)] + elif intel_files and has_graphic_adapter_description("Intel"): + choices = [(auto_intel_name, intel_files)] if intel_files: choices.append(("Intel Open Source (MESA: ANV)", intel_files)) @@ -162,6 +184,7 @@ choices.append(("AMDVLK Open source", amdvlk_files)) if amdvlkpro_files: choices.append(("AMDGPU-PRO Proprietary", amdvlkpro_files)) + choices.append((_("Unspecified (Use System Default)"), "")) return choices diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/base.py lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/base.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/base.py 1970-01-01 00:00:00.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/base.py 2022-12-03 12:25:25.000000000 +0000 @@ -0,0 +1,30 @@ +""" +Discord Rich Presence Base Objects + +""" +from abc import ABCMeta + + +class DiscordRichPresenceBase(metaclass=ABCMeta): + """ + Discord Rich Presence Interface + + """ + + def update(self, discord_id: str) -> None: + raise NotImplementedError() + + def clear(self) -> None: + raise NotImplementedError() + + +class DiscordRPCNull(DiscordRichPresenceBase): + """ + Null client for disabled Discord RPC + """ + + def update(self, discord_id: str) -> None: + pass + + def clear(self) -> None: + pass diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/client.py lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/client.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/client.py 1970-01-01 00:00:00.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/client.py 2022-12-03 12:25:25.000000000 +0000 @@ -0,0 +1,35 @@ +from pypresence import Presence + +from lutris.util.discord.base import DiscordRichPresenceBase + + +class DiscordRichPresenceClient(DiscordRichPresenceBase): + rpc = None # Presence Object + + def __init__(self): + self.playing = None + self.rpc = None + + def update(self, discord_id): + if self.rpc is not None: + # Clear the old RPC before creating a new one + self.clear() + + # Create a new Presence object with the desired app id + self.rpc = Presence(discord_id) + # Connect to discord endpoint + self.rpc.connect() + # Trigger an update making the status available + self.rpc.update() + + def clear(self): + if self.rpc is None: + # Skip already deleted rpc + return + # Clear and Close Presence connection + self.rpc.clear() + self.rpc.close() + # Clear Presence Object + self.rpc = None + # Clear Internal Reference + self.playing = None diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/__init__.py lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/__init__.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/__init__.py 2022-12-03 12:25:25.000000000 +0000 @@ -0,0 +1,3 @@ +__all__ = ['client'] + +from .rpc import client diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/rpc.py lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/rpc.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/discord/rpc.py 1970-01-01 00:00:00.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/discord/rpc.py 2022-12-03 12:25:25.000000000 +0000 @@ -0,0 +1,18 @@ +""" +Discord Rich Presence Loader + +This will enable DiscordRichPresenceClient if pypresence is installed. +Otherwise, it will provide a dummy client that does nothing +""" + + +from lutris.util.discord.base import DiscordRPCNull + +try: + from lutris.util.discord.client import DiscordRichPresenceClient +except ImportError: + # If PyPresence is not present, and ImportError will raise, so we provide dummy client + client = DiscordRPCNull() +else: + # PyPresence is present, so we provide the client + client = DiscordRichPresenceClient() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/discord.py lutris-0.5.12~ubuntu22.04.1/lutris/util/discord.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/discord.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/discord.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -import base64 -import json - -import requests - - -def set_discord_status(token, status): - """Set a custom status for a user referenced by its token""" - if not token: - return - payload = json.dumps({"custom_status": {"text": status}}) - super_properties_raw = ( - '{"os":"Linux","browser":"Firefox","device":"","system_locale":"en-US",' - '"browser_user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0",' - '"browser_version":"102.0","os_version":"","referrer":"","referring_domain":"",' - '"referrer_current":"","referring_domain_current":"","release_channel":"stable",' - '"client_build_number":135341,"client_event_source":null}' - ) - headers = { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "en-US", - "Alt-Used": "discord.com", - "Authorization": token, - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "Content-Length": str(len(payload)), - "Content-Type": "application/json", - "Host": "discord.com", - "Origin": "https://discord.com", - "Pragma": "no-cache", - "Referer": "", - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "no-cors", - "Sec-Fetch-Site": "same-origin", - "TE": "trailers", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0", - "X-Debug-Options": "bugReporterEnabled", - "X-Discord-Locale": "en-US", - "X-Super-Properties": base64.b64encode(super_properties_raw.encode('utf-8')) - } - return requests.patch("https://discord.com/api/v9/users/@me/settings", payload, headers=headers) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/downloader.py lutris-0.5.12~ubuntu22.04.1/lutris/util/downloader.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/downloader.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/downloader.py 2022-12-03 12:25:25.000000000 +0000 @@ -140,7 +140,7 @@ headers["User-Agent"] = "Lutris/%s" % __version__ if self.referer: headers["Referer"] = self.referer - response = requests.get(self.url, headers=headers, stream=True) + response = requests.get(self.url, headers=headers, stream=True, timeout=30) if response.status_code != 200: logger.info("%s returned a %s error", self.url, response.status_code) response.raise_for_status() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/extract.py lutris-0.5.12~ubuntu22.04.1/lutris/util/extract.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/extract.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/extract.py 2022-12-03 12:25:25.000000000 +0000 @@ -87,6 +87,8 @@ extractor = "exe" elif path.endswith(".deb"): extractor = "deb" + elif path.endswith(".AppImage"): + extractor = "AppImage" else: extractor = None return extractor @@ -101,7 +103,7 @@ opener, mode = tarfile.open, "r:gz" elif extractor == "txz": opener, mode = tarfile.open, "r:xz" - elif extractor == "tbz2": + elif extractor in ("tbz2", "bz2"): # bz2 is used for .tar.bz2 in some installer scripts opener, mode = tarfile.open, "r:bz2" elif extractor == "tzst": opener, mode = tarfile.open, "r:zst" # Note: not supported by tarfile yet @@ -113,6 +115,8 @@ opener = "exe" elif extractor == "deb": opener = "deb" + elif extractor == "AppImage": + opener = "AppImage" else: opener = "7zip" return opener, mode @@ -187,6 +191,8 @@ extract_gog(archive, dest) elif opener == "deb": extract_deb(archive, dest) + elif opener == "AppImage": + extract_AppImage(archive, dest) else: handler = opener(archive, mode) handler.extractall(dest) @@ -233,6 +239,13 @@ break +def extract_AppImage(path, dest): + """This is really here to prevent 7-zip from extracting the AppImage; + we want to just use this sort of file as-is.""" + system.create_folder(dest) + shutil.copy(path, dest) + + def extract_gog(path, dest): if check_inno_exe(path): decompress_gog(path, dest) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/graphics/drivers.py lutris-0.5.12~ubuntu22.04.1/lutris/util/graphics/drivers.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/graphics/drivers.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/graphics/drivers.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,11 +2,9 @@ Everything in this module should rely on /proc or /sys only, no executable calls """ -# Standard Library import os import re -# Lutris Modules from lutris.util.log import logger MIN_RECOMMENDED_NVIDIA_DRIVER = 415 @@ -66,7 +64,12 @@ if not os.path.exists("/sys/class/drm"): logger.error("No GPU available on this system!") return - for cardname in os.listdir("/sys/class/drm/"): + try: + cardlist = os.listdir("/sys/class/drm/") + except PermissionError: + logger.error("Your system does not allow reading from /sys/class/drm, no GPU detected.") + return + for cardname in cardlist: if re.match(r"^card\d$", cardname): yield cardname diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/http.py lutris-0.5.12~ubuntu22.04.1/lutris/util/http.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/http.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/http.py 2022-12-03 12:25:25.000000000 +0000 @@ -2,17 +2,22 @@ import json import os import socket +import ssl import urllib.error import urllib.parse import urllib.request from ssl import CertificateError +import certifi + from lutris.settings import PROJECT, SITE_URL, VERSION, read_setting from lutris.util import system from lutris.util.log import logger DEFAULT_TIMEOUT = read_setting("default_http_timeout") or 30 +ssl._create_default_https_context = lambda: ssl.create_default_context(cafile=certifi.where()) + class HTTPError(Exception): """Exception raised on request failures""" diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/jobs.py lutris-0.5.12~ubuntu22.04.1/lutris/util/jobs.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/jobs.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/jobs.py 2022-12-03 12:25:25.000000000 +0000 @@ -63,7 +63,7 @@ def _make_idle_safe(function): """Wrap a function in another, which just discards its result. - GLib.idle_add may call the function again if it returns True, + GLib.idle_add may call the function again if it returns True, but this wrapper only returns false.""" def discarding_result(*args, **kwargs): function(*args, **kwargs) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/process_watcher.py lutris-0.5.12~ubuntu22.04.1/lutris/util/process_watcher.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/process_watcher.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/process_watcher.py 2022-12-03 12:25:25.000000000 +0000 @@ -24,6 +24,8 @@ "mscorsvw.exe", "iexplore.exe", "winedbg.exe", + "tabtip.exe", + "conhost.exe", } diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/settings.py lutris-0.5.12~ubuntu22.04.1/lutris/util/settings.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/settings.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/settings.py 2022-12-03 12:25:25.000000000 +0000 @@ -19,7 +19,7 @@ except configparser.ParsingError as ex: logger.error("Failed to readconfig file %s: %s", self.config_file, ex) except UnicodeDecodeError as ex: - logger.error("Some invalid characters are preventing " "the setting file from loading properly: %s", ex) + logger.error("Some invalid characters are preventing the setting file from loading properly: %s", ex) def read_setting(self, key, section="lutris", default=""): """Read a setting from the config file diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/steam/config.py lutris-0.5.12~ubuntu22.04.1/lutris/util/steam/config.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/steam/config.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/steam/config.py 2022-12-03 12:25:25.000000000 +0000 @@ -112,7 +112,7 @@ settings.STEAM_API_KEY, steamid ) ) - response = requests.get(steam_games_url) + response = requests.get(steam_games_url, timeout=30) if response.status_code > 400: logger.error("Invalid response from steam: %s", response) return [] diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/steam/shortcut.py lutris-0.5.12~ubuntu22.04.1/lutris/util/steam/shortcut.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/steam/shortcut.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/steam/shortcut.py 2022-12-03 12:25:25.000000000 +0000 @@ -26,7 +26,7 @@ def vdf_file_exists(): - return bool(get_shortcuts_vdf_path) + return bool(get_shortcuts_vdf_path()) def matches_id(shortcut, game): @@ -39,12 +39,18 @@ def shortcut_exists(game): - shortcut_path = get_shortcuts_vdf_path() - if not shortcut_path or not os.path.exists(shortcut_path): + try: + shortcut_path = get_shortcuts_vdf_path() + if not shortcut_path or not os.path.exists(shortcut_path): + return False + + with open(shortcut_path, "rb") as shortcut_file: + shortcuts = vdf.binary_loads(shortcut_file.read())['shortcuts'].values() + + return bool([s for s in shortcuts if matches_id(s, game)]) + except Exception as ex: + logger.error("Failed to read shortcut vdf file: %s", ex) return False - with open(shortcut_path, "rb") as shortcut_file: - shortcuts = vdf.binary_loads(shortcut_file.read())['shortcuts'].values() - return bool([s for s in shortcuts if matches_id(s, game)]) def is_steam_game(game): @@ -150,15 +156,18 @@ def update_all_artwork(): - shortcut_path = get_shortcuts_vdf_path() - if not shortcut_path or not os.path.exists(shortcut_path): - return - with open(shortcut_path, "rb") as shortcut_file: - shortcuts = vdf.binary_loads(shortcut_file.read())['shortcuts'].values() - for shortcut in shortcuts: - id_match = re.match(r".*lutris:rungameid/(\d+)", shortcut["LaunchOptions"]) - if not id_match: - continue - game_id = int(id_match.groups()[0]) - game = Game(game_id) - set_artwork(game) + try: + shortcut_path = get_shortcuts_vdf_path() + if not shortcut_path or not os.path.exists(shortcut_path): + return + with open(shortcut_path, "rb") as shortcut_file: + shortcuts = vdf.binary_loads(shortcut_file.read())['shortcuts'].values() + for shortcut in shortcuts: + id_match = re.match(r".*lutris:rungameid/(\d+)", shortcut["LaunchOptions"]) + if not id_match: + continue + game_id = int(id_match.groups()[0]) + game = Game(game_id) + set_artwork(game) + except Exception as ex: + logger.error("Failed to update steam shortcut artwork: %s", ex) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/steam/vdf/vdict.py lutris-0.5.12~ubuntu22.04.1/lutris/util/steam/vdf/vdict.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/steam/vdf/vdict.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/steam/vdf/vdict.py 2022-12-03 12:25:25.000000000 +0000 @@ -1,4 +1,4 @@ -# pylint: disable=no-member +# pylint: disable=no-member,unnecessary-dunder-call from collections import Counter _iter_values = 'values' diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/system.py lutris-0.5.12~ubuntu22.04.1/lutris/util/system.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/system.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/system.py 2022-12-03 12:25:25.000000000 +0000 @@ -88,6 +88,52 @@ return stdout.strip() +def spawn(command, env=None, cwd=None, quiet=False, shell=False): + """ + Execute a system command but discard its results and do not wait + for it to complete. + + Params: + command (list): A list containing an executable and its parameters + env (dict): Dict of values to add to the current environment + cwd (str): Working directory + quiet (bool): Do not display log messages + """ + + # Check if the executable exists + if not command: + logger.error("No executable provided!") + return + if os.path.isabs(command[0]) and not path_exists(command[0]): + logger.error("No executable found in %s", command) + return + + if not quiet: + logger.debug("Spawning %s", " ".join([str(i) for i in command])) + + # Set up environment + existing_env = os.environ.copy() + if env: + if not quiet: + logger.debug(" ".join("{}={}".format(k, v) for k, v in env.items())) + env = {k: v for k, v in env.items() if v is not None} + existing_env.update(env) + + # Piping stderr can cause slowness in the programs, use carefully + # (especially when using regedit with wine) + try: + subprocess.Popen( # pylint: disable=consider-using-with + command, + shell=shell, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + env=existing_env, + cwd=cwd + ) + except (OSError, TypeError) as ex: + logger.error("Could not run command %s (env: %s): %s", command, env, ex) + + def read_process_output(command, timeout=5): """Return the output of a command as a string""" try: @@ -241,7 +287,7 @@ """ if not os.path.exists(path): logger.warning("Non existent path: %s", path) - return + return False logger.debug("Removing folder %s", path) if os.path.samefile(os.path.expanduser("~"), path): raise RuntimeError("Lutris tried to erase home directory!") @@ -421,19 +467,26 @@ """Return the disk size in bytes of a folder""" total_size = 0 for base, _dirs, files in os.walk(path): - total_size += sum([ + total_size += sum( os.stat(os.path.join(base, f)).st_size for f in files if os.path.isfile(os.path.join(base, f)) - ]) + ) return total_size def get_locale_list(): """Return list of available locales""" - with subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE) as locale_getter: - output = locale_getter.communicate() - locales = output[0].decode('ASCII').split() # locale names use only ascii characters + try: + with subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE) as locale_getter: + output = locale_getter.communicate() + locales = output[0].decode('ASCII').split() # locale names use only ascii characters + except FileNotFoundError: + lang = os.environ.get('LANG', '') + if lang: + locales = [lang] + else: + locales = [] return locales diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/ubisoft/client.py lutris-0.5.12~ubuntu22.04.1/lutris/util/ubisoft/client.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/ubisoft/client.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/ubisoft/client.py 2022-12-03 12:25:25.000000000 +0000 @@ -9,7 +9,7 @@ import requests from lutris.util.log import logger -from lutris.util.ubisoft.consts import CHROME_USERAGENT, CLUB_APPID, UBISOFT_APPID +from lutris.util.ubisoft.consts import CHROME_USERAGENT, CLUB_APPID def parse_date(date_str): @@ -105,7 +105,7 @@ def _do_options_request(self): self._do_request('options', "https://public-ubiservices.ubi.com/v3/profiles/sessions", headers={ "Origin": "https://connect.ubisoft.com", - "Referer": f"https://connect.ubisoft.com/login?appId={UBISOFT_APPID}", + "Referer": "https://connect.ubisoft.com/", "User-Agent": CHROME_USERAGENT, }) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/ubisoft/consts.py lutris-0.5.12~ubuntu22.04.1/lutris/util/ubisoft/consts.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/ubisoft/consts.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/ubisoft/consts.py 2022-12-03 12:25:25.000000000 +0000 @@ -12,11 +12,10 @@ "Chrome/72.0.3626.121 Safari/537.36" ) -CLUB_GENOME_ID = "fbd6791c-a6c6-4206-a75e-77234080b87b" -UBISOFT_APPID = "b8fde481-327d-4031-85ce-7c10a202a700" +CLUB_GENOME_ID = "85c31714-0941-4876-a18d-2c7e9dce8d40" CLUB_APPID = "314d4fef-e568-454a-ae06-43e3bece12a6" LOGIN_URL = ( - f"https://connect.ubisoft.com/login?appId={UBISOFT_APPID}&genomeId={CLUB_GENOME_ID}" + f"https://connect.ubisoft.com/login?appId={CLUB_APPID}&genomeId={CLUB_GENOME_ID}" "&lang=en-US&nextUrl=https:%2F%2Fconnect.ubisoft.com%2Fready" ) diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/wine/dxvk.py lutris-0.5.12~ubuntu22.04.1/lutris/util/wine/dxvk.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/wine/dxvk.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/wine/dxvk.py 2022-12-03 12:25:25.000000000 +0000 @@ -1,7 +1,13 @@ """DXVK helper module""" import os +import shutil +from lutris import api from lutris.settings import RUNTIME_DIR +from lutris.util.extract import extract_archive +from lutris.util.http import download_file +from lutris.util.log import logger +from lutris.util.system import create_folder, execute, remove_folder from lutris.util.wine.dll_manager import DLLManager @@ -34,3 +40,60 @@ except OSError: pass return False + + +def update_shader_cache(game): + state_cache_path = game.config.system_config["env"]["DXVK_STATE_CACHE_PATH"] + if not os.path.exists(state_cache_path): + logger.warning("%s is not a valid path", state_cache_path) + return False + game_details = api.get_game_details(game.slug) + if not game_details.get("shaders"): + logger.debug("No shaders for %s", game) + return False + last_updated_local = game.config.game_config.get("dxvk_cache_updated_at") + most_recent_update = None + shader_url = None + for shader in game_details["shaders"]: + if not most_recent_update or most_recent_update < shader["updated_at"]: + shader_url = shader["url"] + most_recent_update = shader["updated_at"] + if last_updated_local and last_updated_local >= most_recent_update: + logger.debug("Cache up to date") + return False + shader_merge_path = os.path.join(state_cache_path, "dxvk-state-cache") + create_folder(shader_merge_path) + shader_archive_path = os.path.join(shader_merge_path, os.path.basename(shader_url)) + download_file(shader_url, shader_archive_path) + extract_archive(shader_archive_path, to_directory=shader_merge_path) + try: + remote_cache_path = [ + shader_file for shader_file in os.listdir(shader_merge_path) + if shader_file.endswith(".dxvk-cache") + ][0] + except IndexError: + logger.error("Cache path not found") + return False + cache_file_name = os.path.basename(remote_cache_path) + local_cache_path = os.path.join(state_cache_path, cache_file_name) + if not os.path.exists(local_cache_path): + shutil.copy(remote_cache_path, state_cache_path) + else: + local_copy_path = os.path.join(shader_merge_path, "Local.dxvk-cache") + output_path = os.path.join(shader_merge_path, "output.dxvk-cache") + shutil.copy(local_cache_path, local_copy_path) + state_merge_tool_path = os.path.join(RUNTIME_DIR, "dxvk-cache-tool/dxvk_cache_tool") + if not os.path.exists(state_merge_tool_path): + logger.error("dxvk_cache_tool not present") + return False + execute([ + state_merge_tool_path, + remote_cache_path, + local_copy_path + ], cwd=shader_merge_path) + if not os.path.exists(output_path): + logger.error("Merging of shader failed") + shutil.copy(output_path, local_cache_path) + remove_folder(shader_merge_path) + game.config.game_level["game"]["dxvk_cache_updated_at"] = most_recent_update + game.config.save() diff -Nru lutris-0.5.11~ubuntu22.04.1/lutris/util/wine/extract_icon.py lutris-0.5.12~ubuntu22.04.1/lutris/util/wine/extract_icon.py --- lutris-0.5.11~ubuntu22.04.1/lutris/util/wine/extract_icon.py 1970-01-01 00:00:00.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/lutris/util/wine/extract_icon.py 2022-12-03 12:25:25.000000000 +0000 @@ -0,0 +1,143 @@ +# pylint: disable=no-member +import struct +from io import BytesIO + +try: + import pefile + PEFILE_AVAILABLE = True +except ImportError: + pefile = None + PEFILE_AVAILABLE = False + +from PIL import Image + +# From https://github.com/firodj/extract-icon-py + + +class ExtractIcon(object): + GRPICONDIRENTRY_format = ('GRPICONDIRENTRY', + ('B,Width', 'B,Height', 'B,ColorCount', 'B,Reserved', + 'H,Planes', 'H,BitCount', 'I,BytesInRes', 'H,ID')) + GRPICONDIR_format = ('GRPICONDIR', + ('H,Reserved', 'H,Type', 'H,Count')) + RES_ICON = 1 + RES_CURSOR = 2 + + def __init__(self, filepath): + self.pe = pefile.PE(filepath) + + def find_resource_base(self, res_type): + rt_base_idx = [entry.id for + entry in self.pe.DIRECTORY_ENTRY_RESOURCE.entries].index( + pefile.RESOURCE_TYPE[res_type] + ) + + if rt_base_idx is not None: + return self.pe.DIRECTORY_ENTRY_RESOURCE.entries[rt_base_idx] + + return None + + def find_resource(self, res_type, res_index): + rt_base_dir = self.find_resource_base(res_type) + + if res_index < 0: + try: + idx = [entry.id for entry in rt_base_dir.directory.entries].index(-res_index) + except: + return None + else: + idx = res_index if res_index < len(rt_base_dir.directory.entries) else None + + if idx is None: + return None + + test_res_dir = rt_base_dir.directory.entries[idx] + res_dir = test_res_dir + if test_res_dir.struct.DataIsDirectory: + # another Directory + # probably language take the first one + res_dir = test_res_dir.directory.entries[0] + if res_dir.struct.DataIsDirectory: + # Ooooooooooiconoo no !! another Directory !!! + return None + + return res_dir + + def get_group_icons(self): + rt_base_dir = self.find_resource_base('RT_GROUP_ICON') + groups = [] + for res_index in range(0, len(rt_base_dir.directory.entries)): + grp_icon_dir_entry = self.find_resource('RT_GROUP_ICON', res_index) + + if not grp_icon_dir_entry: + continue + + data_rva = grp_icon_dir_entry.data.struct.OffsetToData + size = grp_icon_dir_entry.data.struct.Size + data = self.pe.get_memory_mapped_image()[data_rva:data_rva + size] + file_offset = self.pe.get_offset_from_rva(data_rva) + + grp_icon_dir = pefile.Structure(self.GRPICONDIR_format, file_offset=file_offset) + grp_icon_dir.__unpack__(data) + + if grp_icon_dir.Reserved != 0 or grp_icon_dir.Type != self.RES_ICON: + continue + offset = grp_icon_dir.sizeof() + + entries = [] + for _idx in range(0, grp_icon_dir.Count): + grp_icon = pefile.Structure(self.GRPICONDIRENTRY_format, file_offset=file_offset + offset) + grp_icon.__unpack__(data[offset:]) + offset += grp_icon.sizeof() + entries.append(grp_icon) + + groups.append(entries) + return groups + + def get_icon(self, index): + icon_entry = self.find_resource('RT_ICON', -index) + if not icon_entry: + return None + + data_rva = icon_entry.data.struct.OffsetToData + size = icon_entry.data.struct.Size + data = self.pe.get_memory_mapped_image()[data_rva:data_rva + size] + + return data + + def export_raw(self, entries, index=None): + if index is not None: + entries = entries[index:index + 1] + + ico = struct.pack('not be " "duplicated." msgstr "" +"Möchten Sie %s duplizieren?\n" +"Die Konfiguration wird dupliziert, aber die Spieldateien werden nicht " +"dupliziert." #: lutris/game_actions.py:223 msgid "Duplicate game?" -msgstr "" +msgstr "Spiel duplizieren?" #: lutris/game_actions.py:284 msgid "This game has no installation directory" -msgstr "Dieses Spiel hat keinen Installationsverzeichnis" +msgstr "Dieses Spiel hat kein Installationsverzeichnis" #: lutris/game_actions.py:288 #, python-format @@ -350,7 +350,7 @@ #: lutris/game.py:191 #, python-format msgid "The path '%s' is not set. please set it in the options." -msgstr "Der Pfad '%s' is nicht gesetzt. Bitte setzte ihn in den Einstellungen." +msgstr "Der Pfad '%s' is nicht gesetzt. Bitte setzen Sie ihn in den Einstellungen." #: lutris/game.py:193 #, python-format @@ -358,10 +358,9 @@ msgstr "Unbehandelter Fehler: %s" #: lutris/game.py:298 -#, fuzzy msgid "Tried to launch a game that isn't installed." msgstr "" -"Es wurde versucht ein Spiel zu starten, was nicht installiert ist. (Warum?)" +"Es wurde versucht ein Spiel zu starten, das nicht installiert ist." #: lutris/game.py:300 lutris/game.py:417 msgid "Invalid game configuration: Missing runner" @@ -378,6 +377,8 @@ #: lutris/game.py:342 msgid "Unable to find Xephyr, install it or disable the Xephyr option" msgstr "" +"Konnte Xephyr nicht finden, installieren Sie es oder deaktivieren Sie " +"die Xephyr-Option" #: lutris/game.py:398 #, python-format @@ -462,7 +463,7 @@ #: lutris/gui/addgameswindow.py:126 msgid "Search Lutris.net" -msgstr "" +msgstr "Durchsuche Lutris.net" #: lutris/gui/addgameswindow.py:149 msgid "Select folder to scan" @@ -487,23 +488,20 @@ msgstr "Installationsdatei auswählen" #: lutris/gui/addgameswindow.py:245 -#, fuzzy msgid "Game name" -msgstr "Spielinfo" +msgstr "Spielname" #: lutris/gui/addgameswindow.py:249 msgid "Continue" msgstr "Fortfahren" #: lutris/gui/addgameswindow.py:257 -#, fuzzy msgid "Setup file" -msgstr "Datei auswählen" +msgstr "Installationsdatei" #: lutris/gui/addgameswindow.py:266 -#, fuzzy msgid "Select the setup file" -msgstr "Installationsdatei auswählen" +msgstr "Installationsdatei auswählen" #: lutris/gui/addgameswindow.py:279 msgid "Select a Lutris installer" @@ -519,7 +517,7 @@ #: lutris/gui/application.py:97 msgid "Your Linux distribution is too old. Lutris won't function properly." msgstr "" -"Ihre Linux-Verteilung ist zu alt. Lutris wird nicht ordnungsgemäß " +"Ihre Linux-Distribution ist zu alt. Lutris wird nicht ordnungsgemäß " "funktionieren." #: lutris/gui/application.py:102 @@ -551,7 +549,7 @@ #: lutris/gui/application.py:139 msgid "Generate a bash script to run a game without the client" -msgstr "Generiere einen Bash-Skript zum Ausführen des Spiels ohne Klient" +msgstr "Generiere ein Bash-Skript zum Ausführen des Spiels ohne Klient" #: lutris/gui/application.py:147 msgid "Execute a program with the Lutris Runtime" @@ -574,36 +572,32 @@ msgstr "Liste alle bekannten Steam-Bibliothek-Ordner" #: lutris/gui/application.py:187 -#, fuzzy msgid "List all known runners" -msgstr "Starter installieren" +msgstr "Liste alle bekannten Starter" #: lutris/gui/application.py:195 -#, fuzzy msgid "List all known Wine versions" -msgstr "Andere Version installieren" +msgstr "Liste alle bekannten Wine-Versionen" #: lutris/gui/application.py:203 -#, fuzzy msgid "Install a Runner" msgstr "Starter installieren" #: lutris/gui/application.py:211 -#, fuzzy msgid "Uninstall a Runner" -msgstr "Starter installieren" +msgstr "Starter deinstallieren" #: lutris/gui/application.py:219 msgid "Export a game" -msgstr "" +msgstr "Spiel exportieren" #: lutris/gui/application.py:227 msgid "Import a game" -msgstr "" +msgstr "Spiel importieren" #: lutris/gui/application.py:235 msgid "Destination path for export" -msgstr "" +msgstr "Zielpfad für Export" #: lutris/gui/application.py:243 msgid "Display the list of games in JSON format" @@ -611,7 +605,7 @@ #: lutris/gui/application.py:251 msgid "Reinstall game" -msgstr "Spiel neuinstallieren" +msgstr "Spiel neu installieren" #: lutris/gui/application.py:254 msgid "Submit an issue" @@ -649,12 +643,12 @@ #: lutris/gui/application.py:655 #, fuzzy msgid "No updates found" -msgstr "Kein Spiel gefunden" +msgstr "Keine Aktualisierung gefunden" #: lutris/gui/application.py:665 #, fuzzy msgid "No DLC found" -msgstr "Kein Spiel gefunden" +msgstr "Keinen herunterladbaren Inhalt gefunden" #: lutris/gui/config/add_game.py:11 msgid "Add a new game" @@ -666,7 +660,7 @@ #: lutris/gui/config/boxes.py:105 msgid "Reset option to global or default config" -msgstr "Optionen zu den allgemeinen oder vorgegebenen Werten zurücksetzen" +msgstr "Option auf globalen oder vorgegebenen Wert zurücksetzen" #: lutris/gui/config/boxes.py:126 msgid "Default: " @@ -721,29 +715,27 @@ "If modified, these options supersede the same options from the base runner " "configuration." msgstr "" -"Wenn hier Optionen modifiziert werden, ersetzen diese die selben Optionen " -"bei der Konfiguration des Basis-Starters." +"Bei Änderungen ersetzen diese Optionen die selben Optionen der Konfiguration " +"des Basis-Starters." #: lutris/gui/config/boxes.py:661 msgid "" "If modified, these options supersede the same options from the base runner " "configuration, which themselves supersede the global preferences." msgstr "" -"Wenn hier Optionen modifiziert werden, ersetzen diese die selben Optionen " -"bei der Konfiguration des Basis-Starters, der wiederum die allgemeinen " -"Einstellungen ersetzt." +"Bei Änderungen ersetzen diese Optionen die selben Optionen der Konfiguration " +"des Basis-Starters, der wiederum die globalen Einstellungen ersetzt." #: lutris/gui/config/boxes.py:667 msgid "" "If modified, these options supersede the same options from the global " "preferences." msgstr "" -"Wenn hier Optionen modifiziert werden, ersetzten diese die selben Optionen " -"bei den allgemeinen Einstellungen." +"Bei Änderungen ersetzen diese Optionen die globalen Einstellungen." #: lutris/gui/config/common.py:28 msgid "Select a runner in the Game Info tab" -msgstr "Wähle im Spielinforeiter ein Starter aus" +msgstr "Wähle im Reiter Spielinfo einen Starter aus" #: lutris/gui/config/common.py:109 msgid "Game info" @@ -886,7 +878,7 @@ #: lutris/gui/config/preferences_dialog.py:18 msgid "Lutris settings" -msgstr "Lutriseinstellungen" +msgstr "Lutris-Einstellungen" #: lutris/gui/config/preferences_dialog.py:26 msgid "Interface" @@ -916,7 +908,7 @@ #: lutris/gui/config/runner_box.py:121 #, python-format msgid "Do you want to uninstall %s?" -msgstr "Wollen Sie %s deinstallieren?" +msgstr "Möchten Sie %s deinstallieren?" #: lutris/gui/config/runner_box.py:122 #, python-format @@ -937,7 +929,7 @@ #: lutris/gui/config/services_box.py:18 msgid "Enable integrations with game sources" -msgstr "Aktiviere die Integrierungen mit Spielequellen" +msgstr "Aktiviere Integrationen mit Spielequellen" #: lutris/gui/config/services_box.py:20 msgid "" @@ -945,7 +937,7 @@ "to take effect." msgstr "" "Greifen Sie auf Ihre Bibliotheken aus verschiedenen Quellen zu. Änderungen " -"benötigen einen Neustart um wirksam zu werden." +"benötigen einen Neustart, um wirksam zu werden." #: lutris/gui/config/sysinfo_box.py:12 msgid "Hide text under icons" @@ -995,7 +987,7 @@ #: lutris/gui/dialogs/download.py:15 #, python-format msgid "Downloading %s" -msgstr "Herunterladen %s" +msgstr "Lade %s herunter" #: lutris/gui/dialogs/__init__.py:133 lutris/gui/dialogs/__init__.py:160 #: lutris/gui/dialogs/issue.py:74 lutris/gui/dialogs/runner_install.py:63 @@ -1022,7 +1014,7 @@ #: lutris/gui/dialogs/__init__.py:225 msgid "Install the game again" -msgstr "Spiel noch einmal installieren" +msgstr "Spiel erneut installieren" #: lutris/gui/dialogs/__init__.py:229 lutris/gui/dialogs/__init__.py:272 #: lutris/gui/dialogs/__init__.py:360 @@ -1108,11 +1100,11 @@ #: lutris/gui/dialogs/runner_install.py:87 #, python-format msgid "Unable to get runner versions: %s" -msgstr "Nicht in der Lage die Version des Starters zu ermitteln: %s" +msgstr "Version des Starters konnte nicht ermittelt werden: %s" #: lutris/gui/dialogs/runner_install.py:101 msgid "Unable to get runner versions from lutris.net" -msgstr "Nicht in der Lage die Version des Starters auf lutris.net zu ermitteln" +msgstr "Version des Starters auf lutris.net nicht gefunden" #: lutris/gui/dialogs/runner_install.py:108 #, python-format @@ -1132,9 +1124,8 @@ msgstr "Deinstallieren" #: lutris/gui/dialogs/runner_install.py:214 -#, fuzzy msgid "Wine version usage" -msgstr "Wine-Version" +msgstr "Genutzte Wine-Version" #: lutris/gui/dialogs/runner_install.py:256 msgid "Do you want to cancel the download?" @@ -1159,7 +1150,7 @@ #: lutris/gui/dialogs/runner_install.py:377 msgid "Failed to retrieve the runner archive" -msgstr "Das Abrufen des Archives von Startern ist fehlgeschlagen" +msgstr "Abruf des Starter-Archivs fehlgeschlagen" #: lutris/gui/dialogs/uninstall_game.py:27 #, python-format @@ -1177,7 +1168,7 @@ #: lutris/gui/dialogs/uninstall_game.py:44 msgid "Calculating size…" -msgstr "Größe berechnen..." +msgstr "Größe berechnen…" #: lutris/gui/dialogs/uninstall_game.py:48 #, python-format @@ -1223,8 +1214,8 @@ "Completely remove %s from the library?\n" "All play time will be lost." msgstr "" -"%s vollständig von der Bibliothek entfernen?\n" -"Die ganze Spielzeit wird verloren gehen." +"%s vollständig aus der Bibliothek entfernen?\n" +"Die Spielzeit wird verloren gehen." #: lutris/gui/dialogs/webconnect_dialog.py:106 msgid "Loading..." @@ -1351,11 +1342,14 @@ "Select which one you want and they will be available in the 'extras' folder " "where the game is installed." msgstr "" +"Zu diesem Spiel gibt es Zusatzinhalte. \n" +"Wähle die Inhalte aus, die dann im Verzeichnis 'extras' im Installations-" +"Pfad des Spiels abgelegt werden." #: lutris/gui/installerwindow.py:459 #, python-format msgid "Unable to get files: %s" -msgstr "Nicht in der Lage die Dateien zu erhalten: %s" +msgstr "Konnte Spieldateien nicht abrufen: %s" #: lutris/gui/installerwindow.py:554 msgid "Remove game files" @@ -1372,7 +1366,7 @@ #: lutris/gui/lutriswindow.py:345 #, python-format msgid "Connect your %s account to access your games" -msgstr "Verbinden Sie Ihren %s-Konto für den Zugriff auf Ihre Spiele" +msgstr "Verbinden Sie Ihr %s-Konto für den Zugriff auf Ihre Spiele" #: lutris/gui/lutriswindow.py:416 #, python-format @@ -1386,7 +1380,7 @@ #: lutris/gui/lutriswindow.py:421 msgid "No installed games found. Press Ctrl+H to show all games." msgstr "" -"Keine installierte Spiele gefunden. Drücke Strg+H damit alle gezeigt werden." +"Keine installierte Spiele gefunden. Drücke Strg+H, um alle anzuzeigen." #: lutris/gui/lutriswindow.py:430 msgid "No games found" @@ -1482,7 +1476,7 @@ #: lutris/gui/widgets/sidebar.py:128 lutris/gui/widgets/sidebar.py:161 msgid "Reload" -msgstr "Neuladen" +msgstr "Neu laden" #: lutris/gui/widgets/sidebar.py:160 lutris/gui/widgets/sidebar.py:204 msgid "Run" @@ -1873,7 +1867,7 @@ #: lutris/runners/dolphin.py:50 msgid "Custom Global User Directory" -msgstr "" +msgstr "Benutzerdefiniertes globales Benutzerverzeichnis" #: lutris/runners/dosbox.py:13 msgid "DOSBox" @@ -3798,7 +3792,7 @@ #: lutris/runners/scummvm.py:12 msgid "Engine for point-and-click games." -msgstr "Engine für Point-und-Klick-Spiele." +msgstr "Engine für Point-and-Click-Spiele." #: lutris/runners/scummvm.py:13 lutris/services/scummvm.py:10 msgid "ScummVM" @@ -3836,7 +3830,7 @@ #: lutris/runners/scummvm.py:161 msgid "Filtering" -msgstr "" +msgstr "Filter" #: lutris/runners/scummvm.py:163 msgid "" @@ -3860,7 +3854,7 @@ #: lutris/runners/scummvm.py:187 msgid "Enables joystick input (default: 0 = first joystick)" -msgstr "" +msgstr "Aktiviert Joystick-Eingaben (Standard: 0 = erster Joystick)" #: lutris/runners/scummvm.py:193 #, fuzzy @@ -3871,11 +3865,12 @@ msgid "" "Selects language (en, de, fr, it, pt, es, jp, zh, kr, se, gb, hb, ru, cz)" msgstr "" +"Wählt die Sprache (en, de, fr, it, pt, es, jp, zh, kr, se, gb, hb, ru, cz)" #: lutris/runners/scummvm.py:200 #, fuzzy msgid "Engine speed" -msgstr "Engine" +msgstr "Engine-Geschwindigkeit" #: lutris/runners/scummvm.py:201 msgid "" @@ -3885,15 +3880,15 @@ #: lutris/runners/scummvm.py:208 msgid "Talk speed" -msgstr "" +msgstr "Dialog-Geschwindigkeit" #: lutris/runners/scummvm.py:209 msgid "Sets talk speed for games (default: 60)" -msgstr "" +msgstr "Legt die Sprachgeschwindigkeit für Spiele fest (Standard: 60)" #: lutris/runners/scummvm.py:215 msgid "Music tempo" -msgstr "" +msgstr "Musik-Geschwindigkeit" #: lutris/runners/scummvm.py:216 msgid "Sets music tempo (in percent, 50-200) for SCUMM games (default: 100)" @@ -3918,7 +3913,7 @@ #: lutris/runners/scummvm.py:249 msgid "Output rate" -msgstr "" +msgstr "Ausgabefrequenz" #: lutris/runners/scummvm.py:256 msgid "Selects output sample rate in Hz." @@ -3937,7 +3932,7 @@ #: lutris/runners/scummvm.py:280 msgid "Music volume" -msgstr "" +msgstr "Lautstärke Musik" #: lutris/runners/scummvm.py:281 msgid "Sets the music volume, 0-255 (default: 192)" @@ -3945,7 +3940,7 @@ #: lutris/runners/scummvm.py:287 msgid "SFX volume" -msgstr "" +msgstr "Lautstärke Effekte" #: lutris/runners/scummvm.py:288 msgid "Sets the sfx volume, 0-255 (default: 192)" @@ -3953,7 +3948,7 @@ #: lutris/runners/scummvm.py:294 msgid "Speech volume" -msgstr "" +msgstr "Lautstärke Sprache" #: lutris/runners/scummvm.py:295 msgid "Sets the speech volume, 0-255 (default: 192)" @@ -4006,24 +4001,23 @@ #: lutris/runners/scummvm.py:341 msgid "Use alternate intro" -msgstr "" +msgstr "Alternatives Intro" #: lutris/runners/scummvm.py:342 msgid "Uses alternative intro for CD versions" -msgstr "" +msgstr "Verwendet ein alternatives Intro für CD-Versionen" #: lutris/runners/scummvm.py:348 msgid "Copy protection" -msgstr "" +msgstr "Kopierschutz" #: lutris/runners/scummvm.py:349 msgid "Enables copy protection" -msgstr "" +msgstr "Aktiviert Kopierschutz" #: lutris/runners/scummvm.py:355 -#, fuzzy msgid "Demo mode" -msgstr "Amiga-Modell" +msgstr "Demo-Modus" #: lutris/runners/scummvm.py:356 msgid "Starts demo mode of Maniac Mansion or The 7th Guest" diff -Nru lutris-0.5.11~ubuntu22.04.1/po/hr.po lutris-0.5.12~ubuntu22.04.1/po/hr.po --- lutris-0.5.11~ubuntu22.04.1/po/hr.po 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/po/hr.po 2022-12-03 12:25:25.000000000 +0000 @@ -3,16 +3,16 @@ "Project-Id-Version: lutris\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-06-20 16:54+0200\n" -"PO-Revision-Date: 2021-12-14 14:33+0100\n" +"PO-Revision-Date: 2022-08-30 19:37+0200\n" "Last-Translator: gogo \n" "Language-Team: \n" "Language: hr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.3\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 3.0.1\n" #: share/applications/net.lutris.Lutris.desktop:3 #: share/lutris/ui/lutris-window.ui:25 lutris/gui/application.py:76 @@ -164,9 +164,8 @@ msgstr "Prikaži bočni _panel" #: share/lutris/ui/lutris-window.ui:500 -#, fuzzy msgid "Add games" -msgstr "Dodaj igru" +msgstr "Dodaj igre" #: share/lutris/ui/lutris-window.ui:514 msgid "Preferences" @@ -211,9 +210,8 @@ msgstr "Instaliraj" #: lutris/game_actions.py:63 -#, fuzzy msgid "Install updates" -msgstr "Instaliraj pokretače" +msgstr "Instaliraj nadopune" #: lutris/game_actions.py:65 msgid "Show logs" @@ -225,7 +223,7 @@ #: lutris/game_actions.py:67 msgid "Duplicate" -msgstr "" +msgstr "Duplikat" #: lutris/game_actions.py:68 lutris/gui/widgets/sidebar.py:205 msgid "Configure" @@ -264,14 +262,12 @@ msgstr "Obriši prečac izbornika aplikacija" #: lutris/game_actions.py:95 lutris/gui/installerwindow.py:187 -#, fuzzy msgid "Create steam shortcut" -msgstr "Stvori prečac radne površine" +msgstr "Stvori steam prečac" #: lutris/game_actions.py:100 -#, fuzzy msgid "Delete steam shortcut" -msgstr "Obriši prečac radne površine" +msgstr "Obriši steam prečac" #: lutris/game_actions.py:103 msgid "Install another version" @@ -304,10 +300,12 @@ "The configuration will be duplicated, but the games files will not be " "duplicated." msgstr "" +"Želite li stvoriti duplikat %s?\n" +"Podešavanje će biti duplikat, ali datoteke igre neće biti duplicirane." #: lutris/game_actions.py:223 msgid "Duplicate game?" -msgstr "" +msgstr "Stvori duplikat igre?" #: lutris/game_actions.py:284 msgid "This game has no installation directory" @@ -358,7 +356,7 @@ #: lutris/game.py:298 msgid "Tried to launch a game that isn't installed." -msgstr "" +msgstr "Pokušaj pokretanja igre koja nije instalirana." #: lutris/game.py:300 lutris/game.py:417 msgid "Invalid game configuration: Missing runner" @@ -375,6 +373,7 @@ #: lutris/game.py:342 msgid "Unable to find Xephyr, install it or disable the Xephyr option" msgstr "" +"Neuspjeli Xephyr pronalazak, instalirajte ili onemogućite Xephyr mogućnost" #: lutris/game.py:398 #, python-format @@ -388,7 +387,7 @@ #. The 'file' sort of gameplay_info cannot be made to use a configuration #: lutris/game.py:434 msgid "The runner could not find a command to apply the configuration to." -msgstr "" +msgstr "Pokretač nije mogao pronaći naredbu za primjenjivanje podešavanja." #: lutris/game.py:635 msgid "Error lauching the game:\n" @@ -412,103 +411,95 @@ #: lutris/gui/addgameswindow.py:21 msgid "Search the Lutris website for installers" -msgstr "" +msgstr "Pretražite instalacijske programe na Lutris web stranici" #: lutris/gui/addgameswindow.py:22 msgid "Query our website for community installers" msgstr "" +"Provjerite našu web stranicu za instalacijskim programima pružanih od strane " +"zajednice" #: lutris/gui/addgameswindow.py:27 msgid "Scan a folder for games" -msgstr "" +msgstr "Pretraži igre u mapi" #: lutris/gui/addgameswindow.py:28 msgid "Mass-import a folder of games" -msgstr "" +msgstr "Masovni uvoz mape igara" #: lutris/gui/addgameswindow.py:33 -#, fuzzy msgid "Install a Windows game from media" -msgstr "Instaliraj igru iz yml datoteke" +msgstr "Instaliraj Windows igru iz medija" #: lutris/gui/addgameswindow.py:34 msgid "Launch a setup file from an optical drive or download" -msgstr "" +msgstr "Pokreni datoteku instalacije s optičkog uređaja ili preuzetih datoteka" #: lutris/gui/addgameswindow.py:39 msgid "Install from a local install script" -msgstr "" +msgstr "Instaliraj s lokalnom skriptom instalacije" #: lutris/gui/addgameswindow.py:40 msgid "Run a YAML install script" -msgstr "" +msgstr "Pokreni YAML instalacijsku skriptu" #: lutris/gui/addgameswindow.py:45 -#, fuzzy msgid "Add locally installed game" -msgstr "Dodaj instaliranu igru" +msgstr "Dodaj lokalno instaliranu igru" #: lutris/gui/addgameswindow.py:46 msgid "Manually configure a game available locally" -msgstr "" +msgstr "Ručno podesi lokalno dostupnu igru" #: lutris/gui/addgameswindow.py:51 msgid "Add games to Lutris" -msgstr "" +msgstr "Dodaj igre u Lutris" #: lutris/gui/addgameswindow.py:126 -#, fuzzy msgid "Search Lutris.net" -msgstr "Pretraži Lutris.net" +msgstr "Pretraži Lutris.net" #: lutris/gui/addgameswindow.py:149 -#, fuzzy msgid "Select folder to scan" -msgstr "Odaberi mapu" +msgstr "Odaberi mapu za pretragu" #: lutris/gui/addgameswindow.py:223 msgid "No results" -msgstr "" +msgstr "Nema rezultata" #: lutris/gui/addgameswindow.py:225 #, python-brace-format msgid "Showing {count} results" -msgstr "" +msgstr "Prikazuje se {count} rezultata" #: lutris/gui/addgameswindow.py:227 #, python-brace-format msgid "{total_count} results, only displaying first {count}" -msgstr "" +msgstr "{total_count} rezultata, prikazuje se samo prvih {count}" #: lutris/gui/addgameswindow.py:243 -#, fuzzy msgid "Select setup file" -msgstr "Odaberi datoteku" +msgstr "Odaberi datoteku instalacije" #: lutris/gui/addgameswindow.py:245 -#, fuzzy msgid "Game name" -msgstr "Informacije igre" +msgstr "Naslov igre" #: lutris/gui/addgameswindow.py:249 -#, fuzzy msgid "Continue" -msgstr "_Nastavi" +msgstr "Nastavi" #: lutris/gui/addgameswindow.py:257 -#, fuzzy msgid "Setup file" -msgstr "Odaberi datoteku" +msgstr "Datoteka instalacije" #: lutris/gui/addgameswindow.py:266 -#, fuzzy msgid "Select the setup file" -msgstr "Odaberi datoteku licence" +msgstr "Odaberi datoteku instalacije" #: lutris/gui/addgameswindow.py:279 -#, fuzzy msgid "Select a Lutris installer" -msgstr "Odaberi datoteku licence" +msgstr "Odaberi Lutris instalacijski program" #: lutris/gui/application.py:87 msgid "" @@ -574,38 +565,32 @@ msgstr "Prikaži sve poznate mape Steam biblioteke" #: lutris/gui/application.py:187 -#, fuzzy msgid "List all known runners" -msgstr "Instaliraj pokretače" +msgstr "Prikaži sve poznate pokretače" #: lutris/gui/application.py:195 -#, fuzzy msgid "List all known Wine versions" -msgstr "Instaliraj drugu inačicu" +msgstr "Prikaži sve poznate Wine inačice" #: lutris/gui/application.py:203 -#, fuzzy msgid "Install a Runner" msgstr "Instaliraj pokretač" #: lutris/gui/application.py:211 -#, fuzzy msgid "Uninstall a Runner" -msgstr "Instaliraj pokretač" +msgstr "Deinstaliraj pokretač" #: lutris/gui/application.py:219 -#, fuzzy msgid "Export a game" -msgstr "Uvezi igre" +msgstr "Izvezi igru" #: lutris/gui/application.py:227 -#, fuzzy msgid "Import a game" -msgstr "Uvezi igre" +msgstr "Uvezi igru" #: lutris/gui/application.py:235 msgid "Destination path for export" -msgstr "" +msgstr "Putanja za izvoz" #: lutris/gui/application.py:243 msgid "Display the list of games in JSON format" @@ -644,19 +629,17 @@ msgstr "Nema takve datoteke: %s" #: lutris/gui/application.py:645 -#, fuzzy, python-format +#, python-format msgid "There is no installer available for %s." -msgstr "Nema dostupnog instalacijskog programa za ovu igru" +msgstr "Nema dostupnog instalacijskog programa za %s." #: lutris/gui/application.py:655 -#, fuzzy msgid "No updates found" -msgstr "Nema pronađenih igara" +msgstr "Nema pronađenih nadopuna" #: lutris/gui/application.py:665 -#, fuzzy msgid "No DLC found" -msgstr "Nema pronađenih igara" +msgstr "Nema pronađenog DLC-a" #: lutris/gui/config/add_game.py:11 msgid "Add a new game" @@ -840,7 +823,6 @@ msgstr "Navedite naziv" #: lutris/gui/config/common.py:426 -#, fuzzy msgid "Steam AppID not provided" msgstr "Steam AppId nije naveden" @@ -1031,7 +1013,7 @@ #: lutris/gui/dialogs/__init__.py:248 msgid "Select game to launch" -msgstr "" +msgstr "Odaberite igru za pokretanje" #: lutris/gui/dialogs/__init__.py:333 msgid "Login failed" @@ -1098,7 +1080,7 @@ #: lutris/gui/dialogs/runner_install.py:29 #, python-format msgid "Showing games using %s" -msgstr "" +msgstr "Prikazuju se igre koristeći %s" #: lutris/gui/dialogs/runner_install.py:71 #, python-format @@ -1120,12 +1102,12 @@ msgstr "%s inačica upravljanja" #: lutris/gui/dialogs/runner_install.py:158 -#, fuzzy, python-format +#, python-format msgid "_View %d game" msgid_plural "_View %d games" -msgstr[0] "Nova igra" -msgstr[1] "Nova igra" -msgstr[2] "Nova igra" +msgstr[0] "_Pogledaj %d igru" +msgstr[1] "_Pogledaj %d igre" +msgstr[2] "_Pogledaj %d igra" #: lutris/gui/dialogs/runner_install.py:190 #: lutris/gui/dialogs/uninstall_game.py:34 @@ -1133,9 +1115,8 @@ msgstr "Deinstaliraj" #: lutris/gui/dialogs/runner_install.py:214 -#, fuzzy msgid "Wine version usage" -msgstr "Wine inačica" +msgstr "Wine inačica koja se koristi" #: lutris/gui/dialogs/runner_install.py:256 msgid "Do you want to cancel the download?" @@ -1148,7 +1129,7 @@ #: lutris/gui/dialogs/runner_install.py:307 #, python-format msgid "Version %s is not longer available" -msgstr "" +msgstr "Inačica %s više nije dostupna" #: lutris/gui/dialogs/runner_install.py:332 msgid "Downloading…" @@ -1246,7 +1227,6 @@ msgstr "Preuzmi" #: lutris/gui/installer/file_box.py:123 -#, fuzzy msgid "Use Cache" msgstr "Koristi predmemoriju" @@ -1256,7 +1236,6 @@ msgstr "Steam" #: lutris/gui/installer/file_box.py:126 -#, fuzzy msgid "Select File" msgstr "Odaberi datoteku" @@ -1350,6 +1329,9 @@ "Select which one you want and they will be available in the 'extras' folder " "where the game is installed." msgstr "" +"Ova igra ima dodatan sadržaj. \n" +"Odaberite željeni sadržaj i biti će dostupan u 'extras' mapi gdje je igra " +"instalirana." #: lutris/gui/installerwindow.py:459 #, python-format @@ -1694,7 +1676,7 @@ #: lutris/installer/interpreter.py:139 msgid "You need to install {} before" -msgstr "" +msgstr "Morate prije instalirati {}" #: lutris/installer/interpreter.py:187 msgid "Lutris does not have the necessary permissions to install to path:" @@ -1867,7 +1849,7 @@ #: lutris/runners/dolphin.py:50 msgid "Custom Global User Directory" -msgstr "" +msgstr "Prilagođeni globalni korisnički direktorij" #: lutris/runners/dosbox.py:13 msgid "DOSBox" @@ -3356,9 +3338,8 @@ msgstr "O2EM" #: lutris/runners/o2em.py:12 -#, fuzzy msgid "Magnavox Odyssey² Emulator" -msgstr "Magnavox Osyssey² emulator" +msgstr "Magnavox Odyssey² emulator" #: lutris/runners/o2em.py:14 lutris/runners/o2em.py:30 msgid "Magnavox Odyssey²" @@ -3767,18 +3748,16 @@ msgstr "Datoteka koja sadrži naslovni ključ." #: lutris/runners/scummvm.py:12 -#, fuzzy msgid "Engine for point-and-click games." -msgstr "Pokreće razne 2D usmjeri i klikni avanturističke igre." +msgstr "Pokreće točka-i-klik igre." #: lutris/runners/scummvm.py:13 lutris/services/scummvm.py:10 msgid "ScummVM" msgstr "ScummVM" #: lutris/runners/scummvm.py:82 -#, fuzzy msgid "Enable subtitles" -msgstr "Omogući zvuk" +msgstr "Omogući podnaslove" #: lutris/runners/scummvm.py:89 msgid "Aspect ratio correction" @@ -3795,25 +3774,24 @@ "4:3 omjer slike za koji su napravljene." #: lutris/runners/scummvm.py:140 -#, fuzzy msgid "Render mode" -msgstr "Prikazivatelj" +msgstr "Način prikaza" #: lutris/runners/scummvm.py:157 -#, fuzzy msgid "Changes how the game is rendered." -msgstr "Pokreni igru preko cijelog zaslona." +msgstr "Mijenja način prikaza igre." #: lutris/runners/scummvm.py:161 -#, fuzzy msgid "Filtering" -msgstr "Filter:" +msgstr "Filtriranje" #: lutris/runners/scummvm.py:163 msgid "" "Uses bilinear interpolation instead of nearest neighbor resampling for the " "aspect ratio correction and stretch mode." msgstr "" +"Koristi bilinearnu interpolaciju umjesto uzrokovanja najbližeg susjeda za " +"ispravljanje omjera slike i načina rastezanja." #: lutris/runners/scummvm.py:170 msgid "Data directory" @@ -3828,193 +3806,199 @@ "Specifes platform of game. Allowed values: 2gs, 3do, acorn, amiga, atari, " "c64, fmtowns, nes, mac, pc pc98, pce, segacd, wii, windows" msgstr "" +"Određuje platformu igre. Dopuštene vrijednosti: 2gs, 3do, acorn, amiga, " +"atari, c64, fmtowns, nes, mac, pc pc98, pce, segacd, wii, windows" #: lutris/runners/scummvm.py:187 msgid "Enables joystick input (default: 0 = first joystick)" -msgstr "" +msgstr "Omogućuje ulaz upravljača igre (zadano: 0 = prvi upravljač igre)" #: lutris/runners/scummvm.py:193 -#, fuzzy msgid "Language" -msgstr "Jezik sustava" +msgstr "Jezik" #: lutris/runners/scummvm.py:194 msgid "" "Selects language (en, de, fr, it, pt, es, jp, zh, kr, se, gb, hb, ru, cz)" -msgstr "" +msgstr "Odabir jezika (en, de, fr, it, pt, es, jp, zh, kr, se, gb, hb, ru, cz)" #: lutris/runners/scummvm.py:200 -#, fuzzy msgid "Engine speed" -msgstr "Pogon" +msgstr "Brzina pogona" #: lutris/runners/scummvm.py:201 msgid "" "Sets frames per second limit (0 - 100) for Grim Fandango or Escape from " "Monkey Island (default: 60)." msgstr "" +"Postavlja sličica u sekundi (0 - 100) za Grim Fandango ili Escape from " +"Monkey Island (zadano: 60)." #: lutris/runners/scummvm.py:208 msgid "Talk speed" -msgstr "" +msgstr "Brzina govora" #: lutris/runners/scummvm.py:209 msgid "Sets talk speed for games (default: 60)" -msgstr "" +msgstr "Postavlja brzinu govora (zadano: 60)" #: lutris/runners/scummvm.py:215 msgid "Music tempo" -msgstr "" +msgstr "Tempo glazbe" #: lutris/runners/scummvm.py:216 msgid "Sets music tempo (in percent, 50-200) for SCUMM games (default: 100)" msgstr "" +"Postavlja tempo glazbe (u postocima, 50-200) za SCUMM igre (zadano: 100)" #: lutris/runners/scummvm.py:222 msgid "Digital iMuse tempo" -msgstr "" +msgstr "Digital iMuse tempo" #: lutris/runners/scummvm.py:223 msgid "Sets internal Digital iMuse tempo (10 - 100) per second (default: 10)" msgstr "" +"Postavlja unutarnji Digital iMuse tempo (10 - 100) po sekundi (zadano: 10)" #: lutris/runners/scummvm.py:228 -#, fuzzy msgid "Music driver" msgstr "Upravljački program zvuka" #: lutris/runners/scummvm.py:244 msgid "Specifies the device ScummVM uses to output audio." -msgstr "" +msgstr "Određuje koji izlaz zvuka ScummVM uređaj." #: lutris/runners/scummvm.py:249 msgid "Output rate" -msgstr "" +msgstr "Izlazna frekvencija" #: lutris/runners/scummvm.py:256 msgid "Selects output sample rate in Hz." -msgstr "" +msgstr "Odabire izlaznu frekvenciju u Hz." #: lutris/runners/scummvm.py:261 -#, fuzzy msgid "OPL driver" -msgstr "Upravljački program zvuka" +msgstr "OPL upravljački program" #: lutris/runners/scummvm.py:273 msgid "" "Chooses which emulator is used by ScummVM when the AdLib emulator is chosen " "as the Preferred device." msgstr "" +"Odabire koji emulator koristi ScummVM kada je AdLib emulator odabran kao " +"željeni uređaj." #: lutris/runners/scummvm.py:280 msgid "Music volume" -msgstr "" +msgstr "Glasnoća zvuka igre" #: lutris/runners/scummvm.py:281 msgid "Sets the music volume, 0-255 (default: 192)" -msgstr "" +msgstr "Postavlja glasnoću zvuka glazbe, 0-255 (zadano: 192)" #: lutris/runners/scummvm.py:287 msgid "SFX volume" -msgstr "" +msgstr "SFX glasnoća zvuka" #: lutris/runners/scummvm.py:288 msgid "Sets the sfx volume, 0-255 (default: 192)" -msgstr "" +msgstr "Postavlja sfx glasnoću zvuka, 0-255 (zadano: 192)" #: lutris/runners/scummvm.py:294 msgid "Speech volume" -msgstr "" +msgstr "Glasnoća zvuka govora" #: lutris/runners/scummvm.py:295 msgid "Sets the speech volume, 0-255 (default: 192)" -msgstr "" +msgstr "Postavlja glasnoću zvuka govora, 0-255 (zadano: 192)" #: lutris/runners/scummvm.py:301 msgid "MIDI gain" -msgstr "" +msgstr "MIDI pojačanje" #: lutris/runners/scummvm.py:302 msgid "Sets the gain for MIDI playback. 0-1000 (default: 100)" -msgstr "" +msgstr "Postavlja pojačanje za MIDI reprodukciju. 0-1000 (zadano: 100)" #: lutris/runners/scummvm.py:308 msgid "Soundfont" -msgstr "" +msgstr "Soundfont" #: lutris/runners/scummvm.py:309 msgid "Specifies the path to a soundfont file." -msgstr "" +msgstr "Određuje putanju do soundfont datoteke." #: lutris/runners/scummvm.py:314 msgid "Mixed AdLib/MIDI mode" -msgstr "" +msgstr "Mješoviti AdLib/MIDI naćin" #: lutris/runners/scummvm.py:317 msgid "Combines MIDI music with AdLib sound effects." -msgstr "" +msgstr "Kombinira MIDI glazbu sa AdLib zvučnim efektima." #: lutris/runners/scummvm.py:322 msgid "True Roland MT-32" -msgstr "" +msgstr "Stvarni Roland MT-32" #: lutris/runners/scummvm.py:325 msgid "" "Tells ScummVM that the MIDI device is an actual Roland MT-32, LAPC-I, CM-64, " "CM-32L, CM-500 or other MT-32 device." msgstr "" +"Govori ScummVM da je MIDI uređaj ustvari Roland MT-32, LAPC-I, CM-64, " +"CM-32L, CM-500 ili drugi MT-32 uređaj." #: lutris/runners/scummvm.py:331 -#, fuzzy msgid "Enable Roland GS" -msgstr "Omogući zvuk" +msgstr "Omogući Roland GS" #: lutris/runners/scummvm.py:334 msgid "" "Tells ScummVM that the MIDI device is a GS device that has an MT-32 map, " "such as an SC-55, SC-88 or SC-8820." msgstr "" +"Govori ScummVM da MIDI uređaj je GS uređaj koji ima MT-32 mapiranje, poput " +"SC-55, SC-88 ili SC-8820." #: lutris/runners/scummvm.py:341 msgid "Use alternate intro" -msgstr "" +msgstr "Koristi zamjenski uvod" #: lutris/runners/scummvm.py:342 msgid "Uses alternative intro for CD versions" -msgstr "" +msgstr "Koristi zamjenski uvod za CD izdanja" #: lutris/runners/scummvm.py:348 msgid "Copy protection" -msgstr "" +msgstr "Zaštita od kopiranja" #: lutris/runners/scummvm.py:349 msgid "Enables copy protection" -msgstr "" +msgstr "Omogućuje zaštitu od kopiranja" #: lutris/runners/scummvm.py:355 -#, fuzzy msgid "Demo mode" -msgstr "Amiga model" +msgstr "Demo način rada" #: lutris/runners/scummvm.py:356 msgid "Starts demo mode of Maniac Mansion or The 7th Guest" -msgstr "" +msgstr "Pokreće demo način rada za Maniac Mansion ili The 7th Guest" #: lutris/runners/scummvm.py:362 msgid "Debug level" -msgstr "" +msgstr "Razina otklanjanja grešaka" #: lutris/runners/scummvm.py:363 msgid "Sets debug verbosity level" -msgstr "" +msgstr "Postavlja opširnost otklanjanja grešaka" #: lutris/runners/scummvm.py:369 msgid "Debug flags" -msgstr "" +msgstr "Oznake otklanjanja grešaka" #: lutris/runners/scummvm.py:370 msgid "Enables engine specific debug flags" -msgstr "" +msgstr "Omogućuje specifične oznake otklanjanja grešaka pogona" #: lutris/runners/snes9x.py:17 msgid "Super Nintendo emulator" @@ -4565,7 +4549,6 @@ msgstr "Omogući Fsync" #: lutris/runners/wine.py:317 -#, fuzzy msgid "" "Enable futex-based synchronization (fsync). This will increase performance " "in applications that take advantage of multi-core processors. Requires " @@ -4573,7 +4556,7 @@ msgstr "" "Omogući futex-temeljeno usklađivanje (fsync). To će povećati performanse u " "aplikacijama koje koriste prednosti višejezgrenih procesora. Zahtijeva " -"prilagođeni kernel sa fsync zakrpama." +"prilagođeni kernel 5.16 ili noviji." #: lutris/runners/wine.py:325 msgid "Enable AMD FidelityFX Super Resolution (FSR)" @@ -4606,18 +4589,16 @@ "izgradnju.\n" #: lutris/runners/wine.py:346 -#, fuzzy msgid "Enable Easy Anti-Cheat" -msgstr "Omogući BattlEye Anti-Cheat" +msgstr "Omogući Easy Anti-Cheat" #: lutris/runners/wine.py:350 -#, fuzzy msgid "" "Enable support for Easy Anti-Cheat in supported games\n" "Requires Lutris Wine 7.2 and newer or any other compatible Wine build.\n" msgstr "" -"Omogući podršku za BattlEye Anti-Cheat u podržanim igrama\n" -"Zahtijeva Lutris Wine 6.21-2 i noviji ili bilo koju drugu kompatibilnu Wine " +"Omogući podršku za Easy Anti-Cheat u podržanim igrama\n" +"Zahtijeva Lutris Wine 7.2 i noviji ili bilo koju drugu kompatibilnu Wine " "izgradnju.\n" #: lutris/runners/wine.py:356 @@ -4643,9 +4624,8 @@ msgstr "Veličina virtualne radne površine u pikselima." #: lutris/runners/wine.py:374 -#, fuzzy msgid "Enable DPI Scaling" -msgstr "Omogući DX9 u DXVK" +msgstr "Omogući DPI promjenu veličine" #: lutris/runners/wine.py:378 msgid "" @@ -4653,16 +4633,21 @@ "Otherwise, the Screen Resolution option in 'Wine configuration' controls " "this." msgstr "" +"Omogućuje DPI promjenu veličine u Windows aplikacijama.\n" +"U suprotnome, Screen Resolution mogućnost u 'Wine podešavanjima' upravlja " +"ovime." #: lutris/runners/wine.py:384 msgid "DPI" -msgstr "" +msgstr "DPI" #: lutris/runners/wine.py:387 msgid "" "The DPI to be used if 'Enable DPI Scaling' is turned on.\n" "If blank or 'auto', Lutris will auto-detect this." msgstr "" +"DPI koji se koristi ako je 'Omogući DPI promjenu' uključeno.\n" +"Ako je prazno ili 'auto', Lutris će ovo automatski otkriti." #: lutris/runners/wine.py:393 msgid "Mouse Warp Override" @@ -4919,9 +4904,8 @@ msgstr "Itch.io (nije implementirano)" #: lutris/services/origin.py:87 -#, fuzzy msgid "Origin" -msgstr "Origin (WIP)" +msgstr "Origin" #: lutris/services/steam.py:102 msgid "" @@ -4940,18 +4924,16 @@ msgstr "TOSEC" #: lutris/services/ubisoft.py:81 -#, fuzzy msgid "Ubisoft Connect" -msgstr "Prekini povezivanje" +msgstr "Ubisoft Connect" #: lutris/services/xdg.py:43 msgid "Local" msgstr "Lokalno" #: lutris/settings.py:13 -#, fuzzy msgid "(c) 2010-2022 Lutris Team" -msgstr "(c) 2010-2021 Lutris tim" +msgstr "(c) 2010-2022 Lutris tim" #: lutris/settings.py:14 msgid "The Lutris team" @@ -5010,19 +4992,19 @@ #: lutris/sysoptions.py:85 msgid "Auto: WARNING -- No Vulkan Loader detected!" -msgstr "" +msgstr "Auto: Upozorenje -- Nema otkrivenog Vulkan učitača!" #: lutris/sysoptions.py:119 msgid "Auto: Intel Open Source (MESA: ANV)" -msgstr "" +msgstr "Auto: Intel otvoreni kôd (MESA: ANV)" #: lutris/sysoptions.py:121 msgid "Auto: AMD RADV Open Source (MESA: RADV)" -msgstr "" +msgstr "Auto: AMD RADV otvoreni kôd (MESA: RADV)" #: lutris/sysoptions.py:123 msgid "Auto: Nvidia Proprietary" -msgstr "" +msgstr "Auto: Nvidia vlasnički" #: lutris/sysoptions.py:145 msgid "Default installation folder" @@ -5100,25 +5082,24 @@ msgstr "Razlučivost zaslona vidljivog igri" #: lutris/sysoptions.py:211 -#, fuzzy msgid "Restrict number of cores used" -msgstr "Ograniči na jednu jezgru" +msgstr "Ograniči broj korištenih jezgri" #: lutris/sysoptions.py:214 -#, fuzzy msgid "Restrict the game to a maximum number of CPU cores." -msgstr "Ograniči igru na jednu jezgru procesora." +msgstr "Ograniči igru na najveći broj jezgara." #: lutris/sysoptions.py:219 -#, fuzzy msgid "Restrict number of cores to" -msgstr "Ograniči na jednu jezgru" +msgstr "Ograniči broj jezgara na" #: lutris/sysoptions.py:222 msgid "" "Maximum number of CPU cores to be used, if 'Restrict number of cores used' " "is turned on." msgstr "" +"Najveći broj CPU jezgri koji se koriste, Ako se 'Ograniči broj korištenih " +"jezgri' je uključeno." #: lutris/sysoptions.py:228 msgid "Restore gamma on game exit" @@ -5487,37 +5468,33 @@ msgstr "Otvori Xephyr u cijelom zaslonu (na razlučivosti radne površine)" #: lutris/util/system.py:20 -#, fuzzy msgid "Documents" -msgstr "Argumenti" +msgstr "Dokumenti" #: lutris/util/system.py:21 -#, fuzzy msgid "Downloads" -msgstr "Preuzmi" +msgstr "Preuzimanja" #: lutris/util/system.py:22 -#, fuzzy msgid "Desktop" -msgstr "Igre radnog okruženja" +msgstr "Radna površina" #: lutris/util/system.py:23 lutris/util/system.py:25 msgid "Pictures" -msgstr "" +msgstr "Slike" #: lutris/util/system.py:24 -#, fuzzy msgid "Videos" -msgstr "Video" +msgstr "Snimke" #: lutris/util/system.py:26 msgid "Projects" -msgstr "" +msgstr "Projekti" #: lutris/util/ubisoft/client.py:102 #, python-format msgid "Ubisoft authentication has been lost: %s" -msgstr "" +msgstr "Ubisoft ovjera je izgubljena: %s" #: lutris/util/wine/dll_manager.py:63 msgid "Manual" @@ -5538,7 +5515,6 @@ msgstr "Vulkan nije instaliran ili nije podržan na vašem sustavu" #: lutris/util/wine/wine.py:355 -#, fuzzy msgid "" "If you have compatible hardware, please follow the installation procedures " "as described in\n" @@ -5546,19 +5522,19 @@ "DXVK (https://github.com/lutris/docs/blob/master/HowToDXVK.md)" msgstr "" "Ako imate kompatibilan hardver, slijedite proces instalacije opisan na\n" -"How-to:-DXVK " -"(https://github.com/lutris/lutris/wiki/How-to:-DXVK)" +"How-to:-" +"DXVK (https://github.com/lutris/docs/blob/master/HowToDXVK.md)" #: lutris/util/wine/wine.py:367 -#, fuzzy msgid "" "Your limits are not set correctly. Please increase them as described here: " "How-to:-" "Esync (https://github.com/lutris/docs/blob/master/HowToEsync.md)" msgstr "" "Vaša ograničenja nisu postavljena ispravno. Povisite ih kako je ovdje " -"opisano: How-" -"to:-Esync (https://github.com/lutris/lutris/wiki/How-to:-Esync)" +"opisano: How-to:-Esync (https://github.com/lutris/docs/blob/master/HowToEsync." +"md)" #: lutris/util/wine/wine.py:376 msgid "" diff -Nru lutris-0.5.11~ubuntu22.04.1/po/nl.po lutris-0.5.12~ubuntu22.04.1/po/nl.po --- lutris-0.5.11~ubuntu22.04.1/po/nl.po 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/po/nl.po 2022-12-03 12:25:25.000000000 +0000 @@ -2,8 +2,8 @@ msgstr "" "Project-Id-Version: lutris\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-20 16:54+0200\n" -"PO-Revision-Date: 2022-08-11 11:53+0200\n" +"POT-Creation-Date: 2022-08-29 18:12+0200\n" +"PO-Revision-Date: 2022-08-30 19:50+0200\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: \n" "Language: nl\n" @@ -15,13 +15,13 @@ #: share/applications/net.lutris.Lutris.desktop:3 #: share/lutris/ui/lutris-window.ui:25 lutris/gui/application.py:76 -#: lutris/gui/widgets/status_icon.py:116 lutris/services/lutris.py:39 +#: lutris/gui/widgets/status_icon.py:127 lutris/services/lutris.py:39 msgid "Lutris" msgstr "Lutris" #: share/applications/net.lutris.Lutris.desktop:4 -msgid "video game preservation platform" -msgstr "videogame-bewaarplatform" +msgid "Video Game Preservation Platform" +msgstr "Videogame-bewaarplatform" #: share/applications/net.lutris.Lutris.desktop:6 msgid "gaming;wine;emulator;" @@ -49,7 +49,7 @@ "en compatibiliteitslagen, hebben we één overzichtelijk venster voor al je " "games weten te creëren." -#: share/lutris/ui/about-dialog.ui:8 share/lutris/ui/lutris-window.ui:581 +#: share/lutris/ui/about-dialog.ui:8 share/lutris/ui/lutris-window.ui:582 msgid "About Lutris" msgstr "Over Lutris" @@ -89,8 +89,8 @@ msgid "Forgot password?" msgstr "Wachtwoord vergeten?" -#: share/lutris/ui/dialog-lutris-login.ui:43 lutris/gui/dialogs/issue.py:52 -#: lutris/gui/installerwindow.py:76 +#: share/lutris/ui/dialog-lutris-login.ui:43 lutris/gui/dialogs/issue.py:49 +#: lutris/gui/installerwindow.py:78 msgid "C_ancel" msgstr "_Annuleren" @@ -123,14 +123,14 @@ msgstr "Andere weergave" #: share/lutris/ui/lutris-window.ui:299 -msgid "Zoom:" -msgstr "Zoomen:" +msgid "Zoom " +msgstr "Zoom " #: share/lutris/ui/lutris-window.ui:352 msgid "Sort Ascending" msgstr "Oplopend sorteren" -#: share/lutris/ui/lutris-window.ui:367 lutris/gui/config/common.py:113 +#: share/lutris/ui/lutris-window.ui:367 lutris/gui/config/common.py:98 #: lutris/gui/views/list.py:45 lutris/gui/views/list.py:159 msgid "Name" msgstr "Naam" @@ -159,27 +159,27 @@ msgid "Show _Hidden Games" msgstr "Verborgen s_pellen tonen" -#: share/lutris/ui/lutris-window.ui:485 +#: share/lutris/ui/lutris-window.ui:486 msgid "Show Side _Panel" msgstr "Zij_paneel tonen" -#: share/lutris/ui/lutris-window.ui:500 +#: share/lutris/ui/lutris-window.ui:501 msgid "Add games" msgstr "Games toevoegen" -#: share/lutris/ui/lutris-window.ui:514 +#: share/lutris/ui/lutris-window.ui:515 msgid "Preferences" msgstr "Voorkeuren" -#: share/lutris/ui/lutris-window.ui:539 +#: share/lutris/ui/lutris-window.ui:540 msgid "Discord" msgstr "Discord" -#: share/lutris/ui/lutris-window.ui:553 +#: share/lutris/ui/lutris-window.ui:554 msgid "Lutris forums" msgstr "Lutris-forum" -#: share/lutris/ui/lutris-window.ui:567 +#: share/lutris/ui/lutris-window.ui:568 msgid "Make a donation" msgstr "Doneren" @@ -204,7 +204,7 @@ msgid "Stop" msgstr "Stoppen" -#: lutris/game_actions.py:62 lutris/gui/dialogs/runner_install.py:193 +#: lutris/game_actions.py:62 lutris/gui/dialogs/runner_install.py:194 #: lutris/gui/installer/script_box.py:62 lutris/gui/widgets/game_bar.py:210 msgid "Install" msgstr "Installeren" @@ -245,7 +245,7 @@ msgid "Browse files" msgstr "Bestanden verkennen" -#: lutris/game_actions.py:75 lutris/gui/installerwindow.py:178 +#: lutris/game_actions.py:75 lutris/gui/installerwindow.py:180 msgid "Create desktop shortcut" msgstr "Bureaubladsnelkoppeling maken" @@ -253,7 +253,7 @@ msgid "Delete desktop shortcut" msgstr "Bureaubladsnelkoppeling verwijderen" -#: lutris/game_actions.py:85 lutris/gui/installerwindow.py:182 +#: lutris/game_actions.py:85 lutris/gui/installerwindow.py:184 msgid "Create application menu shortcut" msgstr "Snelkoppeling maken in programmamenu" @@ -261,7 +261,7 @@ msgid "Delete application menu shortcut" msgstr "Snelkoppeling verwijderen uit programmamenu" -#: lutris/game_actions.py:95 lutris/gui/installerwindow.py:187 +#: lutris/game_actions.py:95 lutris/gui/installerwindow.py:189 msgid "Create steam shortcut" msgstr "Steam-snelkoppeling maken" @@ -320,63 +320,63 @@ "Kan %s niet openen\n" "De map bestaat niet." -#: lutris/game.py:178 +#: lutris/game.py:179 msgid "Error the runner is not installed" msgstr "Foutmelding: runner is niet geïnstalleerd" -#: lutris/game.py:180 +#: lutris/game.py:181 msgid "A bios file is required to run this game" msgstr "Deze game vereist een bios-bestand" -#: lutris/game.py:184 +#: lutris/game.py:185 msgid "The file {} could not be found" msgstr "‘{}’ is niet aangetroffen" -#: lutris/game.py:186 +#: lutris/game.py:187 msgid "" "This game has no executable set. The install process didn't finish properly." msgstr "" "Er is geen uitvoerbaar bestand ingesteld omdat het installatieproces " "onverwachts is afgebroken." -#: lutris/game.py:189 +#: lutris/game.py:190 #, python-format msgid "The file %s is not executable" msgstr "‘%s’ is niet uitvoerbaar" -#: lutris/game.py:191 +#: lutris/game.py:192 #, python-format msgid "The path '%s' is not set. please set it in the options." msgstr "‘%s’ is niet ingesteld. Stel deze locatie in in de opties." -#: lutris/game.py:193 +#: lutris/game.py:194 #, python-format msgid "Unhandled error: %s" msgstr "Onverwachte foutmelding: %s" -#: lutris/game.py:298 +#: lutris/game.py:299 msgid "Tried to launch a game that isn't installed." msgstr "Je probeerde een niet-geïnstalleerde game te starten." -#: lutris/game.py:300 lutris/game.py:417 +#: lutris/game.py:301 lutris/game.py:409 msgid "Invalid game configuration: Missing runner" msgstr "Ongeldige gameconfiguratie: runner ontbreekt" -#: lutris/game.py:309 +#: lutris/game.py:310 msgid "Runtime currently updating" msgstr "Bezig met bijwerken van runtime" -#: lutris/game.py:309 +#: lutris/game.py:310 msgid "Game might not work as expected" msgstr "De game werkt mogelijk niet naar behoren" -#: lutris/game.py:342 +#: lutris/game.py:343 msgid "Unable to find Xephyr, install it or disable the Xephyr option" msgstr "" "Xephyr is niet aangetroffen. Installeer dit pakket of schakel de Xephyr-" "optie uit." -#: lutris/game.py:398 +#: lutris/game.py:390 #, python-format msgid "" "The selected terminal application could not be launched:\n" @@ -386,16 +386,16 @@ "%s" #. The 'file' sort of gameplay_info cannot be made to use a configuration -#: lutris/game.py:434 +#: lutris/game.py:426 msgid "The runner could not find a command to apply the configuration to." msgstr "" "De runner kan geen opdracht vinden om de instellingen op toe te passen." -#: lutris/game.py:635 +#: lutris/game.py:636 msgid "Error lauching the game:\n" msgstr "De game kan niet worden gestart:\n" -#: lutris/game.py:727 +#: lutris/game.py:730 #, python-format msgid "" "Error: Missing shared library.\n" @@ -406,7 +406,7 @@ "\n" "%s" -#: lutris/game.py:733 +#: lutris/game.py:736 msgid "" "Error: A different Wine version is already using the same Wine prefix." msgstr "" @@ -503,18 +503,18 @@ msgid "Select a Lutris installer" msgstr "Kies een Lutris-installatiebestand" -#: lutris/gui/application.py:87 +#: lutris/gui/application.py:88 msgid "" "Running Lutris as root is not recommended and may cause unexpected issues" msgstr "" "Door Lutris uit te voeren als root kunnen er onverwachte problemen optreden" -#: lutris/gui/application.py:97 +#: lutris/gui/application.py:98 msgid "Your Linux distribution is too old. Lutris won't function properly." msgstr "" "Je Linux-distributie is verouderd. Lutris zal niet naar behoren werken." -#: lutris/gui/application.py:102 +#: lutris/gui/application.py:103 msgid "" "Run a game directly by adding the parameter lutris:rungame/game-identifier.\n" "If several games share the same identifier you can use the numerical ID " @@ -529,116 +529,116 @@ "numerieke-id toevoegen.\n" "Installeer een game middels lutris:install/identificatie." -#: lutris/gui/application.py:115 +#: lutris/gui/application.py:116 msgid "Print the version of Lutris and exit" msgstr "Toon het versienummer en sluit af" -#: lutris/gui/application.py:123 +#: lutris/gui/application.py:124 msgid "Show debug messages" msgstr "Foutopsporingsberichten tonen" -#: lutris/gui/application.py:131 +#: lutris/gui/application.py:132 msgid "Install a game from a yml file" msgstr "Installeer een game middels een yml-bestand" -#: lutris/gui/application.py:139 +#: lutris/gui/application.py:140 msgid "Generate a bash script to run a game without the client" msgstr "Genereer een bash-script om games te starten zonder de client" -#: lutris/gui/application.py:147 +#: lutris/gui/application.py:148 msgid "Execute a program with the Lutris Runtime" msgstr "Voer een programma uit met de Lutris-runtime" -#: lutris/gui/application.py:155 +#: lutris/gui/application.py:156 msgid "List all games in database" msgstr "Alle games in databank tonen" -#: lutris/gui/application.py:163 +#: lutris/gui/application.py:164 msgid "Only list installed games" msgstr "Alleen geïnstalleerde games tonen" -#: lutris/gui/application.py:171 +#: lutris/gui/application.py:172 msgid "List available Steam games" msgstr "Beschikbare Steam-games tonen" -#: lutris/gui/application.py:179 +#: lutris/gui/application.py:180 msgid "List all known Steam library folders" msgstr "Alle bekende Steam-bibliotheekmappen tonen" -#: lutris/gui/application.py:187 +#: lutris/gui/application.py:188 msgid "List all known runners" msgstr "Alle bekende runners tonen" -#: lutris/gui/application.py:195 +#: lutris/gui/application.py:196 msgid "List all known Wine versions" msgstr "Alle bekende Wine-versies tonen" -#: lutris/gui/application.py:203 +#: lutris/gui/application.py:204 msgid "Install a Runner" msgstr "Runner installeren" -#: lutris/gui/application.py:211 +#: lutris/gui/application.py:212 msgid "Uninstall a Runner" msgstr "Runner verwijderen" -#: lutris/gui/application.py:219 +#: lutris/gui/application.py:220 msgid "Export a game" msgstr "Game exporteren" -#: lutris/gui/application.py:227 +#: lutris/gui/application.py:228 msgid "Import a game" msgstr "Game importeren" -#: lutris/gui/application.py:235 +#: lutris/gui/application.py:236 msgid "Destination path for export" msgstr "Exportlocatie" -#: lutris/gui/application.py:243 +#: lutris/gui/application.py:244 msgid "Display the list of games in JSON format" msgstr "Lijst met games tonen in json-opmaak" -#: lutris/gui/application.py:251 +#: lutris/gui/application.py:252 msgid "Reinstall game" msgstr "Game opnieuw installeren" -#: lutris/gui/application.py:254 +#: lutris/gui/application.py:255 msgid "Submit an issue" msgstr "Probleem melden" -#: lutris/gui/application.py:260 +#: lutris/gui/application.py:261 msgid "URI to open" msgstr "De te openen uri" -#: lutris/gui/application.py:489 +#: lutris/gui/application.py:483 #, python-format msgid "%s is not a valid URI" msgstr "%s is geen geldige uri" -#: lutris/gui/application.py:509 +#: lutris/gui/application.py:503 #, python-format msgid "Failed to download %s" msgstr "‘%s’ kan niet worden gedownload" -#: lutris/gui/application.py:517 +#: lutris/gui/application.py:511 #, python-brace-format msgid "download {url} to {file} started" msgstr "het downloaden van {url} naar {file} is gestart" -#: lutris/gui/application.py:528 +#: lutris/gui/application.py:522 #, python-format msgid "No such file: %s" msgstr "Bestand bestaat niet: %s" -#: lutris/gui/application.py:645 +#: lutris/gui/application.py:644 #, python-format msgid "There is no installer available for %s." msgstr "Er is geen installatiebestand van %s beschikbaar." -#: lutris/gui/application.py:655 +#: lutris/gui/application.py:654 msgid "No updates found" msgstr "Geen games aangetroffen" -#: lutris/gui/application.py:665 +#: lutris/gui/application.py:664 msgid "No DLC found" msgstr "Geen uitbreidingen aangetroffen" @@ -695,8 +695,8 @@ msgid "_Add" msgstr "_Toevoegen" -#: lutris/gui/config/boxes.py:512 lutris/gui/dialogs/__init__.py:134 -#: lutris/gui/dialogs/__init__.py:161 lutris/gui/dialogs/issue.py:75 +#: lutris/gui/config/boxes.py:512 lutris/gui/dialogs/__init__.py:165 +#: lutris/gui/dialogs/__init__.py:192 lutris/gui/dialogs/issue.py:72 #: lutris/gui/widgets/common.py:98 #: lutris/gui/widgets/download_progress_box.py:47 msgid "_Cancel" @@ -730,80 +730,80 @@ msgid "Select a runner in the Game Info tab" msgstr "Kies een runner op het tabblad ‘Game-informatie’" -#: lutris/gui/config/common.py:109 +#: lutris/gui/config/common.py:94 msgid "Game info" msgstr "Game-informatie" -#: lutris/gui/config/common.py:124 +#: lutris/gui/config/common.py:110 msgid "Identifier" msgstr "Identificatie" -#: lutris/gui/config/common.py:133 lutris/gui/config/common.py:259 +#: lutris/gui/config/common.py:119 lutris/gui/config/common.py:245 msgid "Change" msgstr "Wijzigen" -#: lutris/gui/config/common.py:142 +#: lutris/gui/config/common.py:128 msgid "Directory" msgstr "Map" -#: lutris/gui/config/common.py:148 +#: lutris/gui/config/common.py:134 msgid "Move" msgstr "Verplaatsen" -#: lutris/gui/config/common.py:156 lutris/gui/views/list.py:47 +#: lutris/gui/config/common.py:142 lutris/gui/views/list.py:47 msgid "Runner" msgstr "Runner" -#: lutris/gui/config/common.py:177 +#: lutris/gui/config/common.py:163 msgid "Remove custom banner" msgstr "Aangepaste banier verwijderen" -#: lutris/gui/config/common.py:188 +#: lutris/gui/config/common.py:174 msgid "Remove custom icon" msgstr "Aangepast pictogram verwijderen" -#: lutris/gui/config/common.py:197 +#: lutris/gui/config/common.py:183 msgid "Release year" msgstr "Jaar van uitgave" -#: lutris/gui/config/common.py:240 +#: lutris/gui/config/common.py:226 msgid "Select a runner from the list" msgstr "Kies een runner uit de lijst" -#: lutris/gui/config/common.py:248 +#: lutris/gui/config/common.py:234 msgid "Apply" msgstr "Toepassen" -#: lutris/gui/config/common.py:296 +#: lutris/gui/config/common.py:282 msgid "Game options" msgstr "Game-opties" -#: lutris/gui/config/common.py:304 +#: lutris/gui/config/common.py:290 msgid "Runner options" msgstr "Runner-opties" -#: lutris/gui/config/common.py:312 +#: lutris/gui/config/common.py:298 msgid "System options" msgstr "Systeemopties" #. Advanced settings checkbox -#: lutris/gui/config/common.py:322 +#: lutris/gui/config/common.py:308 msgid "Show advanced options" msgstr "Geavanceerde opties tonen" -#: lutris/gui/config/common.py:330 lutris/gui/dialogs/__init__.py:268 -#: lutris/gui/dialogs/runner_install.py:186 +#: lutris/gui/config/common.py:316 lutris/gui/dialogs/__init__.py:299 +#: lutris/gui/dialogs/runner_install.py:187 #: lutris/gui/dialogs/uninstall_game.py:60 #: lutris/gui/dialogs/uninstall_game.py:135 #: lutris/gui/widgets/download_progress_box.py:95 msgid "Cancel" msgstr "Annuleren" -#: lutris/gui/config/common.py:334 +#: lutris/gui/config/common.py:320 msgid "Save" msgstr "Opslaan" -#: lutris/gui/config/common.py:368 +#: lutris/gui/config/common.py:354 msgid "" "Are you sure you want to change the runner for this game ? This will reset " "the full configuration for this game and is not reversible." @@ -812,35 +812,35 @@ "de volledige configuratie van de game verwijderd. Dit kan niet ongedaan " "worden gemaakt." -#: lutris/gui/config/common.py:372 +#: lutris/gui/config/common.py:358 msgid "Confirm runner change" msgstr "Bevestigen" -#: lutris/gui/config/common.py:420 +#: lutris/gui/config/common.py:406 msgid "Runner not provided" msgstr "Geen runner opgegeven" -#: lutris/gui/config/common.py:423 +#: lutris/gui/config/common.py:409 msgid "Please fill in the name" msgstr "Voer de naam in" -#: lutris/gui/config/common.py:426 +#: lutris/gui/config/common.py:412 msgid "Steam AppID not provided" msgstr "Geen Steam AppId opgegeven" -#: lutris/gui/config/common.py:444 +#: lutris/gui/config/common.py:430 msgid "The following fields have invalid values: " msgstr "De volgende velden bevatten ongeldige waarden: " -#: lutris/gui/config/common.py:451 +#: lutris/gui/config/common.py:437 msgid "Current configuration is not valid, ignoring save request" msgstr "De huidige configuratie is ongeldig; het bewaarverzoek is genegeerd" -#: lutris/gui/config/common.py:486 +#: lutris/gui/config/common.py:472 msgid "Please choose a custom image" msgstr "Kies een aangepaste afbeelding" -#: lutris/gui/config/common.py:494 +#: lutris/gui/config/common.py:480 msgid "Images" msgstr "Afbeeldingen" @@ -857,9 +857,9 @@ msgid "Hide text under icons (requires restart)" msgstr "Tekst onder pictogrammen verbergen (herstart vereist)" -#: lutris/gui/config/preferences_box.py:13 -msgid "Show Tray Icon (requires restart)" -msgstr "Systeemvakpictogram tonen (herstart vereist)" +#: lutris/gui/config/preferences_box.py:13 lutris/gui/config/sysinfo_box.py:13 +msgid "Show Tray Icon" +msgstr "Systeemvakpictogram tonen" #: lutris/gui/config/preferences_box.py:14 msgid "Use dark theme (requires dark theme variant for Gtk)" @@ -877,11 +877,11 @@ msgid "Interface" msgstr "Vormgeving" -#: lutris/gui/config/preferences_dialog.py:27 lutris/gui/widgets/sidebar.py:271 +#: lutris/gui/config/preferences_dialog.py:27 lutris/gui/widgets/sidebar.py:272 msgid "Runners" msgstr "Runners" -#: lutris/gui/config/preferences_dialog.py:28 lutris/gui/widgets/sidebar.py:270 +#: lutris/gui/config/preferences_dialog.py:28 lutris/gui/widgets/sidebar.py:271 msgid "Sources" msgstr "Bronnen" @@ -893,7 +893,7 @@ msgid "Global options" msgstr "Algemene opties" -#: lutris/gui/config/runner_box.py:90 lutris/gui/widgets/sidebar.py:214 +#: lutris/gui/config/runner_box.py:92 lutris/gui/widgets/sidebar.py:214 #, python-format msgid "Manage %s versions" msgstr "%s-versies beheren" @@ -936,10 +936,6 @@ msgid "Hide text under icons" msgstr "Tekst onder pictogrammen verbergen" -#: lutris/gui/config/sysinfo_box.py:13 -msgid "Show Tray Icon" -msgstr "Systeemvakpictogram tonen" - #: lutris/gui/config/sysinfo_box.py:38 msgid "Copy to clipboard" msgstr "Kopiëren naar klembord" @@ -980,59 +976,59 @@ msgid "Downloading %s" msgstr "Bezig met downloaden van %s" -#: lutris/gui/dialogs/__init__.py:133 lutris/gui/dialogs/__init__.py:160 -#: lutris/gui/dialogs/issue.py:74 lutris/gui/dialogs/runner_install.py:63 +#: lutris/gui/dialogs/__init__.py:164 lutris/gui/dialogs/__init__.py:191 +#: lutris/gui/dialogs/issue.py:71 lutris/gui/dialogs/runner_install.py:64 #: lutris/gui/widgets/common.py:98 msgid "_OK" msgstr "_Oké" -#: lutris/gui/dialogs/__init__.py:151 +#: lutris/gui/dialogs/__init__.py:182 msgid "Please choose a file" msgstr "Kies een bestand" -#: lutris/gui/dialogs/__init__.py:181 +#: lutris/gui/dialogs/__init__.py:212 msgid "Checking for runtime updates, please wait…" msgstr "Bezig met controleren op runtime-updates…" -#: lutris/gui/dialogs/__init__.py:212 +#: lutris/gui/dialogs/__init__.py:243 #, python-format msgid "%s is already installed" msgstr "%s is al geïnstalleerd" -#: lutris/gui/dialogs/__init__.py:221 +#: lutris/gui/dialogs/__init__.py:252 msgid "Launch game" msgstr "Game starten" -#: lutris/gui/dialogs/__init__.py:225 +#: lutris/gui/dialogs/__init__.py:256 msgid "Install the game again" msgstr "Game opnieuw installeren" -#: lutris/gui/dialogs/__init__.py:229 lutris/gui/dialogs/__init__.py:272 -#: lutris/gui/dialogs/__init__.py:360 +#: lutris/gui/dialogs/__init__.py:260 lutris/gui/dialogs/__init__.py:303 +#: lutris/gui/dialogs/__init__.py:391 msgid "OK" msgstr "Oké" -#: lutris/gui/dialogs/__init__.py:248 +#: lutris/gui/dialogs/__init__.py:279 msgid "Select game to launch" msgstr "Kies een te starten game" -#: lutris/gui/dialogs/__init__.py:333 +#: lutris/gui/dialogs/__init__.py:364 msgid "Login failed" msgstr "Inloggen mislukt" -#: lutris/gui/dialogs/__init__.py:344 +#: lutris/gui/dialogs/__init__.py:375 msgid "Install script for {}" msgstr "Installatiescript van {}" -#: lutris/gui/dialogs/__init__.py:396 +#: lutris/gui/dialogs/__init__.py:427 msgid "Do not display this message again." msgstr "Niet meer tonen" -#: lutris/gui/dialogs/__init__.py:417 +#: lutris/gui/dialogs/__init__.py:448 msgid "Wine is not installed on your system." msgstr "Wine is niet geïnstalleerd." -#: lutris/gui/dialogs/__init__.py:419 +#: lutris/gui/dialogs/__init__.py:450 msgid "" "Having Wine installed on your system guarantees that Wine builds from Lutris " "will have all required dependencies.\n" @@ -1046,16 +1042,16 @@ "Ga naar de Lutris-wiki (Engels) om te lezen hoe je Wine installeert." -#: lutris/gui/dialogs/__init__.py:445 +#: lutris/gui/dialogs/__init__.py:476 #, python-format msgid "Moving %s to %s..." msgstr "Bezig met verplaatsen van %s naar %s…" -#: lutris/gui/dialogs/issue.py:27 +#: lutris/gui/dialogs/issue.py:24 msgid "Submit an issue" msgstr "Probleem melden" -#: lutris/gui/dialogs/issue.py:32 +#: lutris/gui/dialogs/issue.py:29 msgid "" "Describe the problem you're having in the text box below. This information " "will be sent the Lutris team along with your system information. You can " @@ -1065,81 +1061,81 @@ "verstuurd naar het Lutris-team, tezamen met je systeeminformatie. Je kunt " "deze informatie ook lokaal opslaan." -#: lutris/gui/dialogs/issue.py:55 +#: lutris/gui/dialogs/issue.py:52 msgid "_Save" msgstr "Op_slaan" -#: lutris/gui/dialogs/issue.py:71 +#: lutris/gui/dialogs/issue.py:68 msgid "Select a location to save the issue" msgstr "Kies een locatie om het probleem in op te slaan" -#: lutris/gui/dialogs/issue.py:91 +#: lutris/gui/dialogs/issue.py:88 #, python-format msgid "Issue saved in %s" msgstr "Probleem opgeslagen in %s" -#: lutris/gui/dialogs/runner_install.py:29 +#: lutris/gui/dialogs/runner_install.py:30 #, python-format msgid "Showing games using %s" msgstr "Games die gebruikmaken van %s" -#: lutris/gui/dialogs/runner_install.py:71 +#: lutris/gui/dialogs/runner_install.py:72 #, python-format msgid "Waiting for response from %s" msgstr "Bezig met wachten op antwoord van %s" -#: lutris/gui/dialogs/runner_install.py:87 +#: lutris/gui/dialogs/runner_install.py:88 #, python-format msgid "Unable to get runner versions: %s" msgstr "De runner-versies kunnen niet worden opgehaald: %s" -#: lutris/gui/dialogs/runner_install.py:101 +#: lutris/gui/dialogs/runner_install.py:102 msgid "Unable to get runner versions from lutris.net" msgstr "De runner-versies kunnen niet worden opgehaald van lutris.net" -#: lutris/gui/dialogs/runner_install.py:108 +#: lutris/gui/dialogs/runner_install.py:109 #, python-format msgid "%s version management" msgstr "%s-versiebeheer" -#: lutris/gui/dialogs/runner_install.py:158 +#: lutris/gui/dialogs/runner_install.py:159 #, python-format msgid "_View %d game" msgid_plural "_View %d games" msgstr[0] "%d game to_nen" msgstr[1] "%d games to_nen" -#: lutris/gui/dialogs/runner_install.py:190 +#: lutris/gui/dialogs/runner_install.py:191 #: lutris/gui/dialogs/uninstall_game.py:34 msgid "Uninstall" msgstr "Deïnstalleren" -#: lutris/gui/dialogs/runner_install.py:214 +#: lutris/gui/dialogs/runner_install.py:215 msgid "Wine version usage" msgstr "Wine-versiegebruik" -#: lutris/gui/dialogs/runner_install.py:256 +#: lutris/gui/dialogs/runner_install.py:274 msgid "Do you want to cancel the download?" msgstr "Weet je zeker dat je het downloaden wilt afbreken?" -#: lutris/gui/dialogs/runner_install.py:257 +#: lutris/gui/dialogs/runner_install.py:275 msgid "Download starting" msgstr "Bezig met starten" -#: lutris/gui/dialogs/runner_install.py:307 +#: lutris/gui/dialogs/runner_install.py:325 #, python-format msgid "Version %s is not longer available" msgstr "Versie %s is niet meer beschikbaar." -#: lutris/gui/dialogs/runner_install.py:332 +#: lutris/gui/dialogs/runner_install.py:350 msgid "Downloading…" msgstr "Bezig met downloaden…" -#: lutris/gui/dialogs/runner_install.py:335 +#: lutris/gui/dialogs/runner_install.py:353 msgid "Extracting…" msgstr "Bezig met uitpakken…" -#: lutris/gui/dialogs/runner_install.py:377 +#: lutris/gui/dialogs/runner_install.py:395 msgid "Failed to retrieve the runner archive" msgstr "Kan het runner-archief niet ophalen" @@ -1166,7 +1162,7 @@ msgid "Content of %s are protected and will not be deleted." msgstr "De inhoud van %s is beveiligd en wordt daarom niet verwijderd." -#: lutris/gui/dialogs/uninstall_game.py:76 +#: lutris/gui/dialogs/uninstall_game.py:75 #, python-format msgid "Delete %s (%s)" msgstr "%s verwijderen (%s)" @@ -1208,124 +1204,119 @@ "Weet je zeker dat je %s wilt verwijderen uit je verzameling?\n" "Alle bijgehouden tijden worden eveneens gewist." -#: lutris/gui/dialogs/webconnect_dialog.py:106 +#: lutris/gui/dialogs/webconnect_dialog.py:103 msgid "Loading..." msgstr "Bezig met laden…" -#: lutris/gui/installer/file_box.py:95 +#: lutris/gui/installer/file_box.py:94 #, python-brace-format msgid "Steam game {appid}" msgstr "Steam-game {appid}" -#: lutris/gui/installer/file_box.py:110 -#, python-brace-format -msgid "{file} on {host}" -msgstr "{file} op {host}" - -#: lutris/gui/installer/file_box.py:121 +#: lutris/gui/installer/file_box.py:108 msgid "Download" msgstr "Downloaden" -#: lutris/gui/installer/file_box.py:123 +#: lutris/gui/installer/file_box.py:110 msgid "Use Cache" msgstr "Cache gebruiken" -#: lutris/gui/installer/file_box.py:125 lutris/runners/steam.py:29 +#: lutris/gui/installer/file_box.py:112 lutris/runners/steam.py:29 #: lutris/services/steam.py:73 msgid "Steam" msgstr "Steam" -#: lutris/gui/installer/file_box.py:126 +#: lutris/gui/installer/file_box.py:113 msgid "Select File" msgstr "Bestand kiezen" -#: lutris/gui/installer/file_box.py:187 +#: lutris/gui/installer/file_box.py:174 msgid "Cache file for future installations" msgstr "Cachebestand voor toekomstige installaties" -#: lutris/gui/installer/file_box.py:206 +#: lutris/gui/installer/file_box.py:193 msgid "Source:" msgstr "Bron:" -#: lutris/gui/installerwindow.py:65 +#: lutris/gui/installerwindow.py:67 msgid "Cache" msgstr "Cache" -#: lutris/gui/installerwindow.py:76 +#: lutris/gui/installerwindow.py:78 msgid "Abort and revert the installation" msgstr "Afbreken en installatie terugdraaien" -#: lutris/gui/installerwindow.py:78 +#: lutris/gui/installerwindow.py:80 msgid "_Eject" msgstr "Uitw_erpen" -#: lutris/gui/installerwindow.py:79 +#: lutris/gui/installerwindow.py:81 msgid "_View source" msgstr "Bron _bekijken" -#: lutris/gui/installerwindow.py:80 +#: lutris/gui/installerwindow.py:82 msgid "_Install" msgstr "_Installeren" -#: lutris/gui/installerwindow.py:81 +#: lutris/gui/installerwindow.py:83 msgid "_Continue" msgstr "_Doorgaan" -#: lutris/gui/installerwindow.py:82 +#: lutris/gui/installerwindow.py:84 msgid "_Launch" msgstr "_Starten" -#: lutris/gui/installerwindow.py:83 +#: lutris/gui/installerwindow.py:85 msgid "_Close" msgstr "_Sluiten" -#: lutris/gui/installerwindow.py:122 +#: lutris/gui/installerwindow.py:124 #, python-format msgid "Missing field \"%s\" in install script" msgstr "Het veld ‘%s’ ontbreekt in het installatiescript" -#: lutris/gui/installerwindow.py:128 +#: lutris/gui/installerwindow.py:130 #, python-format msgid "Install %s" msgstr "%s installeren" -#: lutris/gui/installerwindow.py:162 +#: lutris/gui/installerwindow.py:164 #, python-format msgid "This game requires %s. Do you want to install it?" msgstr "Deze game is afhankelijk van %s. Wil je het installeren?" -#: lutris/gui/installerwindow.py:163 +#: lutris/gui/installerwindow.py:165 msgid "Missing dependency" msgstr "Ontbrekende afhankelijkheid" -#: lutris/gui/installerwindow.py:175 +#: lutris/gui/installerwindow.py:177 msgid "Installing {}" msgstr "Bezig met installeren van {}" -#: lutris/gui/installerwindow.py:196 +#: lutris/gui/installerwindow.py:198 msgid "Select installation directory" msgstr "Installatiemap kiezen" -#: lutris/gui/installerwindow.py:246 +#: lutris/gui/installerwindow.py:248 msgid "Autodetect" msgstr "Automatisch detecteren" -#: lutris/gui/installerwindow.py:252 +#: lutris/gui/installerwindow.py:254 msgid "Browse…" msgstr "Bladeren…" -#: lutris/gui/installerwindow.py:259 +#: lutris/gui/installerwindow.py:261 msgid "Select the folder where the disc is mounted" msgstr "Kies de map waarin de schijf is aangekoppeld" -#: lutris/gui/installerwindow.py:320 +#: lutris/gui/installerwindow.py:322 msgid "" "Please review the files needed for the installation then click 'Continue'" msgstr "" "Neem de voor installatie benodigde bestanden door en klik vervolgens op " "‘Doorgaan’" -#: lutris/gui/installerwindow.py:358 +#: lutris/gui/installerwindow.py:360 msgid "" "This game has extra content. \n" "Select which one you want and they will be available in the 'extras' folder " @@ -1335,44 +1326,75 @@ "Kies de inhoud die je wilt toevoegen aan de ‘extras’-map in de " "installatiemap." -#: lutris/gui/installerwindow.py:459 +#: lutris/gui/installerwindow.py:461 #, python-format msgid "Unable to get files: %s" msgstr "De bestanden kunnen niet worden opgehaald: %s" -#: lutris/gui/installerwindow.py:554 +#: lutris/gui/installerwindow.py:558 msgid "Remove game files" msgstr "Gamebestanden verwijderen" -#: lutris/gui/installerwindow.py:560 +#: lutris/gui/installerwindow.py:568 msgid "Are you sure you want to cancel the installation?" msgstr "Weet je zeker dat je de installatie wilt afbreken?" -#: lutris/gui/installerwindow.py:561 +#: lutris/gui/installerwindow.py:569 msgid "Cancel installation?" msgstr "Installatie afbreken?" -#: lutris/gui/lutriswindow.py:345 +#: lutris/gui/lutriswindow.py:344 #, python-format msgid "Connect your %s account to access your games" msgstr "Koppel je %s-account om toegang te krijgen tot je games" -#: lutris/gui/lutriswindow.py:416 +#: lutris/gui/lutriswindow.py:417 +#, python-format +msgid "Add a game matching '%s' to your favorites to see it here." +msgstr "" +"Voeg een game die overeenkomt met ‘%s’ toe aan je favorieten om hem hier te " +"tonen." + +#: lutris/gui/lutriswindow.py:420 +#, python-format +msgid "" +"No installed games matching '%s' found. Press Ctrl+I to show uninstalled " +"games." +msgstr "" +"Er zijn geen geïnstalleerde games die overeenkomen met ‘%s’. Druk op Ctrl+I " +"om verwijderde games te tonen." + +#. but not if missing! +#: lutris/gui/lutriswindow.py:422 +#, python-format +msgid "" +"No visible games matching '%s' found. Press Ctrl+H to show hidden games." +msgstr "" +"Er zijn geen games die overeenkomen met ‘%s’. Druk op Ctrl+H om verborgen " +"games te tonen." + +#: lutris/gui/lutriswindow.py:425 #, python-format msgid "No games matching '%s' found " msgstr "Er zijn geen games die overeenkomen met “%s” " -#: lutris/gui/lutriswindow.py:419 +#: lutris/gui/lutriswindow.py:428 msgid "Add games to your favorites to see them here." msgstr "Voeg games toe aan je favorieten om ze hier te tonen." -#: lutris/gui/lutriswindow.py:421 -msgid "No installed games found. Press Ctrl+H to show all games." +#: lutris/gui/lutriswindow.py:430 +msgid "No installed games found. Press Ctrl+I to show uninstalled games." msgstr "" -"Geen geïnstalleerde games aangetroffen. Druk op Ctrl+H om alle games te " -"tonen." +"Er zijn geen geïnstalleerde games aangetroffen. Druk op Ctrl+I om " +"verwijderde games te tonen." -#: lutris/gui/lutriswindow.py:430 +#. but not if missing! +#: lutris/gui/lutriswindow.py:432 +msgid "No visible games found. Press Ctrl+H to show hidden games." +msgstr "" +"Er zijn geen zichtbare games. Druk op Ctrl+H om verborgen games te tonen." + +#: lutris/gui/lutriswindow.py:440 msgid "No games found" msgstr "Geen games aangetroffen" @@ -1482,27 +1504,27 @@ msgid "Manage Versions" msgstr "Versies beheren" -#: lutris/gui/widgets/sidebar.py:269 +#: lutris/gui/widgets/sidebar.py:270 msgid "Library" msgstr "Verzameling" -#: lutris/gui/widgets/sidebar.py:272 +#: lutris/gui/widgets/sidebar.py:273 msgid "Platforms" msgstr "Platformen" -#: lutris/gui/widgets/sidebar.py:316 lutris/util/system.py:27 +#: lutris/gui/widgets/sidebar.py:317 lutris/util/system.py:28 msgid "Games" msgstr "Games" -#: lutris/gui/widgets/sidebar.py:325 +#: lutris/gui/widgets/sidebar.py:326 msgid "Recent" msgstr "Recent" -#: lutris/gui/widgets/sidebar.py:334 +#: lutris/gui/widgets/sidebar.py:335 msgid "Favorites" msgstr "Favorieten" -#: lutris/gui/widgets/sidebar.py:342 +#: lutris/gui/widgets/sidebar.py:343 msgid "Running" msgstr "Actief" @@ -1521,8 +1543,8 @@ "Eén van de volgende oprachtregelopties is vereist om de {cmd}-opdracht uit " "te kunnen voeren: {params}" -#: lutris/installer/commands.py:67 lutris/installer/interpreter.py:123 -#: lutris/installer/interpreter.py:146 +#: lutris/installer/commands.py:67 lutris/installer/interpreter.py:128 +#: lutris/installer/interpreter.py:151 msgid " or " msgstr " of " @@ -1640,37 +1662,42 @@ msgid "Wrong value for write_file mode: '%s'" msgstr "Onjuiste waarde van write_file-modus: ‘%s’" -#: lutris/installer/installer_file.py:26 +#: lutris/installer/installer_file.py:27 #, python-format msgid "missing field `url` for file `%s`" msgstr "Het veld ‘url’ ontbreekt in ‘%s’" -#: lutris/installer/installer_file.py:38 +#: lutris/installer/installer_file.py:46 #, python-format msgid "missing field `filename` in file `%s`" msgstr "Het veld ‘filename’ ontbreekt in ‘%s’" -#: lutris/installer/installer_file.py:176 +#: lutris/installer/installer_file.py:95 +#, python-brace-format +msgid "{file} on {host}" +msgstr "{file} op {host}" + +#: lutris/installer/installer_file.py:196 msgid "Invalid checksum, expected format (type:hash) " msgstr "Ongeldige controlesom. Verwacht: (type:hash) " -#: lutris/installer/installer_file.py:179 +#: lutris/installer/installer_file.py:199 msgid " checksum mismatch " msgstr " De controlesom komt niet overeen" -#: lutris/installer/installer.py:179 +#: lutris/installer/installer.py:177 msgid "Game config key must be a string" msgstr "De gameconfiguratiesleutel dient een tekenreeks te zijn" -#: lutris/installer/installer.py:228 +#: lutris/installer/installer.py:227 msgid "Invalid 'game' section" msgstr "Ongeldige ‘game’sectie" -#: lutris/installer/interpreter.py:53 +#: lutris/installer/interpreter.py:55 msgid "This installer doesn't have a 'script' section" msgstr "De installatiewizard bevat geen ‘script’sectie" -#: lutris/installer/interpreter.py:57 +#: lutris/installer/interpreter.py:59 msgid "" "Invalid script: \n" "{}" @@ -1678,38 +1705,38 @@ "Ongeldig script:\n" "{}" -#: lutris/installer/interpreter.py:123 lutris/installer/interpreter.py:126 +#: lutris/installer/interpreter.py:128 lutris/installer/interpreter.py:131 #, python-format msgid "This installer requires %s on your system" msgstr "Deze installatiewizard vereist %s" -#: lutris/installer/interpreter.py:139 +#: lutris/installer/interpreter.py:144 msgid "You need to install {} before" msgstr "Installeer eerst ‘{}’" -#: lutris/installer/interpreter.py:187 +#: lutris/installer/interpreter.py:191 msgid "Lutris does not have the necessary permissions to install to path:" msgstr "Lutris is niet bevoegd om de game te installeren in" -#: lutris/installer/interpreter.py:271 +#: lutris/installer/interpreter.py:275 #, python-format msgid "Invalid runner provided %s" msgstr "Ongeldige runner opgegeven bij %s" -#: lutris/installer/interpreter.py:297 +#: lutris/installer/interpreter.py:301 msgid "Installing game data" msgstr "Bezig met installeren van gamegegevens" -#: lutris/installer/interpreter.py:309 +#: lutris/installer/interpreter.py:313 msgid "Installer commands are not formatted correctly" msgstr "De installatie-opdrachten zijn onjuist opgemaakt" -#: lutris/installer/interpreter.py:342 +#: lutris/installer/interpreter.py:346 #, python-format msgid "The command \"%s\" does not exist." msgstr "De opdracht ‘%s’ bestaat niet." -#: lutris/installer/interpreter.py:363 +#: lutris/installer/interpreter.py:367 #, python-format msgid "" "The executable at path %s can't be found, please check the destination " @@ -1720,7 +1747,7 @@ "bestemming.\n" "Sommige onderdelen van het installatieproces zijn niet correct afgerond." -#: lutris/installer/interpreter.py:369 +#: lutris/installer/interpreter.py:373 msgid "Installation completed!" msgstr "Installatie voltooid!" @@ -1729,24 +1756,24 @@ msgid "Malformed steam path: %s" msgstr "Ongeldige Steam-locatie: %s" -#: lutris/runners/atari800.py:19 +#: lutris/runners/atari800.py:17 msgid "Desktop resolution" msgstr "Bureaubladresolutie" -#: lutris/runners/atari800.py:25 +#: lutris/runners/atari800.py:22 msgid "Atari800" msgstr "Atari800" -#: lutris/runners/atari800.py:26 +#: lutris/runners/atari800.py:23 msgid "Atari 8bit computers" msgstr "Atari 8-bitcomputers" -#: lutris/runners/atari800.py:29 +#: lutris/runners/atari800.py:26 msgid "Atari 400, 800 and XL emulator" msgstr "Atari 400-, 800- en XL-emulator" -#: lutris/runners/atari800.py:41 lutris/runners/jzintv.py:19 -#: lutris/runners/libretro.py:74 lutris/runners/mame.py:78 +#: lutris/runners/atari800.py:38 lutris/runners/jzintv.py:19 +#: lutris/runners/libretro.py:73 lutris/runners/mame.py:78 #: lutris/runners/mednafen.py:57 lutris/runners/mupen64plus.py:20 #: lutris/runners/o2em.py:45 lutris/runners/openmsx.py:17 #: lutris/runners/osmose.py:21 lutris/runners/snes9x.py:27 @@ -1754,7 +1781,7 @@ msgid "ROM file" msgstr "ROM-bestand" -#: lutris/runners/atari800.py:43 +#: lutris/runners/atari800.py:40 msgid "" "The game data, commonly called a ROM image. \n" "Supported formats: ATR, XFD, DCM, ATR.GZ, XFD.GZ and PRO." @@ -1762,11 +1789,11 @@ "De gamedata, ook wel bekend als rom.\n" "Ondersteunde rom-formaten: ATR, XFD, DCM, ATR.GZ, XFD.GZ en PRO." -#: lutris/runners/atari800.py:57 +#: lutris/runners/atari800.py:51 msgid "BIOS location" msgstr "BIOS-locatie" -#: lutris/runners/atari800.py:59 +#: lutris/runners/atari800.py:53 msgid "" "A folder containing the Atari 800 BIOS files.\n" "They are provided by Lutris so you shouldn't have to change this." @@ -1774,34 +1801,34 @@ "De map die de Atari 800-biosbestanden bevat.\n" "Deze worden aangeleverd door Lutris, dus dit zou je niet hoeven aanpassen." -#: lutris/runners/atari800.py:70 +#: lutris/runners/atari800.py:62 msgid "Emulate Atari 800" msgstr "Atari 800 emuleren" -#: lutris/runners/atari800.py:71 +#: lutris/runners/atari800.py:63 msgid "Emulate Atari 800 XL" msgstr "Atari 800 XL emuleren" -#: lutris/runners/atari800.py:72 +#: lutris/runners/atari800.py:64 msgid "Emulate Atari 320 XE (Compy Shop)" msgstr "Atari 320 XE (Compy Shop) emuleren" -#: lutris/runners/atari800.py:73 +#: lutris/runners/atari800.py:65 msgid "Emulate Atari 320 XE (Rambo)" msgstr "Atari 320 XE (Rambo) emuleren" -#: lutris/runners/atari800.py:74 +#: lutris/runners/atari800.py:66 msgid "Emulate Atari 5200" msgstr "Atari 5200 emuleren" -#: lutris/runners/atari800.py:79 lutris/runners/mame.py:83 +#: lutris/runners/atari800.py:69 lutris/runners/mame.py:83 #: lutris/runners/vice.py:88 msgid "Machine" msgstr "Machine" -#: lutris/runners/atari800.py:85 lutris/runners/easyrpg.py:144 +#: lutris/runners/atari800.py:75 lutris/runners/easyrpg.py:144 #: lutris/runners/hatari.py:72 lutris/runners/jzintv.py:43 -#: lutris/runners/libretro.py:94 lutris/runners/mame.py:157 +#: lutris/runners/libretro.py:93 lutris/runners/mame.py:157 #: lutris/runners/mednafen.py:74 lutris/runners/mupen64plus.py:28 #: lutris/runners/o2em.py:75 lutris/runners/osmose.py:34 #: lutris/runners/pcsx2.py:26 lutris/runners/pico8.py:38 @@ -1811,11 +1838,11 @@ msgid "Fullscreen" msgstr "Beeldvullend" -#: lutris/runners/atari800.py:92 +#: lutris/runners/atari800.py:82 msgid "Fullscreen resolution" msgstr "Beeldvullende resolutie" -#: lutris/runners/atari800.py:104 +#: lutris/runners/atari800.py:94 msgid "Could not download Atari 800 BIOS archive" msgstr "Het Atari800-biosarchief kan niet worden gedownload" @@ -1908,8 +1935,8 @@ msgid "Command line arguments used when launching DOSBox" msgstr "De tezamen met DOSBox uit te voeren opdrachtregelopties" -#: lutris/runners/dosbox.py:51 lutris/runners/linux.py:39 -#: lutris/runners/wine.py:61 +#: lutris/runners/dosbox.py:51 lutris/runners/flatpak.py:74 +#: lutris/runners/linux.py:39 lutris/runners/wine.py:61 msgid "Working directory" msgstr "Werkmap" @@ -1962,10 +1989,10 @@ msgid "Runs RPG Maker 2000/2003 games" msgstr "Speel games die gemaakt zijn met RPG Maker 2000/2003" -#: lutris/runners/easyrpg.py:12 lutris/runners/linux.py:14 -#: lutris/runners/linux.py:16 lutris/runners/residualvm.py:15 -#: lutris/runners/scummvm.py:14 lutris/runners/steam.py:30 -#: lutris/runners/zdoom.py:15 +#: lutris/runners/easyrpg.py:12 lutris/runners/flatpak.py:18 +#: lutris/runners/linux.py:14 lutris/runners/linux.py:16 +#: lutris/runners/residualvm.py:15 lutris/runners/scummvm.py:14 +#: lutris/runners/steam.py:30 lutris/runners/zdoom.py:15 msgid "Linux" msgstr "Linux" @@ -1998,7 +2025,7 @@ msgid "Disable auto detection of the simulated engine." msgstr "Schakel automatische detectie van de gesimuleerde aandrijving uit." -#: lutris/runners/easyrpg.py:41 lutris/runners/fsuae.py:50 +#: lutris/runners/easyrpg.py:41 lutris/runners/fsuae.py:155 #: lutris/runners/mame.py:173 lutris/runners/wine.py:82 #: lutris/runners/wine.py:416 msgid "Auto" @@ -2187,7 +2214,7 @@ "Schakel uit om verticale synchronisatie uit te schakelen het fps-limiet aan " "te houden. VSync wordt mogelijk niet op alle platformen ondersteund." -#: lutris/runners/easyrpg.py:196 lutris/sysoptions.py:318 +#: lutris/runners/easyrpg.py:196 lutris/sysoptions.py:345 msgid "FPS limit" msgstr "FPS-limiet" @@ -2287,196 +2314,268 @@ msgid "The directory {} could not be found" msgstr "‘{}’ is niet aangetroffen" -#: lutris/runners/fsuae.py:11 -msgid "FS-UAE" -msgstr "FS-UAE" +#: lutris/runners/flatpak.py:17 +msgid "Runs Flatpak applications" +msgstr "Flatpakprogramma's uitvoeren" + +#: lutris/runners/flatpak.py:20 +msgid "Flatpak" +msgstr "Flatpak" -#: lutris/runners/fsuae.py:12 -msgid "Amiga emulator" -msgstr "Amiga-emulator" +#: lutris/runners/flatpak.py:33 lutris/runners/steam.py:35 +msgid "Application ID" +msgstr "Programma-id" + +#: lutris/runners/flatpak.py:34 +msgid "The application's unique three-part identifier (tld.domain.app)." +msgstr "De driedelige id-code van het programma (tld.domein.app)." + +#: lutris/runners/flatpak.py:39 +msgid "Architecture" +msgstr "Architectuur" + +#: lutris/runners/flatpak.py:40 +msgid "" +"The architecture to run. See flatpak --supported-arches for architectures " +"supported by the host." +msgstr "" +"De te gebruiken architectuur. Zie flatpak --supported-arches voor de door de " +"host ondersteunde architecturen." + +#: lutris/runners/flatpak.py:47 +msgid "Branch" +msgstr "Afsplitsing" + +#: lutris/runners/flatpak.py:48 +msgid "The branch to use." +msgstr "De te gebruiken afsplitsing." + +#: lutris/runners/flatpak.py:54 +msgid "Install type" +msgstr "Installatietype" + +#: lutris/runners/flatpak.py:55 +msgid "Can be system or user." +msgstr "Kan systeem of gebruiker zijn." + +#: lutris/runners/flatpak.py:61 +msgid "Args" +msgstr "Opdrachtregelopties" + +#: lutris/runners/flatpak.py:62 +msgid "Arguments to be passed to the application." +msgstr "De opdrachtregelopties die het programma dient te gebruiken." + +#: lutris/runners/flatpak.py:67 +msgid "Command" +msgstr "Opdracht" + +#: lutris/runners/flatpak.py:68 +msgid "" +"The command to run instead of the one listed in the application metadata." +msgstr "" +"De opdracht die dient te worden uitgevoerd in plaats van die in de " +"metagegevens." + +#: lutris/runners/flatpak.py:75 +msgid "" +"The directory to run the command in. Note that this must be a directory " +"inside the sandbox." +msgstr "" +"De map waarin de opdracht dient te worden uitgevoerd. Let op: dit dient een " +"map binnen de sandbox te zijn." + +#: lutris/runners/flatpak.py:82 lutris/sysoptions.py:449 +msgid "Environment variables" +msgstr "Omgevingsvariabelen" + +#: lutris/runners/flatpak.py:83 +msgid "" +"Set an environment variable in the application. This overrides to the " +"Context section from the application metadata." +msgstr "" +"Stel de omgevingsvariabelen van het programma in. Deze overschrijven de " +"contextsectie in de metagegevens." -#: lutris/runners/fsuae.py:14 lutris/runners/fsuae.py:25 +#: lutris/runners/flatpak.py:97 +msgid "" +"Flatpak installation is not handled by Lutris.\n" +"Install Steam with the package provided by your distribution." +msgstr "" +"De Flatpak-installatie wordt niet afgehandeld door Lutris.\n" +"Installeer Steam middels het door je distributie geleverde pakket." + +#: lutris/runners/fsuae.py:11 msgid "Amiga 500" msgstr "Amiga 500" -#: lutris/runners/fsuae.py:15 +#: lutris/runners/fsuae.py:19 msgid "Amiga 500+" msgstr "Amiga 500+" -#: lutris/runners/fsuae.py:16 +#: lutris/runners/fsuae.py:25 msgid "Amiga 600" msgstr "Amiga 600" -#: lutris/runners/fsuae.py:17 -msgid "Amiga 1000" -msgstr "Amiga 1000" - -#: lutris/runners/fsuae.py:18 lutris/runners/fsuae.py:19 +#: lutris/runners/fsuae.py:31 msgid "Amiga 1200" msgstr "Amiga 1200" -#: lutris/runners/fsuae.py:20 +#: lutris/runners/fsuae.py:37 +#| msgid "Amiga 1000" +msgid "Amiga 3000" +msgstr "Amiga 3000" + +#: lutris/runners/fsuae.py:43 msgid "Amiga 4000" msgstr "Amiga 4000" -#: lutris/runners/fsuae.py:21 lutris/runners/fsuae.py:32 +#: lutris/runners/fsuae.py:50 +msgid "Amiga 1000" +msgstr "Amiga 1000" + +#: lutris/runners/fsuae.py:56 msgid "Amiga CD32" msgstr "Amiga CD32" -#: lutris/runners/fsuae.py:22 lutris/runners/fsuae.py:33 +#: lutris/runners/fsuae.py:65 msgid "Commodore CDTV" msgstr "Commodore CDTV" -#: lutris/runners/fsuae.py:26 -msgid "Amiga 500+ with 1 MB chip RAM" -msgstr "Amiga 500+ met 1 MB chip-ram" - -#: lutris/runners/fsuae.py:27 -msgid "Amiga 600 with 1 MB chip RAM" -msgstr "Amiga 600 met 1 MB chip-ram" - -#: lutris/runners/fsuae.py:28 -msgid "Amiga 1000 with 512 KB chip RAM" -msgstr "Amiga 1000 met 512 KB chip-ram" - -#: lutris/runners/fsuae.py:29 -msgid "Amiga 1200 with 2 MB chip RAM" -msgstr "Amiga 1200 met 2 MB chip-ram" - -#: lutris/runners/fsuae.py:30 -msgid "Amiga 1200 but with 68020 processor" -msgstr "Amiga 1200 met 68020-processor" +#: lutris/runners/fsuae.py:124 +msgid "FS-UAE" +msgstr "FS-UAE" -#: lutris/runners/fsuae.py:31 -msgid "Amiga 4000 with 2 MB chip RAM and a 68040" -msgstr "Amiga 4000 met 2 MB chip-ram en een 68040" +#: lutris/runners/fsuae.py:125 +msgid "Amiga emulator" +msgstr "Amiga-emulator" -#: lutris/runners/fsuae.py:36 +#: lutris/runners/fsuae.py:141 msgid "68000" msgstr "68000" -#: lutris/runners/fsuae.py:37 +#: lutris/runners/fsuae.py:142 msgid "68010" msgstr "68010" -#: lutris/runners/fsuae.py:38 +#: lutris/runners/fsuae.py:143 msgid "68020 with 24-bit addressing" msgstr "68020 met 24-bit addressering" -#: lutris/runners/fsuae.py:39 +#: lutris/runners/fsuae.py:144 msgid "68020" msgstr "68020" -#: lutris/runners/fsuae.py:40 +#: lutris/runners/fsuae.py:145 msgid "68030 without internal MMU" msgstr "68030 zonder interne MMU" -#: lutris/runners/fsuae.py:41 +#: lutris/runners/fsuae.py:146 msgid "68030" msgstr "68030" -#: lutris/runners/fsuae.py:42 +#: lutris/runners/fsuae.py:147 msgid "68040 without internal FPU and MMU" msgstr "68040 zonder interne FPU en MMU" -#: lutris/runners/fsuae.py:43 +#: lutris/runners/fsuae.py:148 msgid "68040 without internal FPU" msgstr "68040 zonder interne FPU" -#: lutris/runners/fsuae.py:44 +#: lutris/runners/fsuae.py:149 msgid "68040 without internal MMU" msgstr "68040 zonder interne MMU" -#: lutris/runners/fsuae.py:45 +#: lutris/runners/fsuae.py:150 msgid "68040" msgstr "68040" -#: lutris/runners/fsuae.py:46 +#: lutris/runners/fsuae.py:151 msgid "68060 without internal FPU and MMU" msgstr "68060 zonder interne FPU en MMU" -#: lutris/runners/fsuae.py:47 +#: lutris/runners/fsuae.py:152 msgid "68060 without internal FPU" msgstr "68060 zonder interne FPU" -#: lutris/runners/fsuae.py:48 +#: lutris/runners/fsuae.py:153 msgid "68060 without internal MMU" msgstr "68060 zonder interne MMU" -#: lutris/runners/fsuae.py:49 +#: lutris/runners/fsuae.py:154 msgid "68060" msgstr "68060" -#: lutris/runners/fsuae.py:53 lutris/runners/fsuae.py:60 -#: lutris/runners/fsuae.py:94 +#: lutris/runners/fsuae.py:158 lutris/runners/fsuae.py:165 +#: lutris/runners/fsuae.py:199 msgid "0" msgstr "0" -#: lutris/runners/fsuae.py:54 lutris/runners/fsuae.py:61 -#: lutris/runners/fsuae.py:95 +#: lutris/runners/fsuae.py:159 lutris/runners/fsuae.py:166 +#: lutris/runners/fsuae.py:200 msgid "1 MB" msgstr "1 MB" -#: lutris/runners/fsuae.py:55 lutris/runners/fsuae.py:62 -#: lutris/runners/fsuae.py:96 +#: lutris/runners/fsuae.py:160 lutris/runners/fsuae.py:167 +#: lutris/runners/fsuae.py:201 msgid "2 MB" msgstr "2 MB" -#: lutris/runners/fsuae.py:56 lutris/runners/fsuae.py:63 -#: lutris/runners/fsuae.py:97 +#: lutris/runners/fsuae.py:161 lutris/runners/fsuae.py:168 +#: lutris/runners/fsuae.py:202 msgid "4 MB" msgstr "4 MB" -#: lutris/runners/fsuae.py:57 lutris/runners/fsuae.py:64 -#: lutris/runners/fsuae.py:98 +#: lutris/runners/fsuae.py:162 lutris/runners/fsuae.py:169 +#: lutris/runners/fsuae.py:203 msgid "8 MB" msgstr "8 MB" -#: lutris/runners/fsuae.py:65 lutris/runners/fsuae.py:99 +#: lutris/runners/fsuae.py:170 lutris/runners/fsuae.py:204 msgid "16 MB" msgstr "16 MB" -#: lutris/runners/fsuae.py:66 lutris/runners/fsuae.py:100 +#: lutris/runners/fsuae.py:171 lutris/runners/fsuae.py:205 msgid "32 MB" msgstr "32 MB" -#: lutris/runners/fsuae.py:67 lutris/runners/fsuae.py:101 +#: lutris/runners/fsuae.py:172 lutris/runners/fsuae.py:206 msgid "64 MB" msgstr "64 MB" -#: lutris/runners/fsuae.py:68 lutris/runners/fsuae.py:102 +#: lutris/runners/fsuae.py:173 lutris/runners/fsuae.py:207 msgid "128 MB" msgstr "128 MB" -#: lutris/runners/fsuae.py:69 lutris/runners/fsuae.py:103 +#: lutris/runners/fsuae.py:174 lutris/runners/fsuae.py:208 msgid "256 MB" msgstr "256 MB" -#: lutris/runners/fsuae.py:70 +#: lutris/runners/fsuae.py:175 msgid "384 MB" msgstr "384 MB" -#: lutris/runners/fsuae.py:71 +#: lutris/runners/fsuae.py:176 msgid "512 MB" msgstr "512 MB" -#: lutris/runners/fsuae.py:72 +#: lutris/runners/fsuae.py:177 msgid "768 MB" msgstr "768 MB" -#: lutris/runners/fsuae.py:73 +#: lutris/runners/fsuae.py:178 msgid "1 GB" msgstr "1 GB" -#: lutris/runners/fsuae.py:106 +#: lutris/runners/fsuae.py:211 msgid "Turbo" msgstr "Turbo" -#: lutris/runners/fsuae.py:117 +#: lutris/runners/fsuae.py:222 msgid "Boot disk" msgstr "Opstartschijf" -#: lutris/runners/fsuae.py:120 +#: lutris/runners/fsuae.py:225 msgid "" "The main floppy disk file with the game data. \n" "FS-UAE supports floppy images in multiple file formats: ADF, IPF, DMS are " @@ -2492,35 +2591,35 @@ "Bestanden die eindigen op '.hdf' worden aangekoppeld als harde schijven. ISO-" "bestanden kunnen worden gebruikt voor Amiga CD32 en CDTV." -#: lutris/runners/fsuae.py:130 +#: lutris/runners/fsuae.py:235 msgid "Additionnal floppies" msgstr "Aanvullende diskettes" -#: lutris/runners/fsuae.py:132 +#: lutris/runners/fsuae.py:237 msgid "The additional floppy disk image(s)." msgstr "De aanvullende diskette(s)." -#: lutris/runners/fsuae.py:135 +#: lutris/runners/fsuae.py:240 msgid "CD-ROM image" msgstr "CD-ROM-schijfkopie" -#: lutris/runners/fsuae.py:137 +#: lutris/runners/fsuae.py:242 msgid "CD-ROM image to use on non CD32/CDTV models" msgstr "De te gebruiken cd-rom-schijfkopie voor niet-CD32/CDTV-modellen" -#: lutris/runners/fsuae.py:144 +#: lutris/runners/fsuae.py:249 msgid "Amiga model" msgstr "Amiga-model" -#: lutris/runners/fsuae.py:148 +#: lutris/runners/fsuae.py:253 msgid "Specify the Amiga model you want to emulate." msgstr "Kies het te emuleren Amiga-model." -#: lutris/runners/fsuae.py:152 +#: lutris/runners/fsuae.py:257 msgid "Kickstart ROMs location" msgstr "Kickstart-romlocatie" -#: lutris/runners/fsuae.py:155 +#: lutris/runners/fsuae.py:260 msgid "" "Choose the folder containing original Amiga Kickstart ROMs. Refer to FS-UAE " "documentation to find how to acquire them. Without these, FS-UAE uses a " @@ -2530,23 +2629,23 @@ "UAE-documentatie om ze te vinden. Zonder deze roms gebruikt FS-UAE een " "meegeleverde vervangende rom, welke minder compatibel is met Amiga-software." -#: lutris/runners/fsuae.py:164 +#: lutris/runners/fsuae.py:269 msgid "Extended Kickstart location" msgstr "Uitgebreide Kickstart-locatie" -#: lutris/runners/fsuae.py:167 +#: lutris/runners/fsuae.py:272 msgid "Location of extended Kickstart used for CD32" msgstr "Locatie van de uitgebreide Kickstart, zoals gebruikt door CD32" -#: lutris/runners/fsuae.py:171 +#: lutris/runners/fsuae.py:276 msgid "Fullscreen (F12 + S to switch)" msgstr "Beeldvullend (druk op F12 + s om te schakelen)" -#: lutris/runners/fsuae.py:177 lutris/runners/o2em.py:81 +#: lutris/runners/fsuae.py:282 lutris/runners/o2em.py:81 msgid "Scanlines display style" msgstr "Weergavestijl van scanlijnen" -#: lutris/runners/fsuae.py:180 lutris/runners/o2em.py:83 +#: lutris/runners/fsuae.py:285 lutris/runners/o2em.py:83 msgid "" "Activates a display filter adding scanlines to imitate the displays of " "yesteryear." @@ -2554,11 +2653,11 @@ "Schakelt een filter in dat scanlijnen toont om prehistorische schermen na te " "bootsen." -#: lutris/runners/fsuae.py:185 +#: lutris/runners/fsuae.py:290 msgid "CPU" msgstr "CPU" -#: lutris/runners/fsuae.py:190 +#: lutris/runners/fsuae.py:295 msgid "" "Use this option to override the CPU model in the emulated Amiga. All Amiga " "models imply a default CPU model, so you only need to use this option if you " @@ -2568,19 +2667,19 @@ "Amiga. Alle Amiga-modellen gebruiken een standaard cpu-model, dus gebruik " "deze optie niet te allen tijde." -#: lutris/runners/fsuae.py:196 +#: lutris/runners/fsuae.py:301 msgid "Fast Memory" msgstr "Snel geheugen" -#: lutris/runners/fsuae.py:201 +#: lutris/runners/fsuae.py:306 msgid "Specify how much Fast Memory the Amiga model should have." msgstr "Geef aan hoeveel snel geheugen het Amiga-model moet hebben." -#: lutris/runners/fsuae.py:205 +#: lutris/runners/fsuae.py:310 msgid "Zorro III RAM" msgstr "Zorro III-werkgeheugen" -#: lutris/runners/fsuae.py:210 +#: lutris/runners/fsuae.py:315 msgid "" "Override the amount of Zorro III Fast memory, specified in KB. Must be a " "multiple of 1024. The default value depends on [amiga_model]. Requires a " @@ -2591,11 +2690,11 @@ "van [amiga_model]. Een processor met een 32-bit adresseerbare bus is vereist " "(bijv. het A1200/020-model)." -#: lutris/runners/fsuae.py:216 +#: lutris/runners/fsuae.py:321 msgid "Floppy Drive Volume" msgstr "Diskettestation" -#: lutris/runners/fsuae.py:221 +#: lutris/runners/fsuae.py:326 msgid "" "Set volume to 0 to disable floppy drive clicks when the drive is empty. Max " "volume is 100." @@ -2603,11 +2702,11 @@ "Stel in op 0 om diskettekliks uit te schakelen als de schijflade leeg is. " "Max. 100 toegestaan." -#: lutris/runners/fsuae.py:226 +#: lutris/runners/fsuae.py:331 msgid "Floppy Drive Speed" msgstr "Diskettesnelheid" -#: lutris/runners/fsuae.py:232 +#: lutris/runners/fsuae.py:337 msgid "" "Set the speed of the emulated floppy drives, in percent. For example, you " "can specify 800 to get an 8x increase in speed. Use 0 to specify turbo mode. " @@ -2619,11 +2718,11 @@ "specificeren. Turbomodus leidt tot het onmiddellijk afronden van " "diskettehandelingen. Dit is op de meeste modellen 100." -#: lutris/runners/fsuae.py:240 +#: lutris/runners/fsuae.py:345 msgid "Graphics Card" msgstr "Grafische kaart" -#: lutris/runners/fsuae.py:246 +#: lutris/runners/fsuae.py:351 msgid "" "Use this option to enable a graphics card. This option is none by default, " "in which case only chipset graphics (OCS/ECS/AGA) support is available." @@ -2632,11 +2731,11 @@ "standaard op ‘geen’, waardoor alleen grafische chipseteigenschappen (OCS/ECS/" "AGA) beschikbaar zijn." -#: lutris/runners/fsuae.py:252 +#: lutris/runners/fsuae.py:357 msgid "Graphics Card RAM" msgstr "RAM-geheugen van grafische kaart" -#: lutris/runners/fsuae.py:258 +#: lutris/runners/fsuae.py:363 msgid "" "Override the amount of graphics memory on the graphics card. The 0 MB option " "is not really valid, but exists for user interface reasons." @@ -2644,15 +2743,15 @@ "Dwing het geheugen van de grafische kaart af. De optie ‘0 MB’ is ongeldig, " "maar bestaat omwille van gebruikersomgevingsredenen." -#: lutris/runners/fsuae.py:264 +#: lutris/runners/fsuae.py:369 msgid "JIT Compiler" msgstr "JIT-compilatie" -#: lutris/runners/fsuae.py:271 +#: lutris/runners/fsuae.py:376 msgid "Feral GameMode" msgstr "Feral GameMode" -#: lutris/runners/fsuae.py:275 +#: lutris/runners/fsuae.py:380 msgid "" "Automatically uses Feral GameMode daemon if available. Set to true to " "disable the feature." @@ -2660,11 +2759,11 @@ "Gebruikt automatisch Feral GameMode als deze beschikbaar is. Kies ‘true’ om " "uit te schakelen." -#: lutris/runners/fsuae.py:280 +#: lutris/runners/fsuae.py:385 msgid "CPU governor warning" msgstr "CPU-governorwaarschuwing" -#: lutris/runners/fsuae.py:285 +#: lutris/runners/fsuae.py:390 msgid "" "Warn if running with a CPU governor other than performance. Set to true to " "disable the warning." @@ -2672,7 +2771,7 @@ "Toon een waarschuwing als de cpu-governor is ingesteld op een andere waarde " "dan ‘performance’. Kies ‘true’ om uit te schakelen." -#: lutris/runners/fsuae.py:290 +#: lutris/runners/fsuae.py:395 msgid "UAE bsdsocket.library" msgstr "UAE bsdsocket.library" @@ -2833,31 +2932,31 @@ msgid "Resolution" msgstr "Resolutie" -#: lutris/runners/libretro.py:65 +#: lutris/runners/libretro.py:64 msgid "Libretro" msgstr "Libretro" -#: lutris/runners/libretro.py:66 +#: lutris/runners/libretro.py:65 msgid "Multi-system emulator" msgstr "Emulator van meerdere systemen" -#: lutris/runners/libretro.py:79 +#: lutris/runners/libretro.py:78 msgid "Core" msgstr "Kern" -#: lutris/runners/libretro.py:88 lutris/runners/zdoom.py:87 +#: lutris/runners/libretro.py:87 lutris/runners/zdoom.py:87 msgid "Config file" msgstr "Configuratiebestand" -#: lutris/runners/libretro.py:100 +#: lutris/runners/libretro.py:99 msgid "Verbose logging" msgstr "Uitgebreid logboek aanleggen" -#: lutris/runners/libretro.py:271 +#: lutris/runners/libretro.py:270 msgid "No core has been selected for this game" msgstr "Geen kern gekozen voor deze game" -#: lutris/runners/libretro.py:282 +#: lutris/runners/libretro.py:281 msgid "No game file specified" msgstr "Geen gamebestand opgegeven" @@ -3716,11 +3815,11 @@ msgid "Path to EBOOT.BIN" msgstr "Locatie van EBOOT.BIN" -#: lutris/runners/runner.py:148 +#: lutris/runners/runner.py:154 msgid "Custom executable for the runner" msgstr "Aangepast uitvoerbaar bestand voor de runner" -#: lutris/runners/runner.py:293 +#: lutris/runners/runner.py:299 msgid "" "The required runner is not installed.\n" "Do you wish to install it now?" @@ -3728,7 +3827,7 @@ "De vereiste runner is niet geïnstalleerd.\n" "Wil je deze nu installeren?" -#: lutris/runners/runner.py:295 +#: lutris/runners/runner.py:301 msgid "Required runner unavailable" msgstr "Vereiste runner is niet beschikbaar" @@ -4049,10 +4148,6 @@ msgid "Runs Steam for Linux games" msgstr "Hiermee speel je Steam voor Linux-games" -#: lutris/runners/steam.py:35 -msgid "Application ID" -msgstr "Programma-id" - #: lutris/runners/steam.py:38 msgid "" "The application ID can be retrieved from the game's page at steampowered." @@ -4777,7 +4872,7 @@ msgid "Custom directory for desktop integration folders." msgstr "Aangepaste map voor de bureaubladintegratiemappen." -#: lutris/runners/wine.py:490 +#: lutris/runners/wine.py:491 msgid "Run EXE inside Wine prefix" msgstr "EXE uitvoeren op Wine-profiel" @@ -4785,27 +4880,27 @@ msgid "Wine configuration" msgstr "Wine-instellingen" -#: lutris/runners/wine.py:494 +#: lutris/runners/wine.py:493 msgid "Open Bash terminal" msgstr "Bash-terminalvenster openen" -#: lutris/runners/wine.py:495 +#: lutris/runners/wine.py:494 msgid "Open Wine console" msgstr "Wine-opdrachtprompt openen" -#: lutris/runners/wine.py:496 +#: lutris/runners/wine.py:495 msgid "Wine registry" msgstr "Wine-register" -#: lutris/runners/wine.py:497 +#: lutris/runners/wine.py:496 msgid "Winetricks" msgstr "Winetricks" -#: lutris/runners/wine.py:498 +#: lutris/runners/wine.py:497 msgid "Wine Control Panel" msgstr "Wine-configuratiescherm" -#: lutris/runners/wine.py:661 +#: lutris/runners/wine.py:659 msgid "Select an EXE or MSI file" msgstr "Kies een exe- of msi-bestand" @@ -4903,18 +4998,37 @@ "laden. Het bestand moet een wad-mappenlijst bevatten, anders start de game " "niet." +#: lutris/services/amazon.py:58 lutris/services/amazon.py:637 +msgid "Amazon Prime Gaming" +msgstr "Amazon Prime Gaming" + +#: lutris/services/amazon.py:624 +#, python-format +msgid "Installing file: %s" +msgstr "Bezig met installeren van bestand: %s" + +#: lutris/services/base.py:310 +#, python-format +msgid "" +"This service requires a game launcher. The following steps will install it.\n" +"Once the client is installed, you can login to %s." +msgstr "" +"Voor het gebruik van deze dienst is een starter vereist, welke hierna wordt " +"geïnstalleerd.\n" +"Na het installeren kun je inloggen op %s." + #: lutris/services/battlenet.py:13 msgid "Battle.net" msgstr "Battle.net" -#: lutris/services/bethesda.py:13 -msgid "Bethesda" -msgstr "Bethesda" - #: lutris/services/egs.py:134 msgid "Epic Games Store" msgstr "Epic Games Store" +#: lutris/services/flathub.py:56 +msgid "Flathub" +msgstr "Flathub" + #: lutris/services/gog.py:68 msgid "GOG" msgstr "GOG" @@ -5005,44 +5119,52 @@ msgid "Keep current" msgstr "Huidige behouden" -#: lutris/sysoptions.py:37 lutris/sysoptions.py:46 lutris/sysoptions.py:57 -#: lutris/sysoptions.py:501 +#: lutris/sysoptions.py:43 +msgid "(recommended)" +msgstr "(aanbevolen)" + +#: lutris/sysoptions.py:46 +msgid "System" +msgstr "Systeem" + +#: lutris/sysoptions.py:55 lutris/sysoptions.py:64 lutris/sysoptions.py:75 +#: lutris/sysoptions.py:539 msgid "Off" msgstr "Uit" -#: lutris/sysoptions.py:38 +#: lutris/sysoptions.py:56 msgid "Primary" msgstr "Primair" -#: lutris/sysoptions.py:85 +#: lutris/sysoptions.py:101 msgid "Auto: WARNING -- No Vulkan Loader detected!" msgstr "Automatisch: WAARSCHUWING -- er is geen Vulkan-loader aangetroffen!" -#: lutris/sysoptions.py:119 +#: lutris/sysoptions.py:127 msgid "Auto: Intel Open Source (MESA: ANV)" msgstr "Automatisch: Intel Open Source (MESA: ANV)" -#: lutris/sysoptions.py:121 +#: lutris/sysoptions.py:128 msgid "Auto: AMD RADV Open Source (MESA: RADV)" msgstr "Automatisch: AMD RADV Open Source (MESA: RADV)" -#: lutris/sysoptions.py:123 +#: lutris/sysoptions.py:129 msgid "Auto: Nvidia Proprietary" msgstr "Automatisch: Nvidia (gesloten stuurprogramma)" -#: lutris/sysoptions.py:145 +#: lutris/sysoptions.py:172 msgid "Default installation folder" msgstr "Standaard installatiemap" -#: lutris/sysoptions.py:148 +#: lutris/sysoptions.py:175 msgid "The default folder where you install your games." msgstr "De standaardmap waarin je games worden geïnstalleerd." -#: lutris/sysoptions.py:156 +#: lutris/sysoptions.py:183 msgid "Disable Lutris Runtime" msgstr "Lutris-runtime uitschakelen" -#: lutris/sysoptions.py:159 +#: lutris/sysoptions.py:186 msgid "" "The Lutris Runtime loads some libraries before running the game, which can " "cause some incompatibilities in some cases. Check this option to disable it." @@ -5051,22 +5173,22 @@ "dit leiden tot incompatibiliteit. Kruis deze optie aan om de runtime uit te " "schakelen." -#: lutris/sysoptions.py:166 +#: lutris/sysoptions.py:193 msgid "Prefer system libraries" msgstr "Voorkeur voor systeembibliotheken" -#: lutris/sysoptions.py:168 +#: lutris/sysoptions.py:195 msgid "" "When the runtime is enabled, prioritize the system libraries over the " "provided ones." msgstr "" "Geef voorrang aan systeembibliotheken, ook als de runtime is ingeschakeld." -#: lutris/sysoptions.py:174 +#: lutris/sysoptions.py:201 msgid "Restore resolution on game exit" msgstr "Resolutie herstellen na beëindigen van game" -#: lutris/sysoptions.py:176 +#: lutris/sysoptions.py:203 msgid "" "Some games don't restore your screen resolution when \n" "closed or when they crash. This is when this option comes \n" @@ -5076,11 +5198,11 @@ "worden afgesloten of crashen. Met deze optie wordt de\n" "resolutie altijd hersteld." -#: lutris/sysoptions.py:183 +#: lutris/sysoptions.py:210 msgid "Enable gamescope" msgstr "Gamescope inschakelen" -#: lutris/sysoptions.py:187 +#: lutris/sysoptions.py:214 msgid "" "Use gamescope to draw the game window isolated from your desktop.\n" "Use Ctrl+Super+F to toggle fullscreen" @@ -5088,35 +5210,35 @@ "Gebruik gamescope om een venster te isoleren van je werkblad.\n" "Druk op Ctrl+Super+F om de beeldvullende modus in/uit te schakelen." -#: lutris/sysoptions.py:193 +#: lutris/sysoptions.py:220 msgid "Gamescope output resolution" msgstr "Gamescope-resolutie" -#: lutris/sysoptions.py:197 +#: lutris/sysoptions.py:224 msgid "Resolution of the window on your desktop" msgstr "De resolutie van het venster op je werkblad" -#: lutris/sysoptions.py:202 +#: lutris/sysoptions.py:229 msgid "Gamescope game resolution" msgstr "Gamescope-gameresolutie" -#: lutris/sysoptions.py:206 +#: lutris/sysoptions.py:233 msgid "Resolution of the screen visible to the game" msgstr "De resolutie van het scherm die de game doorkrijgt" -#: lutris/sysoptions.py:211 +#: lutris/sysoptions.py:238 msgid "Restrict number of cores used" msgstr "Aantal kernen beperken" -#: lutris/sysoptions.py:214 +#: lutris/sysoptions.py:241 msgid "Restrict the game to a maximum number of CPU cores." msgstr "Beperkt de game tot het gebruik van een maximumaantal cpu-kernen." -#: lutris/sysoptions.py:219 +#: lutris/sysoptions.py:246 msgid "Restrict number of cores to" msgstr "Aantal kernen beperken tot" -#: lutris/sysoptions.py:222 +#: lutris/sysoptions.py:249 msgid "" "Maximum number of CPU cores to be used, if 'Restrict number of cores used' " "is turned on." @@ -5124,11 +5246,11 @@ "Beperkt de game tot het gebruik van een maximumaantal cpu-kernen als ‘Aantal " "kernen beperken’ is ingeschakeld." -#: lutris/sysoptions.py:228 +#: lutris/sysoptions.py:255 msgid "Restore gamma on game exit" msgstr "Gamma herstellen na beëindigen van game" -#: lutris/sysoptions.py:230 +#: lutris/sysoptions.py:257 msgid "" "Some games don't correctly restores gamma on exit, making your display too " "bright. Select this option to correct it." @@ -5136,11 +5258,11 @@ "Sommige gamels herstellen het gamma niet als ze worden afgesloten, waardoor " "je helderheid te hoog staat. Kruis deze optie aan om dat te voorkomen." -#: lutris/sysoptions.py:235 +#: lutris/sysoptions.py:262 msgid "Disable desktop effects" msgstr "Bureaubladeffecten uitschakelen" -#: lutris/sysoptions.py:239 +#: lutris/sysoptions.py:266 msgid "" "Disable desktop effects while game is running, reducing stuttering and " "increasing performance" @@ -5148,11 +5270,11 @@ "Schakel bureaubladeffecten uit zolang de game actief is, om lag en traagheid " "te voorkomen" -#: lutris/sysoptions.py:244 +#: lutris/sysoptions.py:271 msgid "Disable screen saver" msgstr "Schermbeveiliging uitschakelen" -#: lutris/sysoptions.py:249 +#: lutris/sysoptions.py:276 msgid "" "Disable the screen saver while a game is running. Requires the screen " "saver's functionality to be exposed over DBus." @@ -5160,19 +5282,19 @@ "Schakel de schermbeveiliging uit tijdens het games van een game. Hiervoor " "dient de functionaliteit te worden doorgegeven middels DBus." -#: lutris/sysoptions.py:256 +#: lutris/sysoptions.py:283 msgid "Reset PulseAudio" msgstr "PulseAudio herstarten" -#: lutris/sysoptions.py:260 +#: lutris/sysoptions.py:287 msgid "Restart PulseAudio before launching the game." msgstr "Herstart PulseAudio voordat de game start." -#: lutris/sysoptions.py:265 +#: lutris/sysoptions.py:292 msgid "Reduce PulseAudio latency" msgstr "PulseAudio-vertraging verminderen" -#: lutris/sysoptions.py:269 +#: lutris/sysoptions.py:296 msgid "" "Set the environment variable PULSE_LATENCY_MSEC=60 to improve audio quality " "on some games" @@ -5180,20 +5302,20 @@ "Stelt de omgevingsvariabel PULSE_LATENCY_MSEC=60 in om de audiokwaliteit van " "sommige games te verbeteren" -#: lutris/sysoptions.py:275 +#: lutris/sysoptions.py:302 msgid "Switch to US keyboard layout" msgstr "Overschakelen naar Amerikaanse toetsenbordindeling" -#: lutris/sysoptions.py:278 +#: lutris/sysoptions.py:305 msgid "Switch to US keyboard QWERTY layout while game is running" msgstr "" "Schakel over naar de Amerikaanse QWERTY-indeling zolang de game actief is" -#: lutris/sysoptions.py:285 +#: lutris/sysoptions.py:312 msgid "Optimus launcher (NVIDIA Optimus laptops)" msgstr "Optimus-starter (NVIDIA Optimus-laptops)" -#: lutris/sysoptions.py:287 +#: lutris/sysoptions.py:314 msgid "" "If you have installed the primus or bumblebee packages, select what launcher " "will run the game with the command, activating your NVIDIA graphic chip for " @@ -5207,11 +5329,11 @@ "maar optirun/virtualgl werkt met meer games. Primus VK biedt Vulkan-" "ondersteuning voor Bumblebee." -#: lutris/sysoptions.py:299 +#: lutris/sysoptions.py:326 msgid "Vulkan ICD loader" msgstr "Vulkan ICD-loader" -#: lutris/sysoptions.py:301 +#: lutris/sysoptions.py:328 msgid "" "The ICD loader is a library that is placed between a Vulkan application and " "any number of Vulkan drivers, in order to support multiple drivers and the " @@ -5221,11 +5343,11 @@ "Zo worden meerdere stuurprogramma's en instanties ondersteund tussen " "stuurprogramma's." -#: lutris/sysoptions.py:309 +#: lutris/sysoptions.py:336 msgid "FPS counter (MangoHud)" msgstr "FPS-teller (MangoHud)" -#: lutris/sysoptions.py:312 +#: lutris/sysoptions.py:339 msgid "" "Display the game's FPS + other information. Requires MangoHud to be " "installed." @@ -5233,23 +5355,23 @@ "Toont de fps van een game, evenals andere technische informatie. MangoHud is " "hiervoor vereist." -#: lutris/sysoptions.py:321 +#: lutris/sysoptions.py:348 msgid "Limit the game's FPS to desired number" msgstr "Stel een FPS-limiet in" -#: lutris/sysoptions.py:328 +#: lutris/sysoptions.py:355 msgid "Enable Feral GameMode" msgstr "Feral GameMode gebruiken" -#: lutris/sysoptions.py:329 +#: lutris/sysoptions.py:356 msgid "Request a set of optimisations be temporarily applied to the host OS" msgstr "Past tijdelijk een set optimalisaties toe op het hostsysteem" -#: lutris/sysoptions.py:336 +#: lutris/sysoptions.py:363 msgid "Enable NVIDIA Prime Render Offload" msgstr "NVIDIA Prime render offload gebruiken" -#: lutris/sysoptions.py:337 +#: lutris/sysoptions.py:364 msgid "" "If you have the latest NVIDIA driver and the properly patched xorg-server " "(see https://download.nvidia.com/XFree86/Linux-x86_64/435.17/README/" @@ -5263,11 +5385,11 @@ "door deze optie in te schakelen. Dit past __NV_PRIME_RENDER_OFFLOAD=1 en " "__GLX_VENDOR_LIBRARY_NAME=nvidia toe." -#: lutris/sysoptions.py:348 +#: lutris/sysoptions.py:375 msgid "Use discrete graphics" msgstr "Toegepaste videokaart gebruiken" -#: lutris/sysoptions.py:350 +#: lutris/sysoptions.py:377 msgid "" "If you have open source graphic drivers (Mesa), selecting this option will " "run the game with the 'DRI_PRIME=1' environment variable, activating your " @@ -5277,11 +5399,11 @@ "deze optie met ‘DRI_PRIME=1’, waardoor je toegepaste videokaart (bijv. " "Nvidia) gebruikt wordt voor betere prestaties." -#: lutris/sysoptions.py:358 +#: lutris/sysoptions.py:385 msgid "SDL 1.2 Fullscreen Monitor" msgstr "SDL 1.2 beeldvullend scherm" -#: lutris/sysoptions.py:362 +#: lutris/sysoptions.py:389 msgid "" "Hint SDL 1.2 games to use a specific monitor when going fullscreen by " "setting the SDL_VIDEO_FULLSCREEN environment variable" @@ -5290,11 +5412,11 @@ "gebruikt om de beeldvullende modus in te schakelen middels " "SDL_VIDEO_FULLSCREEN" -#: lutris/sysoptions.py:369 +#: lutris/sysoptions.py:396 msgid "Turn off monitors except" msgstr "Beeldschermen uitschakelen, behalve" -#: lutris/sysoptions.py:374 +#: lutris/sysoptions.py:401 msgid "" "Only keep the selected screen active while the game is running. \n" "This is useful if you have a dual-screen setup, and are \n" @@ -5304,19 +5426,19 @@ "Dit is nuttig als je meer dan één scherm hebt en problemen hebt\n" "met beeldvullende games." -#: lutris/sysoptions.py:382 +#: lutris/sysoptions.py:409 msgid "Switch resolution to" msgstr "Resolutie instellen op" -#: lutris/sysoptions.py:386 +#: lutris/sysoptions.py:413 msgid "Switch to this screen resolution while the game is running." msgstr "Stel de resolutie hier op in zolang de game actief is." -#: lutris/sysoptions.py:390 +#: lutris/sysoptions.py:417 msgid "CLI mode" msgstr "CLI-modus" -#: lutris/sysoptions.py:394 +#: lutris/sysoptions.py:421 msgid "" "Enable a terminal for text-based games. Only useful for ASCII based games. " "May cause issues with graphical games." @@ -5324,11 +5446,11 @@ "Gebruik een terminal bij tekstgebaseerde games (alleen nuttig bij ascii-" "games). Let op: dit kan problemen veroorzaken in grafische games." -#: lutris/sysoptions.py:399 +#: lutris/sysoptions.py:426 msgid "Text based games emulator" msgstr "Tekstgebaseerde game-emulator" -#: lutris/sysoptions.py:404 +#: lutris/sysoptions.py:431 msgid "" "The terminal emulator used with the CLI mode. Choose from the list of " "detected terminal apps or enter the terminal's command or path." @@ -5337,69 +5459,77 @@ "een lijst met geïnstalleerde programma's of voer de opdracht of locatie in. " "Let op: niet alle emulators werken." -#: lutris/sysoptions.py:411 -msgid "Environment variables" -msgstr "Omgevingsvariabelen" +#: lutris/sysoptions.py:438 +msgid "Locale" +msgstr "Taal" + +#: lutris/sysoptions.py:444 +msgid "" +"Can be used to force certain locale for an app. Fixes encoding issues in " +"legacy software." +msgstr "" +"Kan worden gebruikt om een bepaalde taal in een game te gebruiken. Dit lost " +"tekensetproblemen in oude software op." -#: lutris/sysoptions.py:412 +#: lutris/sysoptions.py:450 msgid "Environment variables loaded at run time" msgstr "De tezamen met de game toe te passen omgevingsvariabelen" -#: lutris/sysoptions.py:417 +#: lutris/sysoptions.py:455 msgid "AntiMicroX Profile" msgstr "AntiMicroX-profiel" -#: lutris/sysoptions.py:419 +#: lutris/sysoptions.py:457 msgid "Path to an AntiMicroX profile file" msgstr "Locatie van een AntiMicroX-profielbestand" -#: lutris/sysoptions.py:424 +#: lutris/sysoptions.py:462 msgid "Command prefix" msgstr "Opdrachtregelopties" -#: lutris/sysoptions.py:426 +#: lutris/sysoptions.py:464 msgid "" "Command line instructions to add in front of the game's execution command." msgstr "" "De opdrachtregelopties die vóór de game-opdracht moeten worden toegekend." -#: lutris/sysoptions.py:432 +#: lutris/sysoptions.py:470 msgid "Manual script" msgstr "Aangepast script" -#: lutris/sysoptions.py:434 +#: lutris/sysoptions.py:472 msgid "Script to execute from the game's contextual menu" msgstr "Het uit te voeren script middels het rechtermuisknopmenu van de game" -#: lutris/sysoptions.py:439 +#: lutris/sysoptions.py:477 msgid "Pre-launch script" msgstr "Voortijdig script" -#: lutris/sysoptions.py:441 +#: lutris/sysoptions.py:479 msgid "Script to execute before the game starts" msgstr "Het script dat moet worden uitgevoerd voordat de game start" -#: lutris/sysoptions.py:446 +#: lutris/sysoptions.py:484 msgid "Wait for pre-launch script completion" msgstr "Wachten tot voortijdig script is afgerond" -#: lutris/sysoptions.py:449 +#: lutris/sysoptions.py:487 msgid "Run the game only once the pre-launch script has exited" msgstr "Start de game alleen als het script is afgerond" -#: lutris/sysoptions.py:454 +#: lutris/sysoptions.py:492 msgid "Post-exit script" msgstr "Afsluitend script" -#: lutris/sysoptions.py:456 +#: lutris/sysoptions.py:494 msgid "Script to execute when the game exits" msgstr "Het script dat moet worden uitgevoerd nadat de game is afgesloten" -#: lutris/sysoptions.py:461 +#: lutris/sysoptions.py:499 msgid "Include processes" msgstr "In te sluiten deze processen" -#: lutris/sysoptions.py:463 +#: lutris/sysoptions.py:501 msgid "" "What processes to include in process monitoring. This is to override the " "built-in exclude list.\n" @@ -5411,11 +5541,11 @@ "De lijst is spatiegescheiden. Processen met spaties dienen voorzien te zijn " "van dubbele aanhalingstekens." -#: lutris/sysoptions.py:471 +#: lutris/sysoptions.py:509 msgid "Exclude processes" msgstr "Uit te sluiten processen" -#: lutris/sysoptions.py:473 +#: lutris/sysoptions.py:511 msgid "" "What processes to exclude in process monitoring. For example background " "processes that stick around after the game has been closed.\n" @@ -5428,11 +5558,11 @@ "De lijst is spatiegescheiden. Processen met spaties dienen voorzien te zijn " "van dubbele aanhalingstekens." -#: lutris/sysoptions.py:482 +#: lutris/sysoptions.py:520 msgid "Killswitch file" msgstr "Killswitch-bestand" -#: lutris/sysoptions.py:484 +#: lutris/sysoptions.py:522 msgid "" "Path to a file which will stop the game when deleted \n" "(usually /dev/input/js0 to stop the game on joystick unplugging)" @@ -5441,11 +5571,11 @@ "(doorgaans is dit /dev/input/js0 om het game stop te zetten bij het " "afkoppelen van joysticks)" -#: lutris/sysoptions.py:491 +#: lutris/sysoptions.py:529 msgid "SDL2 gamepad mapping" msgstr "SDL2-gamepadtoewijzing" -#: lutris/sysoptions.py:493 +#: lutris/sysoptions.py:531 msgid "" "SDL_GAMECONTROLLERCONFIG mapping string or path to a custom gamecontrollerdb." "txt file containing mappings." @@ -5453,64 +5583,64 @@ "SDL_GAMECONTROLLERCONFIG-toewijzing, in tekenreeks of locatie, naar een " "aangepast gamecontrollerdb.txt-bestand dat de toewijzingen bevat." -#: lutris/sysoptions.py:498 +#: lutris/sysoptions.py:536 msgid "Use Xephyr" msgstr "Xephyr gebruiken" -#: lutris/sysoptions.py:502 +#: lutris/sysoptions.py:540 msgid "8BPP (256 colors)" msgstr "8BPP (256 kleuren)" -#: lutris/sysoptions.py:503 +#: lutris/sysoptions.py:541 msgid "16BPP (65536 colors)" msgstr "16BPP (65536 kleuren)" -#: lutris/sysoptions.py:504 +#: lutris/sysoptions.py:542 msgid "24BPP (16M colors)" msgstr "24BPP (16M kleuren)" -#: lutris/sysoptions.py:508 +#: lutris/sysoptions.py:546 msgid "Run program in Xephyr to support 8BPP and 16BPP color modes" msgstr "" "Start een game met Xephyr om 8BPP- en 16BPP-kleurenmodi te kunnen gebruiken" -#: lutris/sysoptions.py:513 +#: lutris/sysoptions.py:551 msgid "Xephyr resolution" msgstr "Xephyr-resolutie" -#: lutris/sysoptions.py:515 +#: lutris/sysoptions.py:553 msgid "Screen resolution of the Xephyr server" msgstr "Schermresolutie van de Xephyr-server" -#: lutris/sysoptions.py:520 +#: lutris/sysoptions.py:558 msgid "Xephyr Fullscreen" msgstr "Xephyr beeldvullend" -#: lutris/sysoptions.py:523 +#: lutris/sysoptions.py:561 msgid "Open Xephyr in fullscreen (at the desktop resolution)" msgstr "Xephyr op volledig scherm openen (op bureaubladresolutie)" -#: lutris/util/system.py:20 +#: lutris/util/system.py:21 msgid "Documents" msgstr "Documenten" -#: lutris/util/system.py:21 +#: lutris/util/system.py:22 msgid "Downloads" msgstr "Downloads" -#: lutris/util/system.py:22 +#: lutris/util/system.py:23 msgid "Desktop" msgstr "Bureaublad" -#: lutris/util/system.py:23 lutris/util/system.py:25 +#: lutris/util/system.py:24 lutris/util/system.py:26 msgid "Pictures" msgstr "Afbeeldingen" -#: lutris/util/system.py:24 +#: lutris/util/system.py:25 msgid "Videos" msgstr "Video's" -#: lutris/util/system.py:26 +#: lutris/util/system.py:27 msgid "Projects" msgstr "Projecten" @@ -5586,6 +5716,33 @@ "De gekozen Wine-versie heeft geen ondersteuning voor Fsync.\n" "Kies een met fsync compatibele versie." +#~ msgid "video game preservation platform" +#~ msgstr "videogame-bewaarplatform" + +#~ msgid "Show Tray Icon (requires restart)" +#~ msgstr "Systeemvakpictogram tonen (herstart vereist)" + +#~ msgid "Amiga 500+ with 1 MB chip RAM" +#~ msgstr "Amiga 500+ met 1 MB chip-ram" + +#~ msgid "Amiga 600 with 1 MB chip RAM" +#~ msgstr "Amiga 600 met 1 MB chip-ram" + +#~ msgid "Amiga 1000 with 512 KB chip RAM" +#~ msgstr "Amiga 1000 met 512 KB chip-ram" + +#~ msgid "Amiga 1200 with 2 MB chip RAM" +#~ msgstr "Amiga 1200 met 2 MB chip-ram" + +#~ msgid "Amiga 1200 but with 68020 processor" +#~ msgstr "Amiga 1200 met 68020-processor" + +#~ msgid "Amiga 4000 with 2 MB chip RAM and a 68040" +#~ msgstr "Amiga 4000 met 2 MB chip-ram en een 68040" + +#~ msgid "Bethesda" +#~ msgstr "Bethesda" + #~ msgid "Runner not installed." #~ msgstr "De runner is niet geïnstalleerd." @@ -5977,9 +6134,6 @@ #~ msgid "Run the game in a new terminal window." #~ msgstr "Start het spel in een terminalvenster." -#~ msgid "Terminal application" -#~ msgstr "Terminaltoepassing" - #~ msgid "Discord Rich Presence" #~ msgstr "Status tonen op Discord" diff -Nru lutris-0.5.11~ubuntu22.04.1/po/POTFILES lutris-0.5.12~ubuntu22.04.1/po/POTFILES --- lutris-0.5.11~ubuntu22.04.1/po/POTFILES 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/po/POTFILES 2022-12-03 12:25:25.000000000 +0000 @@ -1,4 +1,4 @@ -# generated on 2022-08-21T09:17:44+00:00 +# generated on 2022-09-19T02:53:10+00:00 share/applications/net.lutris.Lutris.desktop share/metainfo/net.lutris.Lutris.metainfo.xml @@ -19,7 +19,6 @@ lutris/database/services.py lutris/database/sources.py lutris/database/sql.py -lutris/discord.py lutris/exceptions.py lutris/game_actions.py lutris/game.py @@ -90,6 +89,7 @@ lutris/migrations/migrate_banners.py lutris/migrations/migrate_hidden_ids.py lutris/migrations/migrate_steam_appids.py +lutris/migrations/retrieve_discord_appids.py lutris/runner_interpreter.py lutris/runners/atari800.py lutris/runners/commands/dosbox.py @@ -163,7 +163,10 @@ lutris/util/audio.py lutris/util/cookies.py lutris/util/datapath.py -lutris/util/discord.py +lutris/util/discord/base.py +lutris/util/discord/client.py +lutris/util/discord/__init__.py +lutris/util/discord/rpc.py lutris/util/display.py lutris/util/dolphin/cache_reader.py lutris/util/dolphin/__init__.py diff -Nru lutris-0.5.11~ubuntu22.04.1/po/README.md lutris-0.5.12~ubuntu22.04.1/po/README.md --- lutris-0.5.11~ubuntu22.04.1/po/README.md 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/po/README.md 2022-12-03 12:25:25.000000000 +0000 @@ -4,6 +4,18 @@ Note: All the commands below need to be run in the project root directory, not in the `po` directory. Otherwise you may get `Not the project root` error in meson. +## Update POTFILES + +Before you start translating, you may want to update `POTFILES`, which contains a list of all source files that need to be translated. + +If someone deletes or renames some file, it has to be updated, otherwise "No such file or directory" will throw. + +Run the following command to update: + +``` +./po/generate-potfiles.sh +``` + ## Updating a translations ```bash diff -Nru lutris-0.5.11~ubuntu22.04.1/po/zh_CN.po lutris-0.5.12~ubuntu22.04.1/po/zh_CN.po --- lutris-0.5.11~ubuntu22.04.1/po/zh_CN.po 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/po/zh_CN.po 2022-12-03 12:25:25.000000000 +0000 @@ -3,31 +3,35 @@ msgstr "" "Project-Id-Version: lutris\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-20 16:54+0200\n" -"PO-Revision-Date: 2022-06-15 01:42+0800\n" +"POT-Creation-Date: 2022-09-19 10:32+0800\n" +"PO-Revision-Date: 2022-09-19 10:33+0800\n" "Last-Translator: Yidaozhan Ya \n" "Language-Team: Chinese \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.2.1\n" +"X-Generator: Poedit 2.3\n" "Plural-Forms: nplurals=1; plural=0;\n" #: share/applications/net.lutris.Lutris.desktop:3 #: share/lutris/ui/lutris-window.ui:25 lutris/gui/application.py:76 -#: lutris/gui/widgets/status_icon.py:116 lutris/services/lutris.py:39 +#: lutris/gui/widgets/status_icon.py:127 lutris/services/lutris.py:39 msgid "Lutris" msgstr "Lutris" #: share/applications/net.lutris.Lutris.desktop:4 -msgid "video game preservation platform" -msgstr "电子游戏保管平台" +msgid "Video Game Preservation Platform" +msgstr "电子游戏保存平台" #: share/applications/net.lutris.Lutris.desktop:6 msgid "gaming;wine;emulator;" msgstr "游戏;wine;模拟器;" +#: share/applications/net.lutris.Lutris.desktop:8 +msgid "lutris" +msgstr "lutris" + #: share/metainfo/net.lutris.Lutris.metainfo.xml:11 #: share/lutris/ui/about-dialog.ui:18 msgid "Video game preservation platform" @@ -48,7 +52,7 @@ "器、兼容层、第三方游戏引擎等进行整合利用,Lutris 可为您提供一个统一的界面来启" "动您的所有游戏。" -#: share/lutris/ui/about-dialog.ui:8 share/lutris/ui/lutris-window.ui:581 +#: share/lutris/ui/about-dialog.ui:8 share/lutris/ui/lutris-window.ui:582 msgid "About Lutris" msgstr "关于 Lutris" @@ -84,8 +88,8 @@ msgid "Forgot password?" msgstr "忘记密码?" -#: share/lutris/ui/dialog-lutris-login.ui:43 lutris/gui/dialogs/issue.py:52 -#: lutris/gui/installerwindow.py:76 +#: share/lutris/ui/dialog-lutris-login.ui:43 lutris/gui/dialogs/issue.py:49 +#: lutris/gui/installerwindow.py:78 msgid "C_ancel" msgstr "取消 (_A)" @@ -118,14 +122,14 @@ msgstr "切换显示模式" #: share/lutris/ui/lutris-window.ui:299 -msgid "Zoom:" -msgstr "缩放:" +msgid "Zoom " +msgstr "缩放 " #: share/lutris/ui/lutris-window.ui:352 msgid "Sort Ascending" msgstr "升序/降序" -#: share/lutris/ui/lutris-window.ui:367 lutris/gui/config/common.py:113 +#: share/lutris/ui/lutris-window.ui:367 lutris/gui/config/common.py:98 #: lutris/gui/views/list.py:45 lutris/gui/views/list.py:159 msgid "Name" msgstr "名称" @@ -154,27 +158,27 @@ msgid "Show _Hidden Games" msgstr "显示已隐藏的游戏 (_H)" -#: share/lutris/ui/lutris-window.ui:485 +#: share/lutris/ui/lutris-window.ui:486 msgid "Show Side _Panel" msgstr "显示边栏 (_P)" -#: share/lutris/ui/lutris-window.ui:500 +#: share/lutris/ui/lutris-window.ui:501 msgid "Add games" msgstr "添加游戏" -#: share/lutris/ui/lutris-window.ui:514 +#: share/lutris/ui/lutris-window.ui:515 msgid "Preferences" msgstr "首选项" -#: share/lutris/ui/lutris-window.ui:539 +#: share/lutris/ui/lutris-window.ui:540 msgid "Discord" msgstr "Discord 讨论组" -#: share/lutris/ui/lutris-window.ui:553 +#: share/lutris/ui/lutris-window.ui:554 msgid "Lutris forums" msgstr "Lutris 论坛" -#: share/lutris/ui/lutris-window.ui:567 +#: share/lutris/ui/lutris-window.ui:568 msgid "Make a donation" msgstr "捐助" @@ -199,7 +203,7 @@ msgid "Stop" msgstr "停止" -#: lutris/game_actions.py:62 lutris/gui/dialogs/runner_install.py:193 +#: lutris/game_actions.py:62 lutris/gui/dialogs/runner_install.py:194 #: lutris/gui/installer/script_box.py:62 lutris/gui/widgets/game_bar.py:210 msgid "Install" msgstr "安装" @@ -240,7 +244,7 @@ msgid "Browse files" msgstr "打开安装目录" -#: lutris/game_actions.py:75 lutris/gui/installerwindow.py:178 +#: lutris/game_actions.py:75 lutris/gui/installerwindow.py:180 msgid "Create desktop shortcut" msgstr "创建桌面快捷方式" @@ -248,7 +252,7 @@ msgid "Delete desktop shortcut" msgstr "删除桌面快捷方式" -#: lutris/game_actions.py:85 lutris/gui/installerwindow.py:182 +#: lutris/game_actions.py:85 lutris/gui/installerwindow.py:184 msgid "Create application menu shortcut" msgstr "创建应用列表快捷方式" @@ -256,7 +260,7 @@ msgid "Delete application menu shortcut" msgstr "删除应用列表快捷方式" -#: lutris/game_actions.py:95 lutris/gui/installerwindow.py:187 +#: lutris/game_actions.py:95 lutris/gui/installerwindow.py:189 msgid "Create steam shortcut" msgstr "创建 Steam 快捷方式" @@ -315,59 +319,59 @@ "无法打开 %s \n" "目录不存在。" -#: lutris/game.py:178 +#: lutris/game.py:182 msgid "Error the runner is not installed" msgstr "错误:运行环境未安装" -#: lutris/game.py:180 +#: lutris/game.py:184 msgid "A bios file is required to run this game" msgstr "该游戏需要载入一个 BIOS 文件才能运行" -#: lutris/game.py:184 +#: lutris/game.py:188 msgid "The file {} could not be found" msgstr "找不到文件 {}" -#: lutris/game.py:186 +#: lutris/game.py:190 msgid "" "This game has no executable set. The install process didn't finish properly." msgstr "该游戏没有可执行文件。安装过程没有正确完成。" -#: lutris/game.py:189 +#: lutris/game.py:193 #, python-format msgid "The file %s is not executable" msgstr "文件 %s 不是可执行文件或者没有执行权限" -#: lutris/game.py:191 +#: lutris/game.py:195 #, python-format msgid "The path '%s' is not set. please set it in the options." msgstr "路径“%s”未设置,请在选项中进行设置。" -#: lutris/game.py:193 +#: lutris/game.py:197 #, python-format msgid "Unhandled error: %s" msgstr "未处理的错误:%s" -#: lutris/game.py:298 +#: lutris/game.py:314 msgid "Tried to launch a game that isn't installed." msgstr "游戏尚未安装,无法启动。" -#: lutris/game.py:300 lutris/game.py:417 +#: lutris/game.py:316 lutris/game.py:424 msgid "Invalid game configuration: Missing runner" msgstr "无效游戏配置:缺少运行环境" -#: lutris/game.py:309 +#: lutris/game.py:325 msgid "Runtime currently updating" msgstr "正在更新运行库" -#: lutris/game.py:309 +#: lutris/game.py:325 msgid "Game might not work as expected" msgstr "更新完成前游戏可能无法正常启动" -#: lutris/game.py:342 +#: lutris/game.py:358 msgid "Unable to find Xephyr, install it or disable the Xephyr option" msgstr "找不到 Xephyr,请先安装 Xephyr 或禁用 Xephyr 选项" -#: lutris/game.py:398 +#: lutris/game.py:405 #, python-format msgid "" "The selected terminal application could not be launched:\n" @@ -377,17 +381,17 @@ "%s" #. The 'file' sort of gameplay_info cannot be made to use a configuration -#: lutris/game.py:434 +#: lutris/game.py:441 msgid "The runner could not find a command to apply the configuration to." msgstr "" "当前运行环境中找不到可应用该配置的指令。\n" "file 类型的 gameplay_info 不能应用配置。" -#: lutris/game.py:635 +#: lutris/game.py:657 msgid "Error lauching the game:\n" msgstr "启动游戏出错:\n" -#: lutris/game.py:727 +#: lutris/game.py:752 #, python-format msgid "" "Error: Missing shared library.\n" @@ -398,7 +402,7 @@ "\n" "%s" -#: lutris/game.py:733 +#: lutris/game.py:758 msgid "" "Error: A different Wine version is already using the same Wine prefix." msgstr "错误:该 Wine 容器正在被另一个不同版本的 Wine 使用。" @@ -493,16 +497,16 @@ msgid "Select a Lutris installer" msgstr "选择安装脚本文件" -#: lutris/gui/application.py:87 +#: lutris/gui/application.py:88 msgid "" "Running Lutris as root is not recommended and may cause unexpected issues" msgstr "不建议以 root 用户身份运行 Lutris,这可能会导致未预期的问题" -#: lutris/gui/application.py:97 +#: lutris/gui/application.py:98 msgid "Your Linux distribution is too old. Lutris won't function properly." msgstr "您的 Linux 发行版太旧了,Lutris 无法正常运行。" -#: lutris/gui/application.py:102 +#: lutris/gui/application.py:103 msgid "" "Run a game directly by adding the parameter lutris:rungame/game-identifier.\n" "If several games share the same identifier you can use the numerical ID " @@ -515,116 +519,116 @@ "数字ID),参数为 lutris:rungameid/数字ID。\n" "要安装游戏,请使用参数 lutris:install/游戏标识符。" -#: lutris/gui/application.py:115 +#: lutris/gui/application.py:116 msgid "Print the version of Lutris and exit" msgstr "打印 Lutris 版本后退出" -#: lutris/gui/application.py:123 +#: lutris/gui/application.py:124 msgid "Show debug messages" msgstr "显示调试信息" -#: lutris/gui/application.py:131 +#: lutris/gui/application.py:132 msgid "Install a game from a yml file" msgstr "从 yml 文件安装游戏" -#: lutris/gui/application.py:139 +#: lutris/gui/application.py:140 msgid "Generate a bash script to run a game without the client" msgstr "生成 bash 脚本以在没有 Lutris 客户端的情况下运行游戏" -#: lutris/gui/application.py:147 +#: lutris/gui/application.py:148 msgid "Execute a program with the Lutris Runtime" msgstr "在 Lutris 运行库中运行程序" -#: lutris/gui/application.py:155 +#: lutris/gui/application.py:156 msgid "List all games in database" msgstr "列出数据库里的所有游戏" -#: lutris/gui/application.py:163 +#: lutris/gui/application.py:164 msgid "Only list installed games" msgstr "仅列出已安装的游戏" -#: lutris/gui/application.py:171 +#: lutris/gui/application.py:172 msgid "List available Steam games" msgstr "列出可用的 Steam 游戏" -#: lutris/gui/application.py:179 +#: lutris/gui/application.py:180 msgid "List all known Steam library folders" msgstr "列出所有已知的 Steam 库文件夹" -#: lutris/gui/application.py:187 +#: lutris/gui/application.py:188 msgid "List all known runners" msgstr "列出所有已知的运行环境" -#: lutris/gui/application.py:195 +#: lutris/gui/application.py:196 msgid "List all known Wine versions" msgstr "列出所有已知的 Wine 版本" -#: lutris/gui/application.py:203 +#: lutris/gui/application.py:204 msgid "Install a Runner" msgstr "安装运行环境" -#: lutris/gui/application.py:211 +#: lutris/gui/application.py:212 msgid "Uninstall a Runner" msgstr "卸载运行环境" -#: lutris/gui/application.py:219 +#: lutris/gui/application.py:220 msgid "Export a game" msgstr "导出游戏" -#: lutris/gui/application.py:227 +#: lutris/gui/application.py:228 msgid "Import a game" msgstr "导入游戏" -#: lutris/gui/application.py:235 +#: lutris/gui/application.py:236 msgid "Destination path for export" msgstr "导出路径" -#: lutris/gui/application.py:243 +#: lutris/gui/application.py:244 msgid "Display the list of games in JSON format" msgstr "以 JSON 格式显示游戏列表" -#: lutris/gui/application.py:251 +#: lutris/gui/application.py:252 msgid "Reinstall game" msgstr "重装游戏" -#: lutris/gui/application.py:254 +#: lutris/gui/application.py:255 msgid "Submit an issue" msgstr "提交反馈" -#: lutris/gui/application.py:260 +#: lutris/gui/application.py:261 msgid "URI to open" msgstr "要打开的 URI" -#: lutris/gui/application.py:489 +#: lutris/gui/application.py:483 #, python-format msgid "%s is not a valid URI" msgstr "URI 格式不正确:%s" -#: lutris/gui/application.py:509 +#: lutris/gui/application.py:503 #, python-format msgid "Failed to download %s" msgstr "无法下载 %s" -#: lutris/gui/application.py:517 +#: lutris/gui/application.py:511 #, python-brace-format msgid "download {url} to {file} started" msgstr "开始下载 {url},保存至 {file}" -#: lutris/gui/application.py:528 +#: lutris/gui/application.py:522 #, python-format msgid "No such file: %s" msgstr "文件不存在:%s" -#: lutris/gui/application.py:645 +#: lutris/gui/application.py:644 #, python-format msgid "There is no installer available for %s." msgstr "%s 没有可用的安装脚本。" -#: lutris/gui/application.py:655 +#: lutris/gui/application.py:654 msgid "No updates found" msgstr "未找到更新" -#: lutris/gui/application.py:665 +#: lutris/gui/application.py:664 msgid "No DLC found" msgstr "未找到 DLC" @@ -679,8 +683,8 @@ msgid "_Add" msgstr "添加 (_A)" -#: lutris/gui/config/boxes.py:512 lutris/gui/dialogs/__init__.py:134 -#: lutris/gui/dialogs/__init__.py:161 lutris/gui/dialogs/issue.py:75 +#: lutris/gui/config/boxes.py:512 lutris/gui/dialogs/__init__.py:165 +#: lutris/gui/dialogs/__init__.py:192 lutris/gui/dialogs/issue.py:72 #: lutris/gui/widgets/common.py:98 #: lutris/gui/widgets/download_progress_box.py:47 msgid "_Cancel" @@ -709,80 +713,80 @@ msgid "Select a runner in the Game Info tab" msgstr "在“游戏信息”选项卡中选择一个运行环境" -#: lutris/gui/config/common.py:109 +#: lutris/gui/config/common.py:94 msgid "Game info" msgstr "游戏信息" -#: lutris/gui/config/common.py:124 +#: lutris/gui/config/common.py:110 msgid "Identifier" msgstr "标识符" -#: lutris/gui/config/common.py:133 lutris/gui/config/common.py:259 +#: lutris/gui/config/common.py:119 lutris/gui/config/common.py:245 msgid "Change" msgstr "修改" -#: lutris/gui/config/common.py:142 +#: lutris/gui/config/common.py:128 msgid "Directory" msgstr "所在文件夹" -#: lutris/gui/config/common.py:148 +#: lutris/gui/config/common.py:134 msgid "Move" msgstr "移动" -#: lutris/gui/config/common.py:156 lutris/gui/views/list.py:47 +#: lutris/gui/config/common.py:142 lutris/gui/views/list.py:47 msgid "Runner" msgstr "运行环境" -#: lutris/gui/config/common.py:177 +#: lutris/gui/config/common.py:163 msgid "Remove custom banner" msgstr "移除自定义封面" -#: lutris/gui/config/common.py:188 +#: lutris/gui/config/common.py:174 msgid "Remove custom icon" msgstr "移除自定义图标" -#: lutris/gui/config/common.py:197 +#: lutris/gui/config/common.py:183 msgid "Release year" msgstr "发行年份" -#: lutris/gui/config/common.py:240 +#: lutris/gui/config/common.py:226 msgid "Select a runner from the list" msgstr "从列表中选择一个运行环境" -#: lutris/gui/config/common.py:248 +#: lutris/gui/config/common.py:234 msgid "Apply" msgstr "应用" -#: lutris/gui/config/common.py:296 +#: lutris/gui/config/common.py:282 msgid "Game options" msgstr "游戏选项" -#: lutris/gui/config/common.py:304 +#: lutris/gui/config/common.py:290 msgid "Runner options" msgstr "运行环境选项" -#: lutris/gui/config/common.py:312 +#: lutris/gui/config/common.py:298 msgid "System options" msgstr "系统选项" #. Advanced settings checkbox -#: lutris/gui/config/common.py:322 +#: lutris/gui/config/common.py:308 msgid "Show advanced options" msgstr "显示高级选项" -#: lutris/gui/config/common.py:330 lutris/gui/dialogs/__init__.py:268 -#: lutris/gui/dialogs/runner_install.py:186 +#: lutris/gui/config/common.py:316 lutris/gui/dialogs/__init__.py:299 +#: lutris/gui/dialogs/runner_install.py:187 #: lutris/gui/dialogs/uninstall_game.py:60 #: lutris/gui/dialogs/uninstall_game.py:135 #: lutris/gui/widgets/download_progress_box.py:95 msgid "Cancel" msgstr "取消" -#: lutris/gui/config/common.py:334 +#: lutris/gui/config/common.py:320 msgid "Save" msgstr "保存" -#: lutris/gui/config/common.py:368 +#: lutris/gui/config/common.py:354 msgid "" "Are you sure you want to change the runner for this game ? This will reset " "the full configuration for this game and is not reversible." @@ -790,35 +794,35 @@ "您确定要更改此游戏的运行环境吗?\n" "这将重置此游戏的所有配置,并且不可逆。" -#: lutris/gui/config/common.py:372 +#: lutris/gui/config/common.py:358 msgid "Confirm runner change" msgstr "运行环境变更确认" -#: lutris/gui/config/common.py:420 +#: lutris/gui/config/common.py:406 msgid "Runner not provided" msgstr "未选择运行环境" -#: lutris/gui/config/common.py:423 +#: lutris/gui/config/common.py:409 msgid "Please fill in the name" msgstr "请填写游戏名称" -#: lutris/gui/config/common.py:426 +#: lutris/gui/config/common.py:412 msgid "Steam AppID not provided" msgstr "未提供 Steam 应用 ID" -#: lutris/gui/config/common.py:444 +#: lutris/gui/config/common.py:430 msgid "The following fields have invalid values: " msgstr "以下字段的值无效:" -#: lutris/gui/config/common.py:451 +#: lutris/gui/config/common.py:437 msgid "Current configuration is not valid, ignoring save request" msgstr "当前配置无效,无法保存" -#: lutris/gui/config/common.py:486 +#: lutris/gui/config/common.py:472 msgid "Please choose a custom image" msgstr "请选择一个自定义图片" -#: lutris/gui/config/common.py:494 +#: lutris/gui/config/common.py:480 msgid "Images" msgstr "映像文件" @@ -835,15 +839,19 @@ msgid "Hide text under icons (requires restart)" msgstr "隐藏图标下方文字(重启软件生效)" -#: lutris/gui/config/preferences_box.py:13 -msgid "Show Tray Icon (requires restart)" -msgstr "显示托盘图标(重启软件生效)" +#: lutris/gui/config/preferences_box.py:13 lutris/gui/config/sysinfo_box.py:13 +msgid "Show Tray Icon" +msgstr "显示托盘图标" #: lutris/gui/config/preferences_box.py:14 msgid "Use dark theme (requires dark theme variant for Gtk)" msgstr "使用深色主题(需要安装 GTK 深色主题,重启软件生效)" -#: lutris/gui/config/preferences_box.py:29 +#: lutris/gui/config/preferences_box.py:15 +msgid "Enable Discord Rich Presence for Available Games" +msgstr "为兼容 Discord Rich Presence 的游戏启用 Discord Rich Presence" + +#: lutris/gui/config/preferences_box.py:30 msgid "Interface options" msgstr "界面选项" @@ -855,11 +863,11 @@ msgid "Interface" msgstr "界面选项" -#: lutris/gui/config/preferences_dialog.py:27 lutris/gui/widgets/sidebar.py:271 +#: lutris/gui/config/preferences_dialog.py:27 lutris/gui/widgets/sidebar.py:272 msgid "Runners" msgstr "运行环境" -#: lutris/gui/config/preferences_dialog.py:28 lutris/gui/widgets/sidebar.py:270 +#: lutris/gui/config/preferences_dialog.py:28 lutris/gui/widgets/sidebar.py:271 msgid "Sources" msgstr "来源" @@ -871,7 +879,7 @@ msgid "Global options" msgstr "全局设置" -#: lutris/gui/config/runner_box.py:90 lutris/gui/widgets/sidebar.py:214 +#: lutris/gui/config/runner_box.py:92 lutris/gui/widgets/sidebar.py:214 #, python-format msgid "Manage %s versions" msgstr "管理 %s 版本" @@ -910,10 +918,6 @@ msgid "Hide text under icons" msgstr "隐藏图标下方文字" -#: lutris/gui/config/sysinfo_box.py:13 -msgid "Show Tray Icon" -msgstr "显示托盘图标" - #: lutris/gui/config/sysinfo_box.py:38 msgid "Copy to clipboard" msgstr "复制到剪贴板" @@ -926,15 +930,15 @@ msgid "Cache configuration" msgstr "缓存配置" -#: lutris/gui/dialogs/cache.py:24 +#: lutris/gui/dialogs/cache.py:26 msgid "Cache path" msgstr "缓存目录" -#: lutris/gui/dialogs/cache.py:28 +#: lutris/gui/dialogs/cache.py:30 msgid "Set the folder for the cache path" msgstr "设置缓存文件夹路径" -#: lutris/gui/dialogs/cache.py:37 +#: lutris/gui/dialogs/cache.py:39 msgid "" "If provided, this location will be used by installers to cache downloaded " "files locally for future re-use. \n" @@ -953,59 +957,59 @@ msgid "Downloading %s" msgstr "正在下载 %s" -#: lutris/gui/dialogs/__init__.py:133 lutris/gui/dialogs/__init__.py:160 -#: lutris/gui/dialogs/issue.py:74 lutris/gui/dialogs/runner_install.py:63 +#: lutris/gui/dialogs/__init__.py:164 lutris/gui/dialogs/__init__.py:191 +#: lutris/gui/dialogs/issue.py:71 lutris/gui/dialogs/runner_install.py:64 #: lutris/gui/widgets/common.py:98 msgid "_OK" msgstr "确定 (_O)" -#: lutris/gui/dialogs/__init__.py:151 +#: lutris/gui/dialogs/__init__.py:182 msgid "Please choose a file" msgstr "选择一个文件" -#: lutris/gui/dialogs/__init__.py:181 +#: lutris/gui/dialogs/__init__.py:212 msgid "Checking for runtime updates, please wait…" msgstr "正在检查运行库更新,请稍候..." -#: lutris/gui/dialogs/__init__.py:212 +#: lutris/gui/dialogs/__init__.py:243 #, python-format msgid "%s is already installed" msgstr "%s 已安装" -#: lutris/gui/dialogs/__init__.py:221 +#: lutris/gui/dialogs/__init__.py:252 msgid "Launch game" msgstr "启动游戏" -#: lutris/gui/dialogs/__init__.py:225 +#: lutris/gui/dialogs/__init__.py:256 msgid "Install the game again" msgstr "再次安装游戏" -#: lutris/gui/dialogs/__init__.py:229 lutris/gui/dialogs/__init__.py:272 -#: lutris/gui/dialogs/__init__.py:360 +#: lutris/gui/dialogs/__init__.py:260 lutris/gui/dialogs/__init__.py:303 +#: lutris/gui/dialogs/__init__.py:391 msgid "OK" msgstr "确定" -#: lutris/gui/dialogs/__init__.py:248 +#: lutris/gui/dialogs/__init__.py:279 msgid "Select game to launch" msgstr "选择要启动的游戏" -#: lutris/gui/dialogs/__init__.py:333 +#: lutris/gui/dialogs/__init__.py:364 msgid "Login failed" msgstr "登录失败" -#: lutris/gui/dialogs/__init__.py:344 +#: lutris/gui/dialogs/__init__.py:375 msgid "Install script for {}" msgstr "{} 的安装脚本" -#: lutris/gui/dialogs/__init__.py:396 +#: lutris/gui/dialogs/__init__.py:427 msgid "Do not display this message again." msgstr "不再显示此信息。" -#: lutris/gui/dialogs/__init__.py:417 +#: lutris/gui/dialogs/__init__.py:448 msgid "Wine is not installed on your system." msgstr "您需要将 Wine 安装到您的系统中。" -#: lutris/gui/dialogs/__init__.py:419 +#: lutris/gui/dialogs/__init__.py:450 msgid "" "Having Wine installed on your system guarantees that Wine builds from Lutris " "will have all required dependencies.\n" @@ -1018,16 +1022,16 @@ "请按照 Lutris Wiki 中给出的说明安装 Wine。" -#: lutris/gui/dialogs/__init__.py:445 +#: lutris/gui/dialogs/__init__.py:476 #, python-format msgid "Moving %s to %s..." msgstr "正在移动 %s 到 %s ..." -#: lutris/gui/dialogs/issue.py:27 +#: lutris/gui/dialogs/issue.py:24 msgid "Submit an issue" msgstr "提交反馈" -#: lutris/gui/dialogs/issue.py:32 +#: lutris/gui/dialogs/issue.py:29 msgid "" "Describe the problem you're having in the text box below. This information " "will be sent the Lutris team along with your system information. You can " @@ -1036,80 +1040,80 @@ "在下面的文本框中描述您遇到的问题,该信息将与您的系统信息一起发送给 Lutris 团" "队。如果您没有联网,也可以将该信息保存在本地。" -#: lutris/gui/dialogs/issue.py:55 +#: lutris/gui/dialogs/issue.py:52 msgid "_Save" msgstr "保存 (_S)" -#: lutris/gui/dialogs/issue.py:71 +#: lutris/gui/dialogs/issue.py:68 msgid "Select a location to save the issue" msgstr "选择一个位置来保存反馈信息" -#: lutris/gui/dialogs/issue.py:91 +#: lutris/gui/dialogs/issue.py:88 #, python-format msgid "Issue saved in %s" msgstr "反馈已保存到 %s" -#: lutris/gui/dialogs/runner_install.py:29 +#: lutris/gui/dialogs/runner_install.py:30 #, python-format msgid "Showing games using %s" msgstr "查看使用 %s 的游戏" -#: lutris/gui/dialogs/runner_install.py:71 +#: lutris/gui/dialogs/runner_install.py:72 #, python-format msgid "Waiting for response from %s" msgstr "等待 %s 响应" -#: lutris/gui/dialogs/runner_install.py:87 +#: lutris/gui/dialogs/runner_install.py:88 #, python-format msgid "Unable to get runner versions: %s" msgstr "无法获取运行环境版本信息:%s" -#: lutris/gui/dialogs/runner_install.py:101 +#: lutris/gui/dialogs/runner_install.py:102 msgid "Unable to get runner versions from lutris.net" msgstr "无法从 lutris.net 获取运行环境版本信息" -#: lutris/gui/dialogs/runner_install.py:108 +#: lutris/gui/dialogs/runner_install.py:109 #, python-format msgid "%s version management" msgstr "%s 版本管理器" -#: lutris/gui/dialogs/runner_install.py:158 +#: lutris/gui/dialogs/runner_install.py:159 #, python-format msgid "_View %d game" msgid_plural "_View %d games" msgstr[0] "被 %d 个游戏使用" -#: lutris/gui/dialogs/runner_install.py:190 +#: lutris/gui/dialogs/runner_install.py:191 #: lutris/gui/dialogs/uninstall_game.py:34 msgid "Uninstall" msgstr "卸载" -#: lutris/gui/dialogs/runner_install.py:214 +#: lutris/gui/dialogs/runner_install.py:215 msgid "Wine version usage" msgstr "Wine 版本使用情况" -#: lutris/gui/dialogs/runner_install.py:256 +#: lutris/gui/dialogs/runner_install.py:274 msgid "Do you want to cancel the download?" msgstr "是否取消下载?" -#: lutris/gui/dialogs/runner_install.py:257 +#: lutris/gui/dialogs/runner_install.py:275 msgid "Download starting" msgstr "下载开始" -#: lutris/gui/dialogs/runner_install.py:307 +#: lutris/gui/dialogs/runner_install.py:325 #, python-format msgid "Version %s is not longer available" msgstr "版本 %s 不再可用" -#: lutris/gui/dialogs/runner_install.py:332 +#: lutris/gui/dialogs/runner_install.py:350 msgid "Downloading…" msgstr "正在下载..." -#: lutris/gui/dialogs/runner_install.py:335 +#: lutris/gui/dialogs/runner_install.py:353 msgid "Extracting…" msgstr "正在解压..." -#: lutris/gui/dialogs/runner_install.py:377 +#: lutris/gui/dialogs/runner_install.py:395 msgid "Failed to retrieve the runner archive" msgstr "下载运行环境安装包失败" @@ -1136,7 +1140,7 @@ msgid "Content of %s are protected and will not be deleted." msgstr "目录 %s 中的内容受到保护,无法被删除。" -#: lutris/gui/dialogs/uninstall_game.py:76 +#: lutris/gui/dialogs/uninstall_game.py:75 #, python-format msgid "Delete %s (%s)" msgstr "删除 %s (%s)" @@ -1178,122 +1182,117 @@ "是否完全从游戏库中移除 %s?\n" "Lutris 记录的游戏游玩时间也会一并删除。" -#: lutris/gui/dialogs/webconnect_dialog.py:106 +#: lutris/gui/dialogs/webconnect_dialog.py:103 msgid "Loading..." msgstr "载入中..." -#: lutris/gui/installer/file_box.py:95 +#: lutris/gui/installer/file_box.py:94 #, python-brace-format msgid "Steam game {appid}" msgstr "Steam 游戏(应用 ID:{appid})" -#: lutris/gui/installer/file_box.py:110 -#, python-brace-format -msgid "{file} on {host}" -msgstr "{file}(来自 {host})" - -#: lutris/gui/installer/file_box.py:121 +#: lutris/gui/installer/file_box.py:108 msgid "Download" msgstr "下载" -#: lutris/gui/installer/file_box.py:123 +#: lutris/gui/installer/file_box.py:110 msgid "Use Cache" msgstr "使用缓存" -#: lutris/gui/installer/file_box.py:125 lutris/runners/steam.py:29 +#: lutris/gui/installer/file_box.py:112 lutris/runners/steam.py:29 #: lutris/services/steam.py:73 msgid "Steam" msgstr "Steam" -#: lutris/gui/installer/file_box.py:126 +#: lutris/gui/installer/file_box.py:113 msgid "Select File" msgstr "选择文件" -#: lutris/gui/installer/file_box.py:187 +#: lutris/gui/installer/file_box.py:174 msgid "Cache file for future installations" msgstr "缓存文件以供将来安装" -#: lutris/gui/installer/file_box.py:206 +#: lutris/gui/installer/file_box.py:193 msgid "Source:" msgstr "来源:" -#: lutris/gui/installerwindow.py:65 +#: lutris/gui/installerwindow.py:67 msgid "Cache" msgstr "缓存" -#: lutris/gui/installerwindow.py:76 +#: lutris/gui/installerwindow.py:78 msgid "Abort and revert the installation" msgstr "中止并回滚安装" -#: lutris/gui/installerwindow.py:78 +#: lutris/gui/installerwindow.py:80 msgid "_Eject" msgstr "弹出 (_E)" -#: lutris/gui/installerwindow.py:79 +#: lutris/gui/installerwindow.py:81 msgid "_View source" msgstr "查看来源 (_V)" -#: lutris/gui/installerwindow.py:80 +#: lutris/gui/installerwindow.py:82 msgid "_Install" msgstr "安装 (_I)" -#: lutris/gui/installerwindow.py:81 +#: lutris/gui/installerwindow.py:83 msgid "_Continue" msgstr "继续 (_C)" -#: lutris/gui/installerwindow.py:82 +#: lutris/gui/installerwindow.py:84 msgid "_Launch" msgstr "启动 (_L)" -#: lutris/gui/installerwindow.py:83 +#: lutris/gui/installerwindow.py:85 msgid "_Close" msgstr "关闭 (_C)" -#: lutris/gui/installerwindow.py:122 +#: lutris/gui/installerwindow.py:124 #, python-format msgid "Missing field \"%s\" in install script" msgstr "安装脚本缺少字段“%s”" -#: lutris/gui/installerwindow.py:128 +#: lutris/gui/installerwindow.py:130 #, python-format msgid "Install %s" msgstr "安装 %s" -#: lutris/gui/installerwindow.py:162 +#: lutris/gui/installerwindow.py:164 #, python-format msgid "This game requires %s. Do you want to install it?" msgstr "该游戏需要 %s,您要安装吗?" -#: lutris/gui/installerwindow.py:163 +#: lutris/gui/installerwindow.py:165 msgid "Missing dependency" msgstr "缺少依赖" -#: lutris/gui/installerwindow.py:175 +#: lutris/gui/installerwindow.py:177 msgid "Installing {}" msgstr "安装 {}" -#: lutris/gui/installerwindow.py:196 +#: lutris/gui/installerwindow.py:198 msgid "Select installation directory" msgstr "选择安装目录" -#: lutris/gui/installerwindow.py:246 +#: lutris/gui/installerwindow.py:248 msgid "Autodetect" msgstr "自动检测" -#: lutris/gui/installerwindow.py:252 +#: lutris/gui/installerwindow.py:254 msgid "Browse…" msgstr "浏览..." -#: lutris/gui/installerwindow.py:259 +#: lutris/gui/installerwindow.py:261 msgid "Select the folder where the disc is mounted" msgstr "选择安装光盘的挂载文件夹" -#: lutris/gui/installerwindow.py:320 +#: lutris/gui/installerwindow.py:322 msgid "" "Please review the files needed for the installation then click 'Continue'" msgstr "请检查安装所需的文件,然后点击“继续”" -#: lutris/gui/installerwindow.py:358 +#: lutris/gui/installerwindow.py:360 msgid "" "This game has extra content. \n" "Select which one you want and they will be available in the 'extras' folder " @@ -1302,42 +1301,66 @@ "该游戏包含附加内容。\n" "请选择您想下载的附加内容,这些内容将被安装至游戏的“extras”文件夹。" -#: lutris/gui/installerwindow.py:459 +#: lutris/gui/installerwindow.py:462 #, python-format msgid "Unable to get files: %s" msgstr "无法读取文件:%s" -#: lutris/gui/installerwindow.py:554 +#: lutris/gui/installerwindow.py:559 msgid "Remove game files" msgstr "删除游戏文件" -#: lutris/gui/installerwindow.py:560 +#: lutris/gui/installerwindow.py:569 msgid "Are you sure you want to cancel the installation?" msgstr "确定要取消安装吗?" -#: lutris/gui/installerwindow.py:561 +#: lutris/gui/installerwindow.py:570 msgid "Cancel installation?" msgstr "是否取消安装?" -#: lutris/gui/lutriswindow.py:345 +#: lutris/gui/lutriswindow.py:344 #, python-format msgid "Connect your %s account to access your games" msgstr "关联您的 %s 账户来查看您的游戏" -#: lutris/gui/lutriswindow.py:416 +#: lutris/gui/lutriswindow.py:417 +#, python-format +msgid "Add a game matching '%s' to your favorites to see it here." +msgstr "将匹配“%s”的游戏添加到收藏夹以在此处查看。" + +#: lutris/gui/lutriswindow.py:420 +#, python-format +msgid "" +"No installed games matching '%s' found. Press Ctrl+I to show uninstalled " +"games." +msgstr "找不到与“%s”匹配的已安装游戏。按 Ctrl+I 显示已卸载的游戏。" + +#. but not if missing! +#: lutris/gui/lutriswindow.py:422 +#, python-format +msgid "" +"No visible games matching '%s' found. Press Ctrl+H to show hidden games." +msgstr "找不到与“%s”匹配的可见游戏。按 Ctrl+H 显示隐藏游戏。" + +#: lutris/gui/lutriswindow.py:425 #, python-format msgid "No games matching '%s' found " msgstr "未找到包含“%s”的游戏" -#: lutris/gui/lutriswindow.py:419 +#: lutris/gui/lutriswindow.py:428 msgid "Add games to your favorites to see them here." msgstr "将游戏添加到收藏夹就能在这里看到" -#: lutris/gui/lutriswindow.py:421 -msgid "No installed games found. Press Ctrl+H to show all games." -msgstr "未找到已安装的游戏,按 Ctrl+H 显示所有游戏" - #: lutris/gui/lutriswindow.py:430 +msgid "No installed games found. Press Ctrl+I to show uninstalled games." +msgstr "未找到已安装的游戏。按 Ctrl+I 显示已卸载的游戏。" + +#. but not if missing! +#: lutris/gui/lutriswindow.py:432 +msgid "No visible games found. Press Ctrl+H to show hidden games." +msgstr "找不到可见的游戏。按 Ctrl+H 显示隐藏游戏。" + +#: lutris/gui/lutriswindow.py:440 msgid "No games found" msgstr "未找到游戏" @@ -1446,27 +1469,27 @@ msgid "Manage Versions" msgstr "添加/删除版本" -#: lutris/gui/widgets/sidebar.py:269 +#: lutris/gui/widgets/sidebar.py:270 msgid "Library" msgstr "游戏库" -#: lutris/gui/widgets/sidebar.py:272 +#: lutris/gui/widgets/sidebar.py:273 msgid "Platforms" msgstr "平台" -#: lutris/gui/widgets/sidebar.py:316 lutris/util/system.py:27 +#: lutris/gui/widgets/sidebar.py:317 lutris/util/system.py:28 msgid "Games" msgstr "游戏" -#: lutris/gui/widgets/sidebar.py:325 +#: lutris/gui/widgets/sidebar.py:326 msgid "Recent" msgstr "最近打开" -#: lutris/gui/widgets/sidebar.py:334 +#: lutris/gui/widgets/sidebar.py:335 msgid "Favorites" msgstr "收藏夹" -#: lutris/gui/widgets/sidebar.py:342 +#: lutris/gui/widgets/sidebar.py:343 msgid "Running" msgstr "运行中" @@ -1483,8 +1506,8 @@ msgid "One of {params} parameter is mandatory for the {cmd} command" msgstr "{cmd} 命令必须有以下参数之一:{params}" -#: lutris/installer/commands.py:67 lutris/installer/interpreter.py:123 -#: lutris/installer/interpreter.py:146 +#: lutris/installer/commands.py:67 lutris/installer/interpreter.py:128 +#: lutris/installer/interpreter.py:151 msgid " or " msgstr " 或 " @@ -1596,25 +1619,30 @@ msgid "Wrong value for write_file mode: '%s'" msgstr "“write_file”命令的“mode”参数值“%s”不正确" -#: lutris/installer/installer_file.py:26 +#: lutris/installer/installer_file.py:27 #, python-format msgid "missing field `url` for file `%s`" msgstr "文件“%s”缺少“url”字段" -#: lutris/installer/installer_file.py:38 +#: lutris/installer/installer_file.py:46 #, python-format msgid "missing field `filename` in file `%s`" msgstr "文件“%s”缺少“filename”字段" -#: lutris/installer/installer_file.py:176 +#: lutris/installer/installer_file.py:95 +#, python-brace-format +msgid "{file} on {host}" +msgstr "{file}(来自 {host})" + +#: lutris/installer/installer_file.py:196 msgid "Invalid checksum, expected format (type:hash) " msgstr "“checksum”参数无效,格式应为“算法:哈希值”" -#: lutris/installer/installer_file.py:179 +#: lutris/installer/installer_file.py:199 msgid " checksum mismatch " msgstr "校验和不匹配" -#: lutris/installer/installer.py:179 +#: lutris/installer/installer.py:178 msgid "Game config key must be a string" msgstr "游戏配置“key”必须是字符串" @@ -1622,11 +1650,11 @@ msgid "Invalid 'game' section" msgstr "“game”配置节无效" -#: lutris/installer/interpreter.py:53 +#: lutris/installer/interpreter.py:55 msgid "This installer doesn't have a 'script' section" msgstr "安装脚本缺少“script”配置节" -#: lutris/installer/interpreter.py:57 +#: lutris/installer/interpreter.py:59 msgid "" "Invalid script: \n" "{}" @@ -1634,38 +1662,38 @@ "无效的安装脚本:\n" "{}" -#: lutris/installer/interpreter.py:123 lutris/installer/interpreter.py:126 +#: lutris/installer/interpreter.py:128 lutris/installer/interpreter.py:131 #, python-format msgid "This installer requires %s on your system" msgstr "要使用此安装脚本,您需要将 %s 安装到您的系统中。" -#: lutris/installer/interpreter.py:139 +#: lutris/installer/interpreter.py:144 msgid "You need to install {} before" msgstr "需要先安装 {}" -#: lutris/installer/interpreter.py:187 +#: lutris/installer/interpreter.py:191 msgid "Lutris does not have the necessary permissions to install to path:" msgstr "Lutris 没有安装到以下路径所需的权限:" -#: lutris/installer/interpreter.py:271 +#: lutris/installer/interpreter.py:275 #, python-format msgid "Invalid runner provided %s" msgstr "无效的运行环境 %s" -#: lutris/installer/interpreter.py:297 +#: lutris/installer/interpreter.py:301 msgid "Installing game data" msgstr "安装游戏数据..." -#: lutris/installer/interpreter.py:309 +#: lutris/installer/interpreter.py:313 msgid "Installer commands are not formatted correctly" msgstr "安装脚本命令格式不正确" -#: lutris/installer/interpreter.py:342 +#: lutris/installer/interpreter.py:346 #, python-format msgid "The command \"%s\" does not exist." msgstr "命令“%s”不存在" -#: lutris/installer/interpreter.py:363 +#: lutris/installer/interpreter.py:367 #, python-format msgid "" "The executable at path %s can't be found, please check the destination " @@ -1676,7 +1704,7 @@ "看起来某些安装步骤未顺利完成,可能是安装程序被提前关闭或者发生了错误。\n" "如果游戏无法启动,请尝试重新安装,或向安装脚本维护人员报告问题。" -#: lutris/installer/interpreter.py:369 +#: lutris/installer/interpreter.py:373 msgid "Installation completed!" msgstr "安装完成" @@ -1685,24 +1713,24 @@ msgid "Malformed steam path: %s" msgstr "Steam 路径不正确:%s" -#: lutris/runners/atari800.py:19 +#: lutris/runners/atari800.py:17 msgid "Desktop resolution" msgstr "桌面分辨率" -#: lutris/runners/atari800.py:25 +#: lutris/runners/atari800.py:22 msgid "Atari800" msgstr "Atari 800" -#: lutris/runners/atari800.py:26 +#: lutris/runners/atari800.py:23 msgid "Atari 8bit computers" msgstr "Atari 8位机" -#: lutris/runners/atari800.py:29 +#: lutris/runners/atari800.py:26 msgid "Atari 400, 800 and XL emulator" msgstr "Atari 400、800、XL 模拟器" -#: lutris/runners/atari800.py:41 lutris/runners/jzintv.py:19 -#: lutris/runners/libretro.py:74 lutris/runners/mame.py:78 +#: lutris/runners/atari800.py:38 lutris/runners/jzintv.py:19 +#: lutris/runners/libretro.py:73 lutris/runners/mame.py:78 #: lutris/runners/mednafen.py:57 lutris/runners/mupen64plus.py:20 #: lutris/runners/o2em.py:45 lutris/runners/openmsx.py:17 #: lutris/runners/osmose.py:21 lutris/runners/snes9x.py:27 @@ -1710,7 +1738,7 @@ msgid "ROM file" msgstr "ROM 文件" -#: lutris/runners/atari800.py:43 +#: lutris/runners/atari800.py:40 msgid "" "The game data, commonly called a ROM image. \n" "Supported formats: ATR, XFD, DCM, ATR.GZ, XFD.GZ and PRO." @@ -1718,11 +1746,11 @@ "游戏数据,通常称为 ROM 映像。\n" "支持的 ROM 格式:ATR,XFD,DCM,ATR.GZ,XFD.GZ 和 PRO。" -#: lutris/runners/atari800.py:57 +#: lutris/runners/atari800.py:51 msgid "BIOS location" msgstr "BIOS 路径" -#: lutris/runners/atari800.py:59 +#: lutris/runners/atari800.py:53 msgid "" "A folder containing the Atari 800 BIOS files.\n" "They are provided by Lutris so you shouldn't have to change this." @@ -1730,34 +1758,34 @@ "包含 Atari 800 BIOS 文件的文件夹。\n" "由 Lutris 提供,您不必更改此设置。" -#: lutris/runners/atari800.py:70 +#: lutris/runners/atari800.py:62 msgid "Emulate Atari 800" msgstr "模拟 Atari 800" -#: lutris/runners/atari800.py:71 +#: lutris/runners/atari800.py:63 msgid "Emulate Atari 800 XL" msgstr "模拟 Atari 800 XL" -#: lutris/runners/atari800.py:72 +#: lutris/runners/atari800.py:64 msgid "Emulate Atari 320 XE (Compy Shop)" msgstr "模拟 Atari 320 XE(Compy Shop)" -#: lutris/runners/atari800.py:73 +#: lutris/runners/atari800.py:65 msgid "Emulate Atari 320 XE (Rambo)" msgstr "模拟 Atari 320 XE(Rambo)" -#: lutris/runners/atari800.py:74 +#: lutris/runners/atari800.py:66 msgid "Emulate Atari 5200" msgstr "模拟 Atari 5200" -#: lutris/runners/atari800.py:79 lutris/runners/mame.py:83 +#: lutris/runners/atari800.py:69 lutris/runners/mame.py:83 #: lutris/runners/vice.py:88 msgid "Machine" msgstr "机型" -#: lutris/runners/atari800.py:85 lutris/runners/easyrpg.py:144 +#: lutris/runners/atari800.py:75 lutris/runners/easyrpg.py:144 #: lutris/runners/hatari.py:72 lutris/runners/jzintv.py:43 -#: lutris/runners/libretro.py:94 lutris/runners/mame.py:157 +#: lutris/runners/libretro.py:93 lutris/runners/mame.py:157 #: lutris/runners/mednafen.py:74 lutris/runners/mupen64plus.py:28 #: lutris/runners/o2em.py:75 lutris/runners/osmose.py:34 #: lutris/runners/pcsx2.py:26 lutris/runners/pico8.py:38 @@ -1767,11 +1795,11 @@ msgid "Fullscreen" msgstr "全屏模式" -#: lutris/runners/atari800.py:92 +#: lutris/runners/atari800.py:82 msgid "Fullscreen resolution" msgstr "全屏模式分辨率" -#: lutris/runners/atari800.py:104 +#: lutris/runners/atari800.py:94 msgid "Could not download Atari 800 BIOS archive" msgstr "下载 Atari 800 BIOS 文件失败" @@ -1862,8 +1890,8 @@ msgid "Command line arguments used when launching DOSBox" msgstr "启动 DOSBox 时使用的命令行参数" -#: lutris/runners/dosbox.py:51 lutris/runners/linux.py:39 -#: lutris/runners/wine.py:61 +#: lutris/runners/dosbox.py:51 lutris/runners/flatpak.py:74 +#: lutris/runners/linux.py:39 lutris/runners/wine.py:61 msgid "Working directory" msgstr "工作目录" @@ -1914,10 +1942,10 @@ msgid "Runs RPG Maker 2000/2003 games" msgstr "运行 RPG Maker 2000/2003 游戏" -#: lutris/runners/easyrpg.py:12 lutris/runners/linux.py:14 -#: lutris/runners/linux.py:16 lutris/runners/residualvm.py:15 -#: lutris/runners/scummvm.py:14 lutris/runners/steam.py:30 -#: lutris/runners/zdoom.py:15 +#: lutris/runners/easyrpg.py:12 lutris/runners/flatpak.py:18 +#: lutris/runners/linux.py:14 lutris/runners/linux.py:16 +#: lutris/runners/residualvm.py:15 lutris/runners/scummvm.py:14 +#: lutris/runners/steam.py:30 lutris/runners/zdoom.py:15 msgid "Linux" msgstr "Linux" @@ -1949,7 +1977,7 @@ msgid "Disable auto detection of the simulated engine." msgstr "选择要模拟的游戏引擎" -#: lutris/runners/easyrpg.py:41 lutris/runners/fsuae.py:50 +#: lutris/runners/easyrpg.py:41 lutris/runners/fsuae.py:155 #: lutris/runners/mame.py:173 lutris/runners/wine.py:82 #: lutris/runners/wine.py:416 msgid "Auto" @@ -2129,7 +2157,7 @@ "一定支持垂直同步。\n" "禁用垂直同步后,下方的帧率限制选项才会生效。" -#: lutris/runners/easyrpg.py:196 lutris/sysoptions.py:318 +#: lutris/runners/easyrpg.py:196 lutris/sysoptions.py:345 msgid "FPS limit" msgstr "帧率限制" @@ -2225,196 +2253,263 @@ msgid "The directory {} could not be found" msgstr "找不到文件夹 {}" -#: lutris/runners/fsuae.py:11 -msgid "FS-UAE" -msgstr "FS-UAE" +#: lutris/runners/flatpak.py:17 +msgid "Runs Flatpak applications" +msgstr "运行 Flatpak 应用" + +#: lutris/runners/flatpak.py:20 +msgid "Flatpak" +msgstr "Flatpak" -#: lutris/runners/fsuae.py:12 -msgid "Amiga emulator" -msgstr "Amiga 模拟器" +#: lutris/runners/flatpak.py:33 lutris/runners/steam.py:35 +msgid "Application ID" +msgstr "应用 ID" + +#: lutris/runners/flatpak.py:34 +msgid "The application's unique three-part identifier (tld.domain.app)." +msgstr "应用程序的唯一三元标识符 (tld.domain.app)。" + +#: lutris/runners/flatpak.py:39 +msgid "Architecture" +msgstr "架构" + +#: lutris/runners/flatpak.py:40 +msgid "" +"The architecture to run. See flatpak --supported-arches for architectures " +"supported by the host." +msgstr "" +"要运行的架构。主机支持的架构可通过 flatpak --supported-arches 命令获取。" + +#: lutris/runners/flatpak.py:47 +msgid "Branch" +msgstr "分支" + +#: lutris/runners/flatpak.py:48 +msgid "The branch to use." +msgstr "要使用的分支(Branch),相当于版本。" + +#: lutris/runners/flatpak.py:54 +msgid "Install type" +msgstr "安装位置" + +#: lutris/runners/flatpak.py:55 +msgid "Can be system or user." +msgstr "" +"可以是 system(安装到系统目录 /var/lib/flatpak)或 user(安装到用户主目录 ~/." +"local/share/flatpak/)。" + +#: lutris/runners/flatpak.py:61 +msgid "Args" +msgstr "启动参数" + +#: lutris/runners/flatpak.py:62 +msgid "Arguments to be passed to the application." +msgstr "传递给应用的启动参数。" + +#: lutris/runners/flatpak.py:67 +msgid "Command" +msgstr "启动命令" -#: lutris/runners/fsuae.py:14 lutris/runners/fsuae.py:25 +#: lutris/runners/flatpak.py:68 +msgid "" +"The command to run instead of the one listed in the application metadata." +msgstr "要运行的命令,代替应用程序元数据中列出的命令。" + +#: lutris/runners/flatpak.py:75 +msgid "" +"The directory to run the command in. Note that this must be a directory " +"inside the sandbox." +msgstr "运行命令的目录。请注意,必须是Flatpak沙箱内的目录。" + +#: lutris/runners/flatpak.py:82 lutris/sysoptions.py:449 +msgid "Environment variables" +msgstr "环境变量" + +#: lutris/runners/flatpak.py:83 +msgid "" +"Set an environment variable in the application. This overrides to the " +"Context section from the application metadata." +msgstr "向应用程序传递环境变量,这将覆盖应用程序元数据中的 Context 部分。" + +#: lutris/runners/flatpak.py:97 +msgid "" +"Flatpak installation is not handled by Lutris.\n" +"Install Flatpak with the package provided by your distribution." +msgstr "" +"Lutris 无法自动帮你安装 Flatpak。\n" +"请从您的发行版软件源安装 Flatpak,或参考以下网址:\n" +"https://www.flatpak.org/setup/" + +#: lutris/runners/fsuae.py:11 msgid "Amiga 500" msgstr "Amiga 500" -#: lutris/runners/fsuae.py:15 +#: lutris/runners/fsuae.py:19 msgid "Amiga 500+" msgstr "Amiga 500+" -#: lutris/runners/fsuae.py:16 +#: lutris/runners/fsuae.py:25 msgid "Amiga 600" msgstr "Amiga 600" -#: lutris/runners/fsuae.py:17 -msgid "Amiga 1000" -msgstr "Amiga 1000" - -#: lutris/runners/fsuae.py:18 lutris/runners/fsuae.py:19 +#: lutris/runners/fsuae.py:31 msgid "Amiga 1200" msgstr "Amiga 1200" -#: lutris/runners/fsuae.py:20 +#: lutris/runners/fsuae.py:37 +msgid "Amiga 3000" +msgstr "Amiga 3000" + +#: lutris/runners/fsuae.py:43 msgid "Amiga 4000" msgstr "Amiga 4000" -#: lutris/runners/fsuae.py:21 lutris/runners/fsuae.py:32 +#: lutris/runners/fsuae.py:50 +msgid "Amiga 1000" +msgstr "Amiga 1000" + +#: lutris/runners/fsuae.py:56 msgid "Amiga CD32" msgstr "Amiga CD32" -#: lutris/runners/fsuae.py:22 lutris/runners/fsuae.py:33 +#: lutris/runners/fsuae.py:65 msgid "Commodore CDTV" msgstr "Commodore CDTV" -#: lutris/runners/fsuae.py:26 -msgid "Amiga 500+ with 1 MB chip RAM" -msgstr "Amiga 500+(1 MB 内存)" - -#: lutris/runners/fsuae.py:27 -msgid "Amiga 600 with 1 MB chip RAM" -msgstr "Amiga 600(1 MB 内存)" - -#: lutris/runners/fsuae.py:28 -msgid "Amiga 1000 with 512 KB chip RAM" -msgstr "Amiga 1000(512 KB 内存)" - -#: lutris/runners/fsuae.py:29 -msgid "Amiga 1200 with 2 MB chip RAM" -msgstr "Amiga 1200(2 MB 内存)" - -#: lutris/runners/fsuae.py:30 -msgid "Amiga 1200 but with 68020 processor" -msgstr "Amiga 1200(68020 处理器)" +#: lutris/runners/fsuae.py:124 +msgid "FS-UAE" +msgstr "FS-UAE" -#: lutris/runners/fsuae.py:31 -msgid "Amiga 4000 with 2 MB chip RAM and a 68040" -msgstr "Amiga 4000(2 MB 内存,68040 处理器)" +#: lutris/runners/fsuae.py:125 +msgid "Amiga emulator" +msgstr "Amiga 模拟器" -#: lutris/runners/fsuae.py:36 +#: lutris/runners/fsuae.py:141 msgid "68000" msgstr "68000" -#: lutris/runners/fsuae.py:37 +#: lutris/runners/fsuae.py:142 msgid "68010" msgstr "68010" -#: lutris/runners/fsuae.py:38 +#: lutris/runners/fsuae.py:143 msgid "68020 with 24-bit addressing" msgstr "68020 带24位寻址" -#: lutris/runners/fsuae.py:39 +#: lutris/runners/fsuae.py:144 msgid "68020" msgstr "68020" -#: lutris/runners/fsuae.py:40 +#: lutris/runners/fsuae.py:145 msgid "68030 without internal MMU" msgstr "68030 不带内部 MMU" -#: lutris/runners/fsuae.py:41 +#: lutris/runners/fsuae.py:146 msgid "68030" msgstr "68030" -#: lutris/runners/fsuae.py:42 +#: lutris/runners/fsuae.py:147 msgid "68040 without internal FPU and MMU" msgstr "68040 不带内部 FPU 和 MMU" -#: lutris/runners/fsuae.py:43 +#: lutris/runners/fsuae.py:148 msgid "68040 without internal FPU" msgstr "68040 不带内部 FPU" -#: lutris/runners/fsuae.py:44 +#: lutris/runners/fsuae.py:149 msgid "68040 without internal MMU" msgstr "68040 不带内部 MMU" -#: lutris/runners/fsuae.py:45 +#: lutris/runners/fsuae.py:150 msgid "68040" msgstr "68040" -#: lutris/runners/fsuae.py:46 +#: lutris/runners/fsuae.py:151 msgid "68060 without internal FPU and MMU" msgstr "68060 不带内部 FPU 和 MMU" -#: lutris/runners/fsuae.py:47 +#: lutris/runners/fsuae.py:152 msgid "68060 without internal FPU" msgstr "68060 不带内部 FPU" -#: lutris/runners/fsuae.py:48 +#: lutris/runners/fsuae.py:153 msgid "68060 without internal MMU" msgstr "68060 不带内部 MMU" -#: lutris/runners/fsuae.py:49 +#: lutris/runners/fsuae.py:154 msgid "68060" msgstr "68060" -#: lutris/runners/fsuae.py:53 lutris/runners/fsuae.py:60 -#: lutris/runners/fsuae.py:94 +#: lutris/runners/fsuae.py:158 lutris/runners/fsuae.py:165 +#: lutris/runners/fsuae.py:199 msgid "0" msgstr "0" -#: lutris/runners/fsuae.py:54 lutris/runners/fsuae.py:61 -#: lutris/runners/fsuae.py:95 +#: lutris/runners/fsuae.py:159 lutris/runners/fsuae.py:166 +#: lutris/runners/fsuae.py:200 msgid "1 MB" msgstr "1 MB" -#: lutris/runners/fsuae.py:55 lutris/runners/fsuae.py:62 -#: lutris/runners/fsuae.py:96 +#: lutris/runners/fsuae.py:160 lutris/runners/fsuae.py:167 +#: lutris/runners/fsuae.py:201 msgid "2 MB" msgstr "2 MB" -#: lutris/runners/fsuae.py:56 lutris/runners/fsuae.py:63 -#: lutris/runners/fsuae.py:97 +#: lutris/runners/fsuae.py:161 lutris/runners/fsuae.py:168 +#: lutris/runners/fsuae.py:202 msgid "4 MB" msgstr "4 MB" -#: lutris/runners/fsuae.py:57 lutris/runners/fsuae.py:64 -#: lutris/runners/fsuae.py:98 +#: lutris/runners/fsuae.py:162 lutris/runners/fsuae.py:169 +#: lutris/runners/fsuae.py:203 msgid "8 MB" msgstr "8 MB" -#: lutris/runners/fsuae.py:65 lutris/runners/fsuae.py:99 +#: lutris/runners/fsuae.py:170 lutris/runners/fsuae.py:204 msgid "16 MB" msgstr "16 MB" -#: lutris/runners/fsuae.py:66 lutris/runners/fsuae.py:100 +#: lutris/runners/fsuae.py:171 lutris/runners/fsuae.py:205 msgid "32 MB" msgstr "32 MB" -#: lutris/runners/fsuae.py:67 lutris/runners/fsuae.py:101 +#: lutris/runners/fsuae.py:172 lutris/runners/fsuae.py:206 msgid "64 MB" msgstr "64 MB" -#: lutris/runners/fsuae.py:68 lutris/runners/fsuae.py:102 +#: lutris/runners/fsuae.py:173 lutris/runners/fsuae.py:207 msgid "128 MB" msgstr "128 MB" -#: lutris/runners/fsuae.py:69 lutris/runners/fsuae.py:103 +#: lutris/runners/fsuae.py:174 lutris/runners/fsuae.py:208 msgid "256 MB" msgstr "256 MB" -#: lutris/runners/fsuae.py:70 +#: lutris/runners/fsuae.py:175 msgid "384 MB" msgstr "384 MB" -#: lutris/runners/fsuae.py:71 +#: lutris/runners/fsuae.py:176 msgid "512 MB" msgstr "512 MB" -#: lutris/runners/fsuae.py:72 +#: lutris/runners/fsuae.py:177 msgid "768 MB" msgstr "768 MB" -#: lutris/runners/fsuae.py:73 +#: lutris/runners/fsuae.py:178 msgid "1 GB" msgstr "1 GB" -#: lutris/runners/fsuae.py:106 +#: lutris/runners/fsuae.py:211 msgid "Turbo" msgstr "Turbo 模式" -#: lutris/runners/fsuae.py:117 +#: lutris/runners/fsuae.py:222 msgid "Boot disk" msgstr "启动盘" -#: lutris/runners/fsuae.py:120 +#: lutris/runners/fsuae.py:225 msgid "" "The main floppy disk file with the game data. \n" "FS-UAE supports floppy images in multiple file formats: ADF, IPF, DMS are " @@ -2424,40 +2519,40 @@ "Amiga CD32 and CDTV models." msgstr "" "包含游戏数据的主软盘文件。\n" -"FS-UAE 支持多种格式的软盘映像:ADF,IPF,DMS 是最常见的格式。还支持读取 ADZ" -"(压缩 ADF)和 zip 压缩包里的 ADF 文件。\n" +"FS-UAE 支持多种格式的软盘映像:ADF,IPF,DMS 是最常见的格式。还支持读取 " +"ADZ(压缩 ADF)和 zip 压缩包里的 ADF 文件。\n" "以 .hdf 结尾的文件将作为硬盘驱动器安装,ISO 光盘映像可以用于 Amiga CD32 和 " "CDTV 型号。" -#: lutris/runners/fsuae.py:130 +#: lutris/runners/fsuae.py:235 msgid "Additionnal floppies" msgstr "额外软盘" -#: lutris/runners/fsuae.py:132 +#: lutris/runners/fsuae.py:237 msgid "The additional floppy disk image(s)." msgstr "额外的软盘映像。" -#: lutris/runners/fsuae.py:135 +#: lutris/runners/fsuae.py:240 msgid "CD-ROM image" msgstr "光盘映像" -#: lutris/runners/fsuae.py:137 +#: lutris/runners/fsuae.py:242 msgid "CD-ROM image to use on non CD32/CDTV models" msgstr "在非 CD32/CDTV 机型上使用的光盘映像" -#: lutris/runners/fsuae.py:144 +#: lutris/runners/fsuae.py:249 msgid "Amiga model" msgstr "Amiga 型号" -#: lutris/runners/fsuae.py:148 +#: lutris/runners/fsuae.py:253 msgid "Specify the Amiga model you want to emulate." msgstr "指定要模拟的 Amiga 机型。" -#: lutris/runners/fsuae.py:152 +#: lutris/runners/fsuae.py:257 msgid "Kickstart ROMs location" msgstr "Kickstart ROM 位置" -#: lutris/runners/fsuae.py:155 +#: lutris/runners/fsuae.py:260 msgid "" "Choose the folder containing original Amiga Kickstart ROMs. Refer to FS-UAE " "documentation to find how to acquire them. Without these, FS-UAE uses a " @@ -2467,33 +2562,33 @@ "如果未设置,FS-UAE 将使用自带的内置 ROM,该内置 ROM 与 Amiga 软件的兼容性较" "差。" -#: lutris/runners/fsuae.py:164 +#: lutris/runners/fsuae.py:269 msgid "Extended Kickstart location" msgstr "扩展 Kickstart 路径" -#: lutris/runners/fsuae.py:167 +#: lutris/runners/fsuae.py:272 msgid "Location of extended Kickstart used for CD32" msgstr "用于 CD32 的扩展 Kickstart 位置" -#: lutris/runners/fsuae.py:171 +#: lutris/runners/fsuae.py:276 msgid "Fullscreen (F12 + S to switch)" msgstr "全屏模式(可用 F12 + S 切换)" -#: lutris/runners/fsuae.py:177 lutris/runners/o2em.py:81 +#: lutris/runners/fsuae.py:282 lutris/runners/o2em.py:81 msgid "Scanlines display style" msgstr "显示模拟扫描线" -#: lutris/runners/fsuae.py:180 lutris/runners/o2em.py:83 +#: lutris/runners/fsuae.py:285 lutris/runners/o2em.py:83 msgid "" "Activates a display filter adding scanlines to imitate the displays of " "yesteryear." msgstr "启用显示滤镜,添加扫描线来模仿以前的 CRT 显像管显示效果。" -#: lutris/runners/fsuae.py:185 +#: lutris/runners/fsuae.py:290 msgid "CPU" msgstr "CPU" -#: lutris/runners/fsuae.py:190 +#: lutris/runners/fsuae.py:295 msgid "" "Use this option to override the CPU model in the emulated Amiga. All Amiga " "models imply a default CPU model, so you only need to use this option if you " @@ -2502,19 +2597,19 @@ "此选项可更改 Amiga 模拟器的 CPU 型号。模拟器中的所有 Amiga 机型都使用同一个默" "认 CPU 型号,使用此选项可选择其他 CPU 型号。" -#: lutris/runners/fsuae.py:196 +#: lutris/runners/fsuae.py:301 msgid "Fast Memory" msgstr "快速内存" -#: lutris/runners/fsuae.py:201 +#: lutris/runners/fsuae.py:306 msgid "Specify how much Fast Memory the Amiga model should have." msgstr "指定 Amiga 机型拥有的快速内存。" -#: lutris/runners/fsuae.py:205 +#: lutris/runners/fsuae.py:310 msgid "Zorro III RAM" msgstr "Zorro III 内存" -#: lutris/runners/fsuae.py:210 +#: lutris/runners/fsuae.py:315 msgid "" "Override the amount of Zorro III Fast memory, specified in KB. Must be a " "multiple of 1024. The default value depends on [amiga_model]. Requires a " @@ -2524,21 +2619,21 @@ "于模拟的 Amiga 机型。需要选择具有32位地址总线的处理器(例如 A1200/020 型号)" "才能使用该选项。" -#: lutris/runners/fsuae.py:216 +#: lutris/runners/fsuae.py:321 msgid "Floppy Drive Volume" msgstr "软驱响声音量" -#: lutris/runners/fsuae.py:221 +#: lutris/runners/fsuae.py:326 msgid "" "Set volume to 0 to disable floppy drive clicks when the drive is empty. Max " "volume is 100." msgstr "将音量设为 0 可禁用驱动器为空时的软盘驱动器卡嗒声。最大音量为 100。" -#: lutris/runners/fsuae.py:226 +#: lutris/runners/fsuae.py:331 msgid "Floppy Drive Speed" msgstr "软驱速度" -#: lutris/runners/fsuae.py:232 +#: lutris/runners/fsuae.py:337 msgid "" "Set the speed of the emulated floppy drives, in percent. For example, you " "can specify 800 to get an 8x increase in speed. Use 0 to specify turbo mode. " @@ -2549,53 +2644,53 @@ "倍。您也可以选择 Turbo 模式,Turbo 模式意味着所有软盘操作都将立即完成。对于大" "多数型号,默认值为 100。" -#: lutris/runners/fsuae.py:240 +#: lutris/runners/fsuae.py:345 msgid "Graphics Card" msgstr "显卡" -#: lutris/runners/fsuae.py:246 +#: lutris/runners/fsuae.py:351 msgid "" "Use this option to enable a graphics card. This option is none by default, " "in which case only chipset graphics (OCS/ECS/AGA) support is available." msgstr "" "使用此选项启用独显。默认情况下此选项为空,此时仅板载显卡(OCS/ECS/AGA)可用。" -#: lutris/runners/fsuae.py:252 +#: lutris/runners/fsuae.py:357 msgid "Graphics Card RAM" msgstr "显存" -#: lutris/runners/fsuae.py:258 +#: lutris/runners/fsuae.py:363 msgid "" "Override the amount of graphics memory on the graphics card. The 0 MB option " "is not really valid, but exists for user interface reasons." msgstr "修改显卡上的显存容量。默认值 0 表示不修改显存容量。" -#: lutris/runners/fsuae.py:264 +#: lutris/runners/fsuae.py:369 msgid "JIT Compiler" msgstr "启用 JIT 即时编译器" -#: lutris/runners/fsuae.py:271 +#: lutris/runners/fsuae.py:376 msgid "Feral GameMode" msgstr "禁用游戏性能优先模式" -#: lutris/runners/fsuae.py:275 +#: lutris/runners/fsuae.py:380 msgid "" "Automatically uses Feral GameMode daemon if available. Set to true to " "disable the feature." msgstr "如果游戏性能优先模式可用,默认会自动启用,您可以在此处禁用该功能。" -#: lutris/runners/fsuae.py:280 +#: lutris/runners/fsuae.py:385 msgid "CPU governor warning" msgstr "禁用 CPU调速器警告" -#: lutris/runners/fsuae.py:285 +#: lutris/runners/fsuae.py:390 msgid "" "Warn if running with a CPU governor other than performance. Set to true to " "disable the warning." msgstr "" "如果 CPU 调速器设置过头,性能不好,则启动时会发出警告。您可以在此处禁用警告。" -#: lutris/runners/fsuae.py:290 +#: lutris/runners/fsuae.py:395 msgid "UAE bsdsocket.library" msgstr "UAE bsdsocket.library" @@ -2748,31 +2843,31 @@ msgid "Resolution" msgstr "分辨率" -#: lutris/runners/libretro.py:65 +#: lutris/runners/libretro.py:64 msgid "Libretro" msgstr "Libretro" -#: lutris/runners/libretro.py:66 +#: lutris/runners/libretro.py:65 msgid "Multi-system emulator" msgstr "多平台模拟器" -#: lutris/runners/libretro.py:79 +#: lutris/runners/libretro.py:78 msgid "Core" msgstr "内核" -#: lutris/runners/libretro.py:88 lutris/runners/zdoom.py:87 +#: lutris/runners/libretro.py:87 lutris/runners/zdoom.py:87 msgid "Config file" msgstr "配置文件" -#: lutris/runners/libretro.py:100 +#: lutris/runners/libretro.py:99 msgid "Verbose logging" msgstr "详细日志" -#: lutris/runners/libretro.py:271 +#: lutris/runners/libretro.py:270 msgid "No core has been selected for this game" msgstr "请先为该游戏选择内核" -#: lutris/runners/libretro.py:282 +#: lutris/runners/libretro.py:281 msgid "No game file specified" msgstr "未指定游戏文件" @@ -3621,11 +3716,11 @@ msgid "Path to EBOOT.BIN" msgstr "EBOOT.BIN 路径" -#: lutris/runners/runner.py:148 +#: lutris/runners/runner.py:154 msgid "Custom executable for the runner" msgstr "自定义运行环境可执行文件" -#: lutris/runners/runner.py:293 +#: lutris/runners/runner.py:299 msgid "" "The required runner is not installed.\n" "Do you wish to install it now?" @@ -3633,7 +3728,7 @@ "所需的运行环境未安装。\n" "是否立即安装?" -#: lutris/runners/runner.py:295 +#: lutris/runners/runner.py:301 msgid "Required runner unavailable" msgstr "所需的运行环境不可用" @@ -3946,10 +4041,6 @@ msgid "Runs Steam for Linux games" msgstr "运行 Linux 版 Steam 上的游戏" -#: lutris/runners/steam.py:35 -msgid "Application ID" -msgstr "应用 ID" - #: lutris/runners/steam.py:38 msgid "" "The application ID can be retrieved from the game's page at steampowered." @@ -4650,7 +4741,7 @@ msgid "Custom directory for desktop integration folders." msgstr "自定义“文档”、“下载”、“视频”等文件夹的存放位置。" -#: lutris/runners/wine.py:490 +#: lutris/runners/wine.py:491 msgid "Run EXE inside Wine prefix" msgstr "在 Wine 容器中运行 EXE" @@ -4658,27 +4749,27 @@ msgid "Wine configuration" msgstr "Wine 设置" -#: lutris/runners/wine.py:494 +#: lutris/runners/wine.py:493 msgid "Open Bash terminal" msgstr "Linux 终端" -#: lutris/runners/wine.py:495 +#: lutris/runners/wine.py:494 msgid "Open Wine console" msgstr "Wine CMD" -#: lutris/runners/wine.py:496 +#: lutris/runners/wine.py:495 msgid "Wine registry" msgstr "Wine 注册表编辑器" -#: lutris/runners/wine.py:497 +#: lutris/runners/wine.py:496 msgid "Winetricks" msgstr "Winetricks" -#: lutris/runners/wine.py:498 +#: lutris/runners/wine.py:497 msgid "Wine Control Panel" msgstr "Wine 控制面板" -#: lutris/runners/wine.py:661 +#: lutris/runners/wine.py:659 msgid "Select an EXE or MSI file" msgstr "选择一个 EXE、MSI 或 LNK 文件" @@ -4773,18 +4864,36 @@ "用于加载用户创建的配置文件。如果指定,文件必须包含 wad 目录列表,否则启动将失" "败。" +#: lutris/services/amazon.py:58 lutris/services/amazon.py:637 +msgid "Amazon Prime Gaming" +msgstr "亚马逊Prime游戏" + +#: lutris/services/amazon.py:624 +#, python-format +msgid "Installing file: %s" +msgstr "正在安装:%s" + +#: lutris/services/base.py:310 +#, python-format +msgid "" +"This service requires a game launcher. The following steps will install it.\n" +"Once the client is installed, you can login to %s." +msgstr "" +"此服务需要安装启动器,请通过以下步骤安装。\n" +"安装完成后,您就可以登录到 %s。" + #: lutris/services/battlenet.py:13 msgid "Battle.net" msgstr "暴雪战网" -#: lutris/services/bethesda.py:13 -msgid "Bethesda" -msgstr "Bethesda" - #: lutris/services/egs.py:134 msgid "Epic Games Store" msgstr "Epic 游戏商城" +#: lutris/services/flathub.py:56 +msgid "Flathub" +msgstr "Flathub" + #: lutris/services/gog.py:68 msgid "GOG" msgstr "GOG" @@ -4797,7 +4906,7 @@ msgid "Itch.io (Not implemented)" msgstr "Itch.io(未实现)" -#: lutris/services/origin.py:87 +#: lutris/services/origin.py:128 msgid "Origin" msgstr "Origin" @@ -4871,44 +4980,52 @@ msgid "Keep current" msgstr "保持现状" -#: lutris/sysoptions.py:37 lutris/sysoptions.py:46 lutris/sysoptions.py:57 -#: lutris/sysoptions.py:501 +#: lutris/sysoptions.py:43 +msgid "(recommended)" +msgstr "(推荐)" + +#: lutris/sysoptions.py:46 +msgid "System" +msgstr "系统默认" + +#: lutris/sysoptions.py:55 lutris/sysoptions.py:64 lutris/sysoptions.py:75 +#: lutris/sysoptions.py:539 msgid "Off" msgstr "关闭" -#: lutris/sysoptions.py:38 +#: lutris/sysoptions.py:56 msgid "Primary" msgstr "首选屏幕" -#: lutris/sysoptions.py:85 +#: lutris/sysoptions.py:101 msgid "Auto: WARNING -- No Vulkan Loader detected!" msgstr "自动:【警告】未检测到 Vulkan 加载器!" -#: lutris/sysoptions.py:119 +#: lutris/sysoptions.py:127 msgid "Auto: Intel Open Source (MESA: ANV)" msgstr "自动:Intel Open Source (MESA: ANV)" -#: lutris/sysoptions.py:121 +#: lutris/sysoptions.py:128 msgid "Auto: AMD RADV Open Source (MESA: RADV)" msgstr "自动:AMD RADV Open Source (MESA: RADV)" -#: lutris/sysoptions.py:123 +#: lutris/sysoptions.py:129 msgid "Auto: Nvidia Proprietary" msgstr "自动:Nvidia Proprietary" -#: lutris/sysoptions.py:145 +#: lutris/sysoptions.py:172 msgid "Default installation folder" msgstr "默认安装目录" -#: lutris/sysoptions.py:148 +#: lutris/sysoptions.py:175 msgid "The default folder where you install your games." msgstr "安装游戏的默认文件夹。" -#: lutris/sysoptions.py:156 +#: lutris/sysoptions.py:183 msgid "Disable Lutris Runtime" msgstr "禁用 Lutris 运行库" -#: lutris/sysoptions.py:159 +#: lutris/sysoptions.py:186 msgid "" "The Lutris Runtime loads some libraries before running the game, which can " "cause some incompatibilities in some cases. Check this option to disable it." @@ -4916,11 +5033,11 @@ "Lutris 运行库是在游戏运行前加载的一系列共享库。在某些情况下,这可能会导致不兼" "容。选中此选项可将其禁用。" -#: lutris/sysoptions.py:166 +#: lutris/sysoptions.py:193 msgid "Prefer system libraries" msgstr "优先使用系统库" -#: lutris/sysoptions.py:168 +#: lutris/sysoptions.py:195 msgid "" "When the runtime is enabled, prioritize the system libraries over the " "provided ones." @@ -4928,11 +5045,11 @@ "启用后,将优先使用本机 Linux 系统提供的共享库,而不是 Lutris 或运行环境自带的" "库。" -#: lutris/sysoptions.py:174 +#: lutris/sysoptions.py:201 msgid "Restore resolution on game exit" msgstr "游戏退出时恢复分辨率" -#: lutris/sysoptions.py:176 +#: lutris/sysoptions.py:203 msgid "" "Some games don't restore your screen resolution when \n" "closed or when they crash. This is when this option comes \n" @@ -4941,11 +5058,11 @@ "某些游戏在关闭或意外崩溃时无法恢复屏幕分辨率,启用这个选项让 Lutris 帮您恢" "复。" -#: lutris/sysoptions.py:183 +#: lutris/sysoptions.py:210 msgid "Enable gamescope" msgstr "启用 gamescope 游戏窗口隔离" -#: lutris/sysoptions.py:187 +#: lutris/sysoptions.py:214 msgid "" "Use gamescope to draw the game window isolated from your desktop.\n" "Use Ctrl+Super+F to toggle fullscreen" @@ -4953,35 +5070,35 @@ "使用 gamescope 绘制与当前桌面隔离的游戏窗口。\n" "可按 Ctrl+Super+F 组合键切换全屏与窗口模式。" -#: lutris/sysoptions.py:193 +#: lutris/sysoptions.py:220 msgid "Gamescope output resolution" msgstr "gamescope 输出分辨率" -#: lutris/sysoptions.py:197 +#: lutris/sysoptions.py:224 msgid "Resolution of the window on your desktop" msgstr "窗口在桌面上显示的分辨率" -#: lutris/sysoptions.py:202 +#: lutris/sysoptions.py:229 msgid "Gamescope game resolution" msgstr "gamescope 游戏分辨率" -#: lutris/sysoptions.py:206 +#: lutris/sysoptions.py:233 msgid "Resolution of the screen visible to the game" msgstr "游戏读取到的屏幕分辨率" -#: lutris/sysoptions.py:211 +#: lutris/sysoptions.py:238 msgid "Restrict number of cores used" msgstr "限制 CPU 核数" -#: lutris/sysoptions.py:214 +#: lutris/sysoptions.py:241 msgid "Restrict the game to a maximum number of CPU cores." msgstr "限制游戏可用的最大 CPU 核数。" -#: lutris/sysoptions.py:219 +#: lutris/sysoptions.py:246 msgid "Restrict number of cores to" msgstr "最大可用核数" -#: lutris/sysoptions.py:222 +#: lutris/sysoptions.py:249 msgid "" "Maximum number of CPU cores to be used, if 'Restrict number of cores used' " "is turned on." @@ -4989,11 +5106,11 @@ "如果启用了“限制 CPU 核数”,则在此处设置要使用的最大 CPU 核数,默认为 1(单" "核)。" -#: lutris/sysoptions.py:228 +#: lutris/sysoptions.py:255 msgid "Restore gamma on game exit" msgstr "游戏退出时恢复伽玛值(亮度阈值)" -#: lutris/sysoptions.py:230 +#: lutris/sysoptions.py:257 msgid "" "Some games don't correctly restores gamma on exit, making your display too " "bright. Select this option to correct it." @@ -5001,58 +5118,58 @@ "某些游戏退出时无法正确恢复伽玛值(亮度阈值),从而使显示效果太亮或太暗。启用" "此项让 Lutris 帮您恢复。" -#: lutris/sysoptions.py:235 +#: lutris/sysoptions.py:262 msgid "Disable desktop effects" msgstr "禁用桌面特效" -#: lutris/sysoptions.py:239 +#: lutris/sysoptions.py:266 msgid "" "Disable desktop effects while game is running, reducing stuttering and " "increasing performance" msgstr "在游戏运行时禁用桌面特效(桌面合成),可减少卡顿、提高性能" -#: lutris/sysoptions.py:244 +#: lutris/sysoptions.py:271 msgid "Disable screen saver" msgstr "禁用屏幕保护程序" -#: lutris/sysoptions.py:249 +#: lutris/sysoptions.py:276 msgid "" "Disable the screen saver while a game is running. Requires the screen " "saver's functionality to be exposed over DBus." msgstr "" "在游戏运行时禁用屏幕保护程序。屏幕保护程序需要向 DBus 注册,否则该选项无效。" -#: lutris/sysoptions.py:256 +#: lutris/sysoptions.py:283 msgid "Reset PulseAudio" msgstr "重置 PulseAudio 声音服务" -#: lutris/sysoptions.py:260 +#: lutris/sysoptions.py:287 msgid "Restart PulseAudio before launching the game." msgstr "启动游戏前重启 PulseAudio 声音服务。" -#: lutris/sysoptions.py:265 +#: lutris/sysoptions.py:292 msgid "Reduce PulseAudio latency" msgstr "减少 PulseAudio 延迟" -#: lutris/sysoptions.py:269 +#: lutris/sysoptions.py:296 msgid "" "Set the environment variable PULSE_LATENCY_MSEC=60 to improve audio quality " "on some games" msgstr "设置环境变量 PULSE_LATENCY_MSEC=60 以提高某些游戏的音频质量" -#: lutris/sysoptions.py:275 +#: lutris/sysoptions.py:302 msgid "Switch to US keyboard layout" msgstr "切换到美式键盘布局" -#: lutris/sysoptions.py:278 +#: lutris/sysoptions.py:305 msgid "Switch to US keyboard QWERTY layout while game is running" msgstr "游戏运行时切换到美式键盘 QWERTY 布局" -#: lutris/sysoptions.py:285 +#: lutris/sysoptions.py:312 msgid "Optimus launcher (NVIDIA Optimus laptops)" msgstr "Optimus 启动器(NVIDIA Optimus 笔记本电脑)" -#: lutris/sysoptions.py:287 +#: lutris/sysoptions.py:314 msgid "" "If you have installed the primus or bumblebee packages, select what launcher " "will run the game with the command, activating your NVIDIA graphic chip for " @@ -5064,11 +5181,11 @@ "活 NVIDIA 独显以实现较高的3D性能。primusrun 通常性能更佳,但 optirun 和 " "virtualgl 的兼容性更好。Primus VK 通过 bumblebee 软件包提供 vulkan 支持。" -#: lutris/sysoptions.py:299 +#: lutris/sysoptions.py:326 msgid "Vulkan ICD loader" msgstr "Vulkan ICD 加载器" -#: lutris/sysoptions.py:301 +#: lutris/sysoptions.py:328 msgid "" "The ICD loader is a library that is placed between a Vulkan application and " "any number of Vulkan drivers, in order to support multiple drivers and the " @@ -5077,34 +5194,34 @@ "ICD 加载器是一个库,它位于 Vulkan 应用程序和任意数量的 Vulkan 驱动程序之间," "以便支持多个驱动程序以及跨这些驱动程序工作的实例级功能。" -#: lutris/sysoptions.py:309 +#: lutris/sysoptions.py:336 msgid "FPS counter (MangoHud)" msgstr "显示帧率(MangoHud)" -#: lutris/sysoptions.py:312 +#: lutris/sysoptions.py:339 msgid "" "Display the game's FPS + other information. Requires MangoHud to be " "installed." msgstr "显示游戏的 FPS 和其他信息。需要安装MangoHud。" -#: lutris/sysoptions.py:321 +#: lutris/sysoptions.py:348 msgid "Limit the game's FPS to desired number" msgstr "限制游戏最大帧率不超过指定数值" -#: lutris/sysoptions.py:328 +#: lutris/sysoptions.py:355 msgid "Enable Feral GameMode" msgstr "启用游戏性能优先模式" -#: lutris/sysoptions.py:329 +#: lutris/sysoptions.py:356 msgid "Request a set of optimisations be temporarily applied to the host OS" msgstr "" "在游戏运行时,请求将一组优化临时应用于主机 Linux 操作系统,以提升游戏性能" -#: lutris/sysoptions.py:336 +#: lutris/sysoptions.py:363 msgid "Enable NVIDIA Prime Render Offload" msgstr "启用 NVIDIA Prime 渲染卸载" -#: lutris/sysoptions.py:337 +#: lutris/sysoptions.py:364 msgid "" "If you have the latest NVIDIA driver and the properly patched xorg-server " "(see https://download.nvidia.com/XFree86/Linux-x86_64/435.17/README/" @@ -5118,11 +5235,11 @@ "Render Offload)功能。这将应用 __NV_PRIME_RENDER_OFFLOAD=1 和 " "__GLX_VENDOR_LIBRARY_NAME=nvidia 环境变量。" -#: lutris/sysoptions.py:348 +#: lutris/sysoptions.py:375 msgid "Use discrete graphics" msgstr "使用独立显卡" -#: lutris/sysoptions.py:350 +#: lutris/sysoptions.py:377 msgid "" "If you have open source graphic drivers (Mesa), selecting this option will " "run the game with the 'DRI_PRIME=1' environment variable, activating your " @@ -5131,11 +5248,11 @@ "如果您具有开源图形驱动程序(Mesa),则选择此选项将使用环境变量“DRI_PRIME=1”来" "运行游戏,从而激活您的独显以获得更高的3D性能。" -#: lutris/sysoptions.py:358 +#: lutris/sysoptions.py:385 msgid "SDL 1.2 Fullscreen Monitor" msgstr "指定 SDL 1.2 程序使用的显示器" -#: lutris/sysoptions.py:362 +#: lutris/sysoptions.py:389 msgid "" "Hint SDL 1.2 games to use a specific monitor when going fullscreen by " "setting the SDL_VIDEO_FULLSCREEN environment variable" @@ -5143,11 +5260,11 @@ "通过设置 SDL_VIDEO_FULLSCREEN 环境变量,让 SDL 1.2 游戏在进入全屏模式时使用特" "定的显示器" -#: lutris/sysoptions.py:369 +#: lutris/sysoptions.py:396 msgid "Turn off monitors except" msgstr "关闭除指定显示器外的其他显示器" -#: lutris/sysoptions.py:374 +#: lutris/sysoptions.py:401 msgid "" "Only keep the selected screen active while the game is running. \n" "This is useful if you have a dual-screen setup, and are \n" @@ -5156,19 +5273,19 @@ "游戏运行时仅使选定的屏幕保持活动状态。\n" "如果您有多块显示器,并且在全屏模式下运行游戏时出现显示问题,可以试试该选项。" -#: lutris/sysoptions.py:382 +#: lutris/sysoptions.py:409 msgid "Switch resolution to" msgstr "切换分辨率为" -#: lutris/sysoptions.py:386 +#: lutris/sysoptions.py:413 msgid "Switch to this screen resolution while the game is running." msgstr "在游戏运行时切换到此屏幕分辨率。" -#: lutris/sysoptions.py:390 +#: lutris/sysoptions.py:417 msgid "CLI mode" msgstr "文本模式" -#: lutris/sysoptions.py:394 +#: lutris/sysoptions.py:421 msgid "" "Enable a terminal for text-based games. Only useful for ASCII based games. " "May cause issues with graphical games." @@ -5176,11 +5293,11 @@ "为基于文本的游戏打开终端,仅对基于 ASCII 的游戏有用。可能会导致使用图形的游戏" "出现问题。" -#: lutris/sysoptions.py:399 +#: lutris/sysoptions.py:426 msgid "Text based games emulator" msgstr "基于文本的游戏模拟器" -#: lutris/sysoptions.py:404 +#: lutris/sysoptions.py:431 msgid "" "The terminal emulator used with the CLI mode. Choose from the list of " "detected terminal apps or enter the terminal's command or path." @@ -5189,70 +5306,76 @@ "终端的命令或路径。\n" "注意:不能保证所有终端模拟器都与 Lutris 兼容。" -#: lutris/sysoptions.py:411 -msgid "Environment variables" -msgstr "环境变量" +#: lutris/sysoptions.py:438 +msgid "Locale" +msgstr "区域设置" + +#: lutris/sysoptions.py:444 +msgid "" +"Can be used to force certain locale for an app. Fixes encoding issues in " +"legacy software." +msgstr "强制指定应用程序的某些区域设置,可用于修复旧版软件中的乱码问题。" -#: lutris/sysoptions.py:412 +#: lutris/sysoptions.py:450 msgid "Environment variables loaded at run time" msgstr "游戏运行时加载的环境变量" -#: lutris/sysoptions.py:417 +#: lutris/sysoptions.py:455 msgid "AntiMicroX Profile" msgstr "AntiMicroX 配置" -#: lutris/sysoptions.py:419 +#: lutris/sysoptions.py:457 msgid "Path to an AntiMicroX profile file" msgstr "AntiMicroX 配置文件的路径" -#: lutris/sysoptions.py:424 +#: lutris/sysoptions.py:462 msgid "Command prefix" msgstr "命令前缀" -#: lutris/sysoptions.py:426 +#: lutris/sysoptions.py:464 msgid "" "Command line instructions to add in front of the game's execution command." msgstr "添加到游戏启动命令之前的命令行指令。" -#: lutris/sysoptions.py:432 +#: lutris/sysoptions.py:470 msgid "Manual script" msgstr "维护脚本" -#: lutris/sysoptions.py:434 +#: lutris/sysoptions.py:472 msgid "Script to execute from the game's contextual menu" msgstr "" "设置该选项后,游戏的右键菜单中将出现“执行维护脚本”菜单项,点击可快速执行该脚" "本。" -#: lutris/sysoptions.py:439 +#: lutris/sysoptions.py:477 msgid "Pre-launch script" msgstr "预启动脚本" -#: lutris/sysoptions.py:441 +#: lutris/sysoptions.py:479 msgid "Script to execute before the game starts" msgstr "游戏开始前执行的脚本" -#: lutris/sysoptions.py:446 +#: lutris/sysoptions.py:484 msgid "Wait for pre-launch script completion" msgstr "等待预启动脚本完成" -#: lutris/sysoptions.py:449 +#: lutris/sysoptions.py:487 msgid "Run the game only once the pre-launch script has exited" msgstr "等待预启动脚本退出后才运行游戏" -#: lutris/sysoptions.py:454 +#: lutris/sysoptions.py:492 msgid "Post-exit script" msgstr "退出脚本" -#: lutris/sysoptions.py:456 +#: lutris/sysoptions.py:494 msgid "Script to execute when the game exits" msgstr "游戏退出时执行的脚本" -#: lutris/sysoptions.py:461 +#: lutris/sysoptions.py:499 msgid "Include processes" msgstr "包括进程" -#: lutris/sysoptions.py:463 +#: lutris/sysoptions.py:501 msgid "" "What processes to include in process monitoring. This is to override the " "built-in exclude list.\n" @@ -5263,11 +5386,11 @@ "用空格分隔的列表,名称包括空格的进程可以用引号引起来。\n" "如果列表中的任一进程仍在运行,Lutris 就视为游戏仍在运行。" -#: lutris/sysoptions.py:471 +#: lutris/sysoptions.py:509 msgid "Exclude processes" msgstr "排除进程" -#: lutris/sysoptions.py:473 +#: lutris/sysoptions.py:511 msgid "" "What processes to exclude in process monitoring. For example background " "processes that stick around after the game has been closed.\n" @@ -5278,11 +5401,11 @@ "用空格分隔的列表,名称包括空格的进程可以用引号引起来。\n" "如果只有排除列表中的进程在运行,Lutris 就视为游戏已停止运行。" -#: lutris/sysoptions.py:482 +#: lutris/sysoptions.py:520 msgid "Killswitch file" msgstr "Killswitch 文件" -#: lutris/sysoptions.py:484 +#: lutris/sysoptions.py:522 msgid "" "Path to a file which will stop the game when deleted \n" "(usually /dev/input/js0 to stop the game on joystick unplugging)" @@ -5290,11 +5413,11 @@ "设置一个文件,如果该文件被删除则停止游戏\n" "(通常是 /dev/input/js0,用于在拔下摇杆时停止游戏)" -#: lutris/sysoptions.py:491 +#: lutris/sysoptions.py:529 msgid "SDL2 gamepad mapping" msgstr "SDL2 游戏手柄映射" -#: lutris/sysoptions.py:493 +#: lutris/sysoptions.py:531 msgid "" "SDL_GAMECONTROLLERCONFIG mapping string or path to a custom gamecontrollerdb." "txt file containing mappings." @@ -5302,63 +5425,63 @@ "SDL_GAMECONTROLLERCONFIG 映射字符串或包含映射的自定义 gamecontrollerdb.txt 文" "件路径。" -#: lutris/sysoptions.py:498 +#: lutris/sysoptions.py:536 msgid "Use Xephyr" msgstr "使用 Xephyr" -#: lutris/sysoptions.py:502 +#: lutris/sysoptions.py:540 msgid "8BPP (256 colors)" msgstr "8位(256色)" -#: lutris/sysoptions.py:503 +#: lutris/sysoptions.py:541 msgid "16BPP (65536 colors)" msgstr "16位(6万色)" -#: lutris/sysoptions.py:504 +#: lutris/sysoptions.py:542 msgid "24BPP (16M colors)" msgstr "24位(1600万色)" -#: lutris/sysoptions.py:508 +#: lutris/sysoptions.py:546 msgid "Run program in Xephyr to support 8BPP and 16BPP color modes" msgstr "在 Xephyr 中运行程序以支持8位和16位色彩模式" -#: lutris/sysoptions.py:513 +#: lutris/sysoptions.py:551 msgid "Xephyr resolution" msgstr "Xephyr 分辨率" -#: lutris/sysoptions.py:515 +#: lutris/sysoptions.py:553 msgid "Screen resolution of the Xephyr server" msgstr "Xephyr 服务器的屏幕分辨率" -#: lutris/sysoptions.py:520 +#: lutris/sysoptions.py:558 msgid "Xephyr Fullscreen" msgstr "Xephyr 全屏模式" -#: lutris/sysoptions.py:523 +#: lutris/sysoptions.py:561 msgid "Open Xephyr in fullscreen (at the desktop resolution)" msgstr "以桌面分辨率全屏打开 Xephyr" -#: lutris/util/system.py:20 +#: lutris/util/system.py:21 msgid "Documents" msgstr "文档" -#: lutris/util/system.py:21 +#: lutris/util/system.py:22 msgid "Downloads" msgstr "下载" -#: lutris/util/system.py:22 +#: lutris/util/system.py:23 msgid "Desktop" msgstr "桌面" -#: lutris/util/system.py:23 lutris/util/system.py:25 +#: lutris/util/system.py:24 lutris/util/system.py:26 msgid "Pictures" msgstr "图片" -#: lutris/util/system.py:24 +#: lutris/util/system.py:25 msgid "Videos" msgstr "视频" -#: lutris/util/system.py:26 +#: lutris/util/system.py:27 msgid "Projects" msgstr "工程" @@ -5435,8 +5558,32 @@ "您选择的 Wine 版本不支持 Fsync。\n" "请选择支持 Fsync 的 Wine 版本,或者禁用 Fsync。" -#~ msgid "lutris" -#~ msgstr "lutris" +#~ msgid "video game preservation platform" +#~ msgstr "电子游戏保管平台" + +#~ msgid "Show Tray Icon (requires restart)" +#~ msgstr "显示托盘图标(重启软件生效)" + +#~ msgid "Amiga 500+ with 1 MB chip RAM" +#~ msgstr "Amiga 500+(1 MB 内存)" + +#~ msgid "Amiga 600 with 1 MB chip RAM" +#~ msgstr "Amiga 600(1 MB 内存)" + +#~ msgid "Amiga 1000 with 512 KB chip RAM" +#~ msgstr "Amiga 1000(512 KB 内存)" + +#~ msgid "Amiga 1200 with 2 MB chip RAM" +#~ msgstr "Amiga 1200(2 MB 内存)" + +#~ msgid "Amiga 1200 but with 68020 processor" +#~ msgstr "Amiga 1200(68020 处理器)" + +#~ msgid "Amiga 4000 with 2 MB chip RAM and a 68040" +#~ msgstr "Amiga 4000(2 MB 内存,68040 处理器)" + +#~ msgid "Bethesda" +#~ msgstr "Bethesda" #~ msgid "Lutris Team" #~ msgstr "Lutris 团队" @@ -5825,12 +5972,6 @@ #~ msgid "Run the game in a new terminal window." #~ msgstr "在新的终端窗口中运行该游戏。" -#~ msgid "Terminal application" -#~ msgstr "终端应用" - -#~ msgid "Discord Rich Presence" -#~ msgstr "向 Discord 好友显示游戏状态" - #~ msgid "Enable status to Discord of this game being played" #~ msgstr "在我玩该游戏时向 Discord 好友显示游戏状态" @@ -6167,3 +6308,6 @@ #~ "并为新功能(如云存储或电视上的全屏界面)提供资金!\n" #~ "支持我们!https://lutris.net/donate" + +msgid "Unspecified (Use System Default)" +msgstr "不指定(使用系统默认值)" diff -Nru lutris-0.5.11~ubuntu22.04.1/.pylintrc lutris-0.5.12~ubuntu22.04.1/.pylintrc --- lutris-0.5.11~ubuntu22.04.1/.pylintrc 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/.pylintrc 2022-12-03 12:25:25.000000000 +0000 @@ -56,12 +56,10 @@ global-statement, invalid-name, missing-docstring, - no-self-use, too-few-public-methods, unexpected-keyword-arg, ungrouped-imports, useless-object-inheritance, - bad-continuation, inconsistent-return-statements, unsubscriptable-object, not-an-iterable, @@ -261,12 +259,6 @@ # Maximum number of lines in a module max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no @@ -278,36 +270,21 @@ [BASIC] -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct argument names argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct attribute names attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ @@ -315,9 +292,6 @@ # ones are exempt. docstring-min-length=-1 -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct function names function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ @@ -327,21 +301,12 @@ # Include a hint for the correct naming format with invalid-name include-naming-hint=no -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct method names method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -357,9 +322,6 @@ # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct variable names variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ diff -Nru lutris-0.5.11~ubuntu22.04.1/setup.py lutris-0.5.12~ubuntu22.04.1/setup.py --- lutris-0.5.11~ubuntu22.04.1/setup.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/setup.py 2022-12-03 12:25:25.000000000 +0000 @@ -40,6 +40,7 @@ 'lutris.services', 'lutris.util', 'lutris.util.amazon', + 'lutris.util.discord', 'lutris.util.dolphin', 'lutris.util.egs', 'lutris.util.graphics', @@ -54,12 +55,17 @@ data_files=data_files, zip_safe=False, install_requires=[ - 'PyYAML', - 'PyGObject', + 'certifi', + 'dbus-python', + 'distro', 'evdev', + 'lxml', + 'pillow', + 'PyGObject', + 'pypresence', + 'PyYAML', 'requests', - 'distro', - 'lxml' + 'pypresence' ], url='https://lutris.net', description='Video game preservation platform', diff -Nru lutris-0.5.11~ubuntu22.04.1/share/lutris/json/basiliskii.json lutris-0.5.12~ubuntu22.04.1/share/lutris/json/basiliskii.json --- lutris-0.5.11~ubuntu22.04.1/share/lutris/json/basiliskii.json 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/share/lutris/json/basiliskii.json 2022-12-03 12:25:25.000000000 +0000 @@ -1,6 +1,6 @@ { "human_name": "Basilisk II", - "description": " Basilisk II is an Open Source 68k Macintosh emulator", + "description": "Basilisk II is an Open Source 68k Macintosh emulator", "platforms": ["Macintosh"], "runnable_alone": "True", "runner_executable": "basiliskii/BasiliskII-x86_64.AppImage", diff -Nru lutris-0.5.11~ubuntu22.04.1/share/lutris/json/melonds.json lutris-0.5.12~ubuntu22.04.1/share/lutris/json/melonds.json --- lutris-0.5.11~ubuntu22.04.1/share/lutris/json/melonds.json 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/share/lutris/json/melonds.json 2022-12-03 12:25:25.000000000 +0000 @@ -2,7 +2,7 @@ "human_name": "melonDS", "description": "Nintendo DS emulator", "platforms": ["Nintendo DS"], - "runner_executable": "melonDS/melonDS", + "runner_executable": "melonds/melonDS", "download_url": "http://melonds.kuribo64.net/downloads/melonDS_0.9.3_linux_x64.7z", "game_options": [ { diff -Nru lutris-0.5.11~ubuntu22.04.1/share/lutris/json/sheepshaver.json lutris-0.5.12~ubuntu22.04.1/share/lutris/json/sheepshaver.json --- lutris-0.5.11~ubuntu22.04.1/share/lutris/json/sheepshaver.json 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/share/lutris/json/sheepshaver.json 2022-12-03 12:25:25.000000000 +0000 @@ -14,8 +14,7 @@ } ], "runner_options": [ - - { + { "option": "config", "type": "bool", "label": "config", diff -Nru lutris-0.5.11~ubuntu22.04.1/share/lutris/ui/log-window.ui lutris-0.5.12~ubuntu22.04.1/share/lutris/ui/log-window.ui --- lutris-0.5.11~ubuntu22.04.1/share/lutris/ui/log-window.ui 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/share/lutris/ui/log-window.ui 2022-12-03 12:25:25.000000000 +0000 @@ -1,22 +1,21 @@ - + - False - 640 - 480 - True - lutris - False + False + 640 + 480 + True + lutris + False scrolled_window True - True + True True True - in @@ -25,26 +24,26 @@ True - False - True + False + True True - True - edit-find-symbolic - False - False - Search... + True + edit-find-symbolic + False + False + Search... gtk-save True - True - True - True - True + True + True + True + True 1 diff -Nru lutris-0.5.11~ubuntu22.04.1/share/metainfo/net.lutris.Lutris.metainfo.xml lutris-0.5.12~ubuntu22.04.1/share/metainfo/net.lutris.Lutris.metainfo.xml --- lutris-0.5.11~ubuntu22.04.1/share/metainfo/net.lutris.Lutris.metainfo.xml 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/share/metainfo/net.lutris.Lutris.metainfo.xml 2022-12-03 12:25:25.000000000 +0000 @@ -24,7 +24,7 @@ https://github.com/lutris/lutris/issues net.lutris.Lutris.desktop - + diff -Nru lutris-0.5.11~ubuntu22.04.1/snap/snapcraft.yaml lutris-0.5.12~ubuntu22.04.1/snap/snapcraft.yaml --- lutris-0.5.11~ubuntu22.04.1/snap/snapcraft.yaml 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/snap/snapcraft.yaml 2022-12-03 12:25:25.000000000 +0000 @@ -90,8 +90,7 @@ - gir1.2-glib-2.0 - gir1.2-gnomedesktop-3.0 - gir1.2-gtk-3.0 - - gir1.2-notify-0.7 - - gir1.2-webkit2-4.0 + - gir1.2-webkit2-4.1 - libgnutls30 - libvulkan1 - libxrandr2 diff -Nru lutris-0.5.11~ubuntu22.04.1/tests/test_dialogs.py lutris-0.5.12~ubuntu22.04.1/tests/test_dialogs.py --- lutris-0.5.11~ubuntu22.04.1/tests/test_dialogs.py 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/tests/test_dialogs.py 2022-12-03 12:25:25.000000000 +0000 @@ -1,15 +1,6 @@ -import os from unittest import TestCase -import gi - -gi.require_version('Gtk', '3.0') - -from gi.repository import Gtk - from lutris import runners -from lutris.database import games as games_db -from lutris.game import Game from lutris.gui.application import Application from lutris.gui.config.add_game import AddGameDialog # from lutris import settings @@ -53,58 +44,9 @@ def get_game_box(self): return self.get_viewport(1) - def get_buttons(self): - notebook = self.dlg.vbox.get_children()[1] - button_box = notebook.get_children()[0] - if button_box.__class__ == Gtk.CheckButton: - button_hbox = notebook.get_children()[1] - else: - button_hbox = button_box.get_children()[1] - self.assertEqual(button_hbox.__class__, Gtk.Box) - return button_hbox - def test_dialog(self): self.assertEqual(self.dlg.notebook.get_current_page(), 0) - def test_changing_runner_sets_new_config(self): - label = self.get_notebook().get_children()[1] - self.assertIn('Select a runner', label.get_text()) - - buttons = self.get_buttons().get_children() - self.assertEqual(buttons[0].get_label(), 'Cancel') - self.assertEqual(buttons[1].get_label(), 'Save') - - self.dlg.runner_dropdown.set_active_id('linux') - self.assertEqual(self.dlg.lutris_config.runner_slug, 'linux') - game_box = self.get_game_box() - self.assertEqual(game_box.game.runner_name, 'linux') - exe_box = game_box.get_children()[0].get_children()[0] - exe_field = exe_box.get_children()[1] - self.assertEqual(exe_field.__class__.__name__, 'FileChooserEntry') - - def test_can_add_game(self): - name_entry = self.dlg.name_entry - name_entry.set_text("Test game") - self.dlg.runner_dropdown.set_active_id('linux') - - game_box = self.get_game_box() - exe_box = game_box.get_children()[0].get_children()[0] - exe_label = exe_box.get_children()[0] - self.assertEqual(exe_label.get_text(), "Executable") - test_exe = os.path.abspath(__file__) - exe_field = exe_box.get_children()[1] - exe_field.entry.set_text(test_exe) - self.assertEqual(exe_field.get_text(), test_exe) - - add_button = self.get_buttons().get_children()[1] - add_button.clicked() - - pga_game = games_db.get_game_by_field('test-game', 'slug') - self.assertTrue(pga_game) - game = Game(pga_game['id']) - self.assertEqual(game.name, 'Test game') - game.remove() - class TestSort(TestCase): class FakeModel(object): diff -Nru lutris-0.5.11~ubuntu22.04.1/.travis.yml lutris-0.5.12~ubuntu22.04.1/.travis.yml --- lutris-0.5.11~ubuntu22.04.1/.travis.yml 2022-11-20 10:53:21.000000000 +0000 +++ lutris-0.5.12~ubuntu22.04.1/.travis.yml 2022-12-03 12:25:25.000000000 +0000 @@ -28,8 +28,7 @@ - gir1.2-glib-2.0 - libgirepository1.0-dev - gir1.2-gnomedesktop-3.0 - - gir1.2-webkit2-4.0 - - gir1.2-notify-0.7 + - gir1.2-webkit2-4.1 - at-spi2-core before_install: - "export DISPLAY=:99.0"