1
0
mirror of https://github.com/lutris/lutris synced 2024-06-29 05:34:25 +00:00

Ruff reformat

This commit is contained in:
Mathieu Comandon 2024-02-24 21:02:06 -08:00
parent 2c9d12337d
commit 38fbe9f05e
223 changed files with 4651 additions and 4734 deletions

View File

@ -27,7 +27,7 @@ USER_INFO_FILE_PATH = os.path.join(settings.CACHE_DIR, "user.json")
def get_time_from_api_date(date_string):
"""Convert a date string originating from the API and convert it to a datetime object"""
return time.strptime(date_string[:date_string.find(".")], "%Y-%m-%dT%H:%M:%S")
return time.strptime(date_string[: date_string.find(".")], "%Y-%m-%dT%H:%M:%S")
def get_runtime_versions_date() -> float:
@ -85,7 +85,7 @@ def read_api_key():
"""Read the API token from disk"""
if not system.path_exists(API_KEY_FILE_PATH):
return None
with open(API_KEY_FILE_PATH, "r", encoding='utf-8') as token_file:
with open(API_KEY_FILE_PATH, "r", encoding="utf-8") as token_file:
api_string = token_file.read()
try:
username, token = api_string.split(":")
@ -105,18 +105,23 @@ def connect(username, password):
json_dict = response.json()
if "token" in json_dict:
token = json_dict["token"]
with open(API_KEY_FILE_PATH, "w", encoding='utf-8') as token_file:
with open(API_KEY_FILE_PATH, "w", encoding="utf-8") as token_file:
token_file.write("%s:%s" % (username, token))
account_info = fetch_user_info()
if not account_info:
logger.warning("Unable to fetch user info")
else:
with open(USER_INFO_FILE_PATH, "w", encoding='utf-8') as token_file:
with open(USER_INFO_FILE_PATH, "w", encoding="utf-8") as token_file:
json.dump(account_info, token_file, indent=2)
return token
except (requests.RequestException, requests.ConnectionError, requests.HTTPError, requests.TooManyRedirects,
requests.Timeout) as ex:
except (
requests.RequestException,
requests.ConnectionError,
requests.HTTPError,
requests.TooManyRedirects,
requests.Timeout,
) as ex:
logger.error("Unable to connect to server (%s): %s", login_url, ex)
return False
@ -155,9 +160,7 @@ def get_runners(runner_name):
answers = socket.getaddrinfo(host, 443)
(_family, _type, _proto, _canonname, _sockaddr) = answers[0]
headers = OrderedDict({
'Host': host
})
headers = OrderedDict({"Host": host})
session = requests.Session()
session.headers = headers
response = session.get(api_url, headers=headers)
@ -467,7 +470,7 @@ def parse_installer_url(url):
"action": action,
"service": service,
"appid": appid,
"launch_config_name": launch_config_name
"launch_config_name": launch_config_name,
}

View File

@ -88,12 +88,16 @@ class MonitoredCommand:
def get_wrapper_command(self):
"""Return launch arguments for the wrapper script"""
wrapper_command = [
WRAPPER_SCRIPT,
self._title,
str(len(self.include_processes)),
str(len(self.exclude_processes)),
] + self.include_processes + self.exclude_processes
wrapper_command = (
[
WRAPPER_SCRIPT,
self._title,
str(len(self.include_processes)),
str(len(self.exclude_processes)),
]
+ self.include_processes
+ self.exclude_processes
)
if not self.terminal:
return wrapper_command + self.command
@ -122,7 +126,7 @@ class MonitoredCommand:
# not clear why this needs to be added, the path is already added in
# the wrappper script.
env['PYTHONPATH'] = ':'.join(sys.path)
env["PYTHONPATH"] = ":".join(sys.path)
# Drop bad values of environment keys, those will confuse the Python
# interpreter.
env["LUTRIS_GAME_UUID"] = str(uuid.uuid4())
@ -150,7 +154,7 @@ class MonitoredCommand:
"""Run the thread."""
if os.environ.get("LUTRIS_DEBUG_ENV") == "1":
for key, value in self.env.items():
logger.debug("%s=\"%s\"", key, value)
logger.debug('%s="%s"', key, value)
wrapper_command = self.get_wrapper_command()
env = self.get_child_environment()
self.game_process = self.execute_process(wrapper_command, env)
@ -201,11 +205,11 @@ class MonitoredCommand:
"""Get the return code from the file written by the wrapper"""
return_code_path = "/tmp/lutris-%s" % self.env["LUTRIS_GAME_UUID"]
if os.path.exists(return_code_path):
with open(return_code_path, encoding='utf-8') as return_code_file:
with open(return_code_path, encoding="utf-8") as return_code_file:
return_code = return_code_file.read()
os.unlink(return_code_path)
else:
return_code = ''
return_code = ""
logger.warning("No file %s", return_code_path)
return return_code

View File

@ -6,10 +6,10 @@ from lutris.database import sql
def strip_category_name(name):
""""This strips the name given, and also removes extra internal whitespace."""
""" "This strips the name given, and also removes extra internal whitespace."""
name = (name or "").strip()
if not is_reserved_category(name):
name = re.sub(' +', ' ', name) # Remove excessive whitespaces
name = re.sub(" +", " ", name) # Remove excessive whitespaces
return name
@ -21,7 +21,10 @@ def is_reserved_category(name):
def get_categories():
"""Get the list of every category in database."""
return sql.db_select(settings.DB_PATH, "categories", )
return sql.db_select(
settings.DB_PATH,
"categories",
)
def get_category(name):
@ -63,10 +66,7 @@ def get_game_ids_for_categories(included_category_names=None, excluded_category_
if filters:
query += " WHERE %s" % " AND ".join(filters)
return [
game["id"]
for game in sql.db_query(settings.DB_PATH, query, tuple(parameters))
]
return [game["id"] for game in sql.db_query(settings.DB_PATH, query, tuple(parameters))]
def get_categories_in_game(game_id):
@ -77,10 +77,7 @@ def get_categories_in_game(game_id):
"JOIN games ON games.id = games_categories.game_id "
"WHERE games.id=?"
)
return [
category["name"]
for category in sql.db_query(settings.DB_PATH, query, (game_id,))
]
return [category["name"] for category in sql.db_query(settings.DB_PATH, query, (game_id,))]
def add_category(category_name):
@ -110,9 +107,9 @@ def remove_unused_categories():
empty_categories = sql.db_query(settings.DB_PATH, query)
for category in empty_categories:
if category['name'] == 'favorite':
if category["name"] == "favorite":
continue
query = "DELETE FROM categories WHERE categories.id=?"
with sql.db_cursor(settings.DB_PATH) as cursor:
sql.cursor_execute(cursor, query, (category['id'],))
sql.cursor_execute(cursor, query, (category["id"],))

View File

@ -11,36 +11,26 @@ _SERVICE_CACHE = {}
_SERVICE_CACHE_ACCESSED = False # Keep time of last access to have a self degrading cache
def get_games(
searches=None,
filters=None,
excludes=None,
sorts=None
):
def get_games(searches=None, filters=None, excludes=None, sorts=None):
return sql.filtered_query(
settings.DB_PATH,
"games",
searches=searches,
filters=filters,
excludes=excludes,
sorts=sorts
settings.DB_PATH, "games", searches=searches, filters=filters, excludes=excludes, sorts=sorts
)
def get_games_where(**conditions):
"""
Query games table based on conditions
Query games table based on conditions
Args:
conditions (dict): named arguments with each field matches its desired value.
Special values for field names can be used:
<field>__lessthan will return rows where `field` is less than the value
<field>__isnull will return rows where `field` is NULL if the value is True
<field>__not will invert the condition using `!=` instead of `=`
<field>__in will match rows for every value of `value`, which should be an iterable
Args:
conditions (dict): named arguments with each field matches its desired value.
Special values for field names can be used:
<field>__lessthan will return rows where `field` is less than the value
<field>__isnull will return rows where `field` is NULL if the value is True
<field>__not will invert the condition using `!=` instead of `=`
<field>__in will match rows for every value of `value`, which should be an iterable
Returns:
list: Rows matching the query
Returns:
list: Rows matching the query
"""
query = "select * from games"
@ -86,7 +76,7 @@ def get_games_by_ids(game_ids):
return list(
chain.from_iterable(
[
get_games_where(id__in=list(game_ids)[page * size:page * size + size])
get_games_where(id__in=list(game_ids)[page * size : page * size + size])
for page in range(math.ceil(len(game_ids) / size))
]
)
@ -156,13 +146,13 @@ def add_game(**game_data):
def add_games_bulk(games):
"""
Add a list of games to the database.
The dicts must have an identical set of keys.
Add a list of games to the database.
The dicts must have an identical set of keys.
Args:
games (list): list of games in dict format
Returns:
list: List of inserted game ids
Args:
games (list): list of games in dict format
Returns:
list: List of inserted game ids
"""
return [sql.db_insert(settings.DB_PATH, "games", game) for game in games]
@ -231,8 +221,7 @@ def get_used_platforms():
"""Return a list of platforms currently in use"""
with sql.db_cursor(settings.DB_PATH) as cursor:
query = (
"select distinct platform from games "
"where platform is not null and platform is not '' order by platform"
"select distinct platform from games " "where platform is not null and platform is not '' order by platform"
)
rows = cursor.execute(query)
results = rows.fetchall()

View File

@ -4,142 +4,47 @@ from lutris.util.log import logger
DATABASE = {
"games": [
{
"name": "id",
"type": "INTEGER",
"indexed": True
},
{
"name": "name",
"type": "TEXT"
},
{"name": "id", "type": "INTEGER", "indexed": True},
{"name": "name", "type": "TEXT"},
{
"name": "sortname",
"type": "TEXT",
},
{
"name": "slug",
"type": "TEXT"
},
{
"name": "installer_slug",
"type": "TEXT"
},
{
"name": "parent_slug",
"type": "TEXT"
},
{
"name": "platform",
"type": "TEXT"
},
{
"name": "runner",
"type": "TEXT"
},
{
"name": "executable",
"type": "TEXT"
},
{
"name": "directory",
"type": "TEXT"
},
{
"name": "updated",
"type": "DATETIME"
},
{
"name": "lastplayed",
"type": "INTEGER"
},
{
"name": "installed",
"type": "INTEGER"
},
{
"name": "installed_at",
"type": "INTEGER"
},
{
"name": "year",
"type": "INTEGER"
},
{
"name": "configpath",
"type": "TEXT"
},
{
"name": "has_custom_banner",
"type": "INTEGER"
},
{
"name": "has_custom_icon",
"type": "INTEGER"
},
{
"name": "has_custom_coverart_big",
"type": "INTEGER"
},
{
"name": "playtime",
"type": "REAL"
},
{
"name": "service",
"type": "TEXT"
},
{
"name": "service_id",
"type": "TEXT"
},
{"name": "slug", "type": "TEXT"},
{"name": "installer_slug", "type": "TEXT"},
{"name": "parent_slug", "type": "TEXT"},
{"name": "platform", "type": "TEXT"},
{"name": "runner", "type": "TEXT"},
{"name": "executable", "type": "TEXT"},
{"name": "directory", "type": "TEXT"},
{"name": "updated", "type": "DATETIME"},
{"name": "lastplayed", "type": "INTEGER"},
{"name": "installed", "type": "INTEGER"},
{"name": "installed_at", "type": "INTEGER"},
{"name": "year", "type": "INTEGER"},
{"name": "configpath", "type": "TEXT"},
{"name": "has_custom_banner", "type": "INTEGER"},
{"name": "has_custom_icon", "type": "INTEGER"},
{"name": "has_custom_coverart_big", "type": "INTEGER"},
{"name": "playtime", "type": "REAL"},
{"name": "service", "type": "TEXT"},
{"name": "service_id", "type": "TEXT"},
{
"name": "discord_id",
"type": "TEXT",
},
],
"service_games": [
{
"name": "id",
"type": "INTEGER",
"indexed": True
},
{
"name": "service",
"type": "TEXT"
},
{
"name": "appid",
"type": "TEXT"
},
{
"name": "name",
"type": "TEXT"
},
{
"name": "slug",
"type": "TEXT"
},
{
"name": "icon",
"type": "TEXT"
},
{
"name": "logo",
"type": "TEXT"
},
{
"name": "url",
"type": "TEXT"
},
{
"name": "details",
"type": "TEXT"
},
{
"name": "lutris_slug",
"type": "TEXT"
},
{"name": "id", "type": "INTEGER", "indexed": True},
{"name": "service", "type": "TEXT"},
{"name": "appid", "type": "TEXT"},
{"name": "name", "type": "TEXT"},
{"name": "slug", "type": "TEXT"},
{"name": "icon", "type": "TEXT"},
{"name": "logo", "type": "TEXT"},
{"name": "url", "type": "TEXT"},
{"name": "details", "type": "TEXT"},
{"name": "lutris_slug", "type": "TEXT"},
],
"sources": [
{"name": "id", "type": "INTEGER", "indexed": True},
@ -152,7 +57,7 @@ DATABASE = {
"games_categories": [
{"name": "game_id", "type": "INTEGER", "indexed": False},
{"name": "category_id", "type": "INTEGER", "indexed": False},
]
],
}

View File

@ -4,22 +4,10 @@ from lutris.util.log import logger
class ServiceGameCollection:
@classmethod
def get_service_games(
cls,
searches=None,
filters=None,
excludes=None,
sorts=None
):
def get_service_games(cls, searches=None, filters=None, excludes=None, sorts=None):
return sql.filtered_query(
settings.DB_PATH,
"service_games",
searches=searches,
filters=filters,
excludes=excludes,
sorts=sorts
settings.DB_PATH, "service_games", searches=searches, filters=filters, excludes=excludes, sorts=sorts
)
@classmethod

View File

@ -8,7 +8,6 @@ DB_LOCK = threading.RLock()
class db_cursor(object):
def __init__(self, db_path):
self.db_path = db_path
self.db_conn = None
@ -53,7 +52,7 @@ def db_insert(db_path, table, fields):
def db_update(db_path, table, updated_fields, conditions):
"""Update `table` with the values given in the dict `values` on the
condition given with the `row` tuple.
condition given with the `row` tuple.
"""
columns = "=?, ".join(list(updated_fields.keys())) + "=?"
field_values = tuple(updated_fields.values())
@ -130,14 +129,7 @@ def add_field(db_path, tablename, field):
cursor.execute(query)
def filtered_query(
db_path,
table,
searches=None,
filters=None,
excludes=None,
sorts=None
):
def filtered_query(db_path, table, searches=None, filters=None, excludes=None, sorts=None):
query = "select * from %s" % table
params = []
sql_filters = []
@ -155,9 +147,7 @@ def filtered_query(
if sql_filters:
query += " WHERE " + " AND ".join(sql_filters)
if sorts:
query += " ORDER BY %s" % ", ".join(
["%s %s" % (sort[0], sort[1]) for sort in sorts]
)
query += " ORDER BY %s" % ", ".join(["%s %s" % (sort[0], sort[1]) for sort in sorts])
else:
query += " ORDER BY slug ASC"
return db_query(db_path, query, tuple(params))

View File

@ -45,7 +45,7 @@ def watch_game_errors(game_stop_result, game=None):
_error_handlers: Dict[Type[BaseException], Callable[[BaseException, Gtk.Window], Any]] = {}
TError = TypeVar('TError', bound=BaseException)
TError = TypeVar("TError", bound=BaseException)
def register_error_handler(error_class: Type[TError], handler: Callable[[TError, Gtk.Window], Any]) -> None:
@ -95,10 +95,9 @@ def _get_error_parent(error_objects: Iterable) -> Gtk.Window:
return application.window if application else None
def _create_error_wrapper(handler: Callable, handler_name: str,
error_result: Any,
error_method_name: str,
connected_object: Any = None):
def _create_error_wrapper(
handler: Callable, handler_name: str, error_result: Any, error_method_name: str, connected_object: Any = None
):
"""Wraps a handler function in an error handler that will log and then report
any exceptions, then return the 'error_result'."""
@ -143,28 +142,40 @@ def init_exception_backstops():
"""
def _error_handling_connect(self: Gtk.Widget, signal_spec: str, handler, *args, **kwargs):
error_wrapper = _create_error_wrapper(handler, f"signal '{signal_spec}'",
error_result=None,
error_method_name="on_signal_error",
connected_object=self)
error_wrapper = _create_error_wrapper(
handler,
f"signal '{signal_spec}'",
error_result=None,
error_method_name="on_signal_error",
connected_object=self,
)
return _original_connect(self, signal_spec, error_wrapper, *args, **kwargs)
def _error_handling_add_emission_hook(emitting_type, signal_spec, handler, *args, **kwargs):
error_wrapper = _create_error_wrapper(handler, f"emission hook '{emitting_type}.{signal_spec}'",
error_result=True, # stay attached
error_method_name="on_emission_hook_error")
error_wrapper = _create_error_wrapper(
handler,
f"emission hook '{emitting_type}.{signal_spec}'",
error_result=True, # stay attached
error_method_name="on_emission_hook_error",
)
return _original_add_emission_hook(emitting_type, signal_spec, error_wrapper, *args, **kwargs)
def _error_handling_idle_add(handler, *args, **kwargs):
error_wrapper = _create_error_wrapper(handler, "idle function",
error_result=False, # stop calling idle func
error_method_name="on_idle_error")
error_wrapper = _create_error_wrapper(
handler,
"idle function",
error_result=False, # stop calling idle func
error_method_name="on_idle_error",
)
return _original_idle_add(error_wrapper, *args, **kwargs)
def _error_handling_timeout_add(interval, handler, *args, **kwargs):
error_wrapper = _create_error_wrapper(handler, "timeout function",
error_result=False, # stop calling timeout fund
error_method_name="on_timeout_error")
error_wrapper = _create_error_wrapper(
handler,
"timeout function",
error_result=False, # stop calling timeout fund
error_method_name="on_timeout_error",
)
return _original_timeout_add(interval, error_wrapper, *args, **kwargs)
def _handle_keyerror(error: KeyError, parent: Gtk.Window) -> None:

View File

@ -48,11 +48,8 @@ class UnavailableGameError(LutrisError):
class UnavailableLibrariesError(MisconfigurationError):
def __init__(self, libraries, arch=None):
message = _(
"The following {arch} libraries are required but are not installed on your system:\n{libs}"
).format(
arch=arch if arch else "",
libs=", ".join(libraries)
message = _("The following {arch} libraries are required but are not installed on your system:\n{libs}").format(
arch=arch if arch else "", libs=", ".join(libraries)
)
super().__init__(message)
self.libraries = libraries
@ -114,7 +111,6 @@ class FsyncUnsupportedError(Exception):
def __init__(self, *args, message=None, **kwarg):
if not message:
message = _("Your kernel is not patched for fsync."
" Please get a patched kernel to use fsync.")
message = _("Your kernel is not patched for fsync." " Please get a patched kernel to use fsync.")
super().__init__(message, *args, **kwarg)

View File

@ -42,7 +42,7 @@ HEARTBEAT_DELAY = 2000
class Game(GObject.Object):
"""This class takes cares of loading the configuration for a game
and running it.
and running it.
"""
now_playing_path = os.path.join(settings.CACHE_DIR, "now-playing.txt")
@ -92,7 +92,7 @@ class Game(GObject.Object):
self.service = game_data.get("service")
self.appid = game_data.get("service_id")
self.playtime = float(game_data.get("playtime") or 0.0)
self.discord_id = game_data.get('discord_id') # Discord App ID for RPC
self.discord_id = game_data.get("discord_id") # Discord App ID for RPC
self._config = None
self._runner = None
@ -193,7 +193,7 @@ class Game(GObject.Object):
if category is None:
category_id = categories_db.add_category(category_name)
else:
category_id = category['id']
category_id = category["id"]
categories_db.add_game_to_category(self.id, category_id)
if not no_signal:
@ -207,7 +207,7 @@ class Game(GObject.Object):
category = categories_db.get_category(category_name)
if category is None:
return
category_id = category['id']
category_id = category["id"]
categories_db.remove_category_from_game(self.id, category_id)
if not no_signal:
@ -370,6 +370,7 @@ class Game(GObject.Object):
game_id = None
if game_id:
def on_error(_game, error):
logger.exception("Unable to install game: %s", error)
return True
@ -390,8 +391,9 @@ class Game(GObject.Object):
raise RuntimeError(_("No updates found"))
application = Gio.Application.get_default()
application.show_installer_window(installers, service, self.appid,
installation_kind=InstallationKind.UPDATE)
application.show_installer_window(
installers, service, self.appid, installation_kind=InstallationKind.UPDATE
)
jobs.AsyncCall(service.get_update_installers, on_installers_ready, db_game)
return True
@ -483,7 +485,7 @@ class Game(GObject.Object):
"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
"has_custom_coverart_big": "coverart_big" in self.custom_images,
}
self._id = str(games_db.add_or_update(**game_data))
if not no_signal:
@ -498,12 +500,7 @@ class Game(GObject.Object):
def save_lastplayed(self):
"""Save only the lastplayed field- do not restore any other values the user may have changed
in another window."""
games_db.update_existing(
id=self.id,
slug=self.slug,
lastplayed=self.lastplayed,
playtime=self.playtime
)
games_db.update_existing(id=self.id, slug=self.slug, lastplayed=self.lastplayed, playtime=self.playtime)
self.emit("game-updated")
def check_launchable(self):
@ -788,7 +785,7 @@ class Game(GObject.Object):
self.emit("game-started")
# Game is running, let's update discord status
if settings.read_setting('discord_rpc') == 'True' and self.discord_id:
if settings.read_setting("discord_rpc") == "True" and self.discord_id:
try:
discord.client.update(self.discord_id)
except AssertionError:
@ -815,7 +812,7 @@ class Game(GObject.Object):
jobs.AsyncCall(force_stop_game, force_stop_game_cb)
def force_kill_delayed(self, death_watch_seconds=5, death_watch_interval_seconds=.5):
def force_kill_delayed(self, death_watch_seconds=5, death_watch_interval_seconds=0.5):
"""Forces termination of a running game, but only after a set time has elapsed;
Invokes stop_game() when the game is dead."""
@ -875,9 +872,7 @@ class Game(GObject.Object):
if game_folder in cmdline or "pressure-vessel" in cmdline:
folder_pids.add(pid)
uuid_pids = set(
pid for pid in new_pids
if Process(pid).environ.get("LUTRIS_GAME_UUID") == self.game_uuid)
uuid_pids = set(pid for pid in new_pids if Process(pid).environ.get("LUTRIS_GAME_UUID") == self.game_uuid)
return folder_pids & uuid_pids
@ -939,6 +934,7 @@ class Game(GObject.Object):
logger.info("Stopping %s", self)
if self.game_thread:
def stop_cb(_result, error):
if error:
self.signal_error(error)
@ -1000,7 +996,7 @@ class Game(GObject.Object):
setxkbmap.communicate()
# Clear Discord Client Status
if settings.read_setting('discord_rpc') == 'True' and self.discord_id:
if settings.read_setting("discord_rpc") == "True" and self.discord_id:
discord.client.clear()
self.process_return_codes()
@ -1040,22 +1036,24 @@ class Game(GObject.Object):
if not system.path_exists(old_location):
raise InvalidGameMoveError(
_("The location '%s' does not exist, perhaps the files have already been moved?") % old_location)
_("The location '%s' does not exist, perhaps the files have already been moved?") % old_location
)
if new_location.startswith(old_location):
raise InvalidGameMoveError(
_("Lutris can't move '%s' to a location inside of itself, '%s'.") % (old_location, new_location))
_("Lutris can't move '%s' to a location inside of itself, '%s'.") % (old_location, new_location)
)
self.directory = target_directory
self.save(no_signal=no_signal)
with open(self.config.game_config_path, encoding='utf-8') as config_file:
with open(self.config.game_config_path, encoding="utf-8") as config_file:
for line in config_file.readlines():
if target_directory in line:
new_config += line
else:
new_config += line.replace(old_location, target_directory)
with open(self.config.game_config_path, "w", encoding='utf-8') as config_file:
with open(self.config.game_config_path, "w", encoding="utf-8") as config_file:
config_file.write(new_config)
try:
@ -1063,7 +1061,9 @@ class Game(GObject.Object):
except OSError as ex:
logger.error(
"Failed to move %s to %s, you may have to move files manually (Exception: %s)",
old_location, new_location, ex
old_location,
new_location,
ex,
)
return target_directory
@ -1111,10 +1111,7 @@ def export_game(slug, dest_dir):
json.dump(db_game, config_file, indent=2)
archive_path = os.path.join(dest_dir, "%s.tar.xz" % slug)
command = ["tar", "cJf", archive_path, os.path.basename(game_path)]
system.execute(
command,
cwd=os.path.dirname(game_path)
)
system.execute(command, cwd=os.path.dirname(game_path))
logger.info("%s exported to %s", slug, archive_path)
@ -1139,10 +1136,10 @@ def import_game(file_path, dest_dir):
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:
with open(os.path.join(game_dir, game_config), "w", encoding="utf-8") as config_file:
config_file.write(config_data)
with open(os.path.join(game_dir, game_config), encoding="utf-8") as config_file:
lutris_config = json.load(config_file)

View File

@ -263,35 +263,14 @@ class SingleGameActions(GameActions):
"deletefavorite": game.is_favorite,
"install_more": not game.service and game.is_installed,
"execute-script": bool(
game.is_installed and game.has_runner
and game.runner.system_config.get("manual_command")
),
"desktop-shortcut": (
game.is_installed
and not xdgshortcuts.desktop_launcher_exists(game.slug, game.id)
),
"menu-shortcut": (
game.is_installed
and not xdgshortcuts.menu_launcher_exists(game.slug, game.id)
),
"steam-shortcut": (
game.is_installed
and not has_steam_shortcut
and not is_steam_game
),
"rm-desktop-shortcut": bool(
game.is_installed
and xdgshortcuts.desktop_launcher_exists(game.slug, game.id)
),
"rm-menu-shortcut": bool(
game.is_installed
and xdgshortcuts.menu_launcher_exists(game.slug, game.id)
),
"rm-steam-shortcut": bool(
game.is_installed
and has_steam_shortcut
and not is_steam_game
game.is_installed and game.has_runner and game.runner.system_config.get("manual_command")
),
"desktop-shortcut": (game.is_installed and not xdgshortcuts.desktop_launcher_exists(game.slug, game.id)),
"menu-shortcut": (game.is_installed and not xdgshortcuts.menu_launcher_exists(game.slug, game.id)),
"steam-shortcut": (game.is_installed and not has_steam_shortcut and not is_steam_game),
"rm-desktop-shortcut": bool(game.is_installed and xdgshortcuts.desktop_launcher_exists(game.slug, game.id)),
"rm-menu-shortcut": bool(game.is_installed and xdgshortcuts.menu_launcher_exists(game.slug, game.id)),
"rm-steam-shortcut": bool(game.is_installed and has_steam_shortcut and not is_steam_game),
"remove": self.is_game_removable,
"view": True,
"hide": game.is_installed and not game.is_hidden,
@ -323,11 +302,7 @@ class SingleGameActions(GameActions):
_buffer = game.log_buffer
if not _buffer:
logger.info("No log for game %s", game)
return LogWindow(
game=game,
buffer=_buffer,
application=self.application
)
return LogWindow(game=game, buffer=_buffer, application=self.application)
def on_edit_game_configuration(self, _widget):
"""Edit game preferences"""
@ -398,9 +373,10 @@ class SingleGameActions(GameActions):
"Do you wish to duplicate %s?\nThe configuration will be duplicated, "
"but the games files will <b>not be duplicated</b>.\n"
"Please enter the new name for the copy:"
) % gtk_safe(game.name),
)
% gtk_safe(game.name),
"title": _("Duplicate game?"),
"initial_value": game.name
"initial_value": game.name,
}
)
result = duplicate_game_dialog.run()
@ -469,11 +445,7 @@ class ServiceGameActions(GameActions):
def get_displayed_entries(self):
"""Return a dictionary of actions that should be shown for a game"""
return {
"install": self.is_installable,
"add": self.is_installable,
"view": True
}
return {"install": self.is_installable, "add": self.is_installable, "view": True}
def get_game_actions(games: List[Game], window: Gtk.Window, application=None) -> GameActions:

View File

@ -31,36 +31,36 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
"go-next-symbolic",
_("Import previously installed Lutris games"),
_("Scan a folder for games installed from a previous Lutris installation"),
"scan_folder"
"scan_folder",
),
(
"application-x-executable-symbolic",
"go-next-symbolic",
_("Install a Windows game from an executable"),
_("Launch a Windows executable (.exe) installer"),
"install_from_setup"
"install_from_setup",
),
(
"x-office-document-symbolic",
"go-next-symbolic",
_("Install from a local install script"),
_("Run a YAML install script"),
"install_from_script"
"install_from_script",
),
(
"application-x-firmware-symbolic",
"go-next-symbolic",
_("Import a ROM"),
_("Import a ROM that is known to Lutris"),
"import_rom"
"import_rom",
),
(
"list-add-symbolic",
"view-more-horizontal-symbolic",
_("Add locally installed game"),
_("Manually configure a game available locally"),
"add_local_game"
)
"add_local_game",
),
]
def __init__(self, **kwargs):
@ -122,20 +122,17 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
self.install_from_setup_game_name_entry = Gtk.Entry()
self.install_from_setup_game_slug_checkbox = Gtk.CheckButton(label="Identifier")
self.install_from_setup_game_slug_entry = Gtk.Entry(sensitive=False)
self.install_from_setup_game_slug_entry.connect("focus-out-event",
self.on_install_from_setup_game_slug_entry_focus_out)
self.install_from_setup_game_slug_entry.connect(
"focus-out-event", self.on_install_from_setup_game_slug_entry_focus_out
)
self.installer_presets = Gtk.ListStore(str, str)
self.install_preset_dropdown = Gtk.ComboBox.new_with_model(self.installer_presets)
self.installer_locale = Gtk.ListStore(str, str)
self.install_locale_dropdown = Gtk.ComboBox.new_with_model(self.installer_locale)
self.install_script_file_chooser = FileChooserEntry(
title=_("Select script"), action=Gtk.FileChooserAction.OPEN
)
self.install_script_file_chooser = FileChooserEntry(title=_("Select script"), action=Gtk.FileChooserAction.OPEN)
self.import_rom_file_chooser = FileChooserEntry(
title=_("Select ROM file"), action=Gtk.FileChooserAction.OPEN
)
self.import_rom_file_chooser = FileChooserEntry(title=_("Select ROM file"), action=Gtk.FileChooserAction.OPEN)
self.stack.add_named_factory("initial", self.create_initial_page)
self.stack.add_named_factory("search_installers", self.create_search_installers_page)
@ -265,7 +262,7 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
self.search_spinner.stop()
self.search_spinner.hide()
total_count = api_games.get("count", 0)
count = len(api_games.get('results', []))
count = len(api_games.get("results", []))
if not count:
self.search_result_label.set_markup(_("No results"))
@ -277,11 +274,11 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
row.destroy()
for game in api_games.get("results", []):
platforms = ",".join(gtk_safe(platform["name"]) for platform in game["platforms"])
year = game['year'] or ""
year = game["year"] or ""
if platforms and year:
platforms = ", " + platforms
row = self._get_listbox_row("", gtk_safe(game['name']), f"{year}{platforms}")
row = self._get_listbox_row("", gtk_safe(game["name"]), f"{year}{platforms}")
row.api_info = game
self.search_listbox.add(row)
self.search_result_label.show()
@ -441,7 +438,7 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
self.install_preset_dropdown.pack_start(renderer_text, True)
self.install_preset_dropdown.add_attribute(renderer_text, "text", 1)
self.install_preset_dropdown.set_id_column(0)
self.install_preset_dropdown.set_active_id('win10')
self.install_preset_dropdown.set_active_id("win10")
grid.attach(self.install_preset_dropdown, 1, 3, 1, 1)
self.install_preset_dropdown.set_halign(Gtk.Align.START)
@ -514,21 +511,11 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
"game_slug": game_slug,
"runner": "wine",
"script": {
"game": {
"exe": AUTO_WIN32_EXE, "prefix": "$GAMEDIR"
},
"files": [
{"setupfile": "N/A:%s" % _("Select the setup file")}
],
"installer": [
{"task": {"name": "wineexec", "executable": "setupfile", "arch": arch}}
],
"system": {
"env": {
"LC_ALL": locale_selected
}
}
}
"game": {"exe": AUTO_WIN32_EXE, "prefix": "$GAMEDIR"},
"files": [{"setupfile": "N/A:%s" % _("Select the setup file")}],
"installer": [{"task": {"name": "wineexec", "executable": "setupfile", "arch": arch}}],
"system": {"env": {"LC_ALL": locale_selected}},
},
}
if win_ver_task:
installer["script"]["installer"].insert(0, win_ver_task)
@ -694,12 +681,7 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
return label
def _get_explanation_label(self, markup):
label = Gtk.Label(
visible=True,
margin_right=12,
margin_left=12,
margin_top=12,
margin_bottom=12)
label = Gtk.Label(visible=True, margin_right=12, margin_left=12, margin_top=12, margin_bottom=12)
label.set_markup(markup)
label.set_line_wrap(True)
return label
@ -709,13 +691,7 @@ class AddGamesWindow(ModelessDialog): # pylint: disable=too-many-public-methods
row.set_selectable(False)
row.set_activatable(True)
box = Gtk.Box(
spacing=12,
margin_right=12,
margin_left=12,
margin_top=12,
margin_bottom=12,
visible=True)
box = Gtk.Box(spacing=12, margin_right=12, margin_left=12, margin_top=12, margin_bottom=12, visible=True)
if left_icon_name:
icon = self._get_icon(left_icon_name)

View File

@ -67,7 +67,6 @@ LUTRIS_EXPERIMENTAL_FEATURES_ENABLED = os.environ.get("LUTRIS_EXPERIMENTAL_FEATU
class Application(Gtk.Application):
def __init__(self):
super().__init__(
application_id="net.lutris.Lutris",
@ -113,13 +112,15 @@ class Application(Gtk.Application):
def add_arguments(self):
if hasattr(self, "set_option_context_summary"):
self.set_option_context_summary(_(
"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 "
"(displayed when running lutris --list-games) and add "
"lutris:rungameid/numerical-id.\n"
"To install a game, add lutris:install/game-identifier."
))
self.set_option_context_summary(
_(
"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 "
"(displayed when running lutris --list-games) and add "
"lutris:rungameid/numerical-id.\n"
"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)")
self.add_main_option(
@ -220,7 +221,7 @@ class Application(Gtk.Application):
)
self.add_main_option(
"list-all-service-games",
ord('a'),
ord("a"),
GLib.OptionFlags.NONE,
GLib.OptionArg.NONE,
_("List all games for all services in database"),
@ -391,11 +392,7 @@ class Application(Gtk.Application):
def show_installer_window(self, installers, service=None, appid=None, installation_kind=InstallationKind.INSTALL):
self.show_window(
InstallerWindow,
installers=installers,
service=service,
appid=appid,
installation_kind=installation_kind
InstallerWindow, installers=installers, service=service, appid=appid, installation_kind=installation_kind
)
def show_lutris_installer_window(self, game_slug):
@ -486,9 +483,7 @@ class Application(Gtk.Application):
# List game
if options.contains("list-games"):
game_list = games_db.get_games(filters=(
{"installed": 1} if options.contains("installed") else None)
)
game_list = games_db.get_games(filters=({"installed": 1} if options.contains("installed") else None))
if options.contains("json"):
self.print_game_json(command_line, game_list)
else:
@ -501,9 +496,9 @@ class Application(Gtk.Application):
game_list = games_db.get_games(filters={"installed": 1, "service": service})
service_game_list = ServiceGameCollection.get_for_service(service)
for game in service_game_list:
game['installed'] = any(('service_id', game['appid']) in item.items() for item in game_list)
game["installed"] = any(("service_id", game["appid"]) in item.items() for item in game_list)
if options.contains("installed"):
service_game_list = [d for d in service_game_list if d['installed']]
service_game_list = [d for d in service_game_list if d["installed"]]
if options.contains("json"):
self.print_service_game_json(command_line, service_game_list)
else:
@ -515,10 +510,13 @@ class Application(Gtk.Application):
game_list = games_db.get_games(filters={"installed": 1})
service_game_list = ServiceGameCollection.get_service_games()
for game in service_game_list:
game['installed'] = any(('service_id', game['appid']) in item.items() for item in game_list if
item['service'] == game['service'])
game["installed"] = any(
("service_id", game["appid"]) in item.items()
for item in game_list
if item["service"] == game["service"]
)
if options.contains("installed"):
service_game_list = [d for d in service_game_list if d['installed']]
service_game_list = [d for d in service_game_list if d["installed"]]
if options.contains("json"):
self.print_service_game_json(command_line, service_game_list)
else:
@ -574,6 +572,7 @@ class Application(Gtk.Application):
return 0
if LUTRIS_EXPERIMENTAL_FEATURES_ENABLED:
def get_game_match(slug):
# First look for an exact match
games = games_db.get_games_by_slug(slug)
@ -581,8 +580,10 @@ class Application(Gtk.Application):
# Then look for matches
games = games_db.get_games(searches={"slug": slug})
if len(games) > 1:
self._print(command_line, "Multiple games matching %s: %s" %
(slug, ",".join(game["slug"] for game in games)))
self._print(
command_line,
"Multiple games matching %s: %s" % (slug, ",".join(game["slug"] for game in games)),
)
return
if not games:
self._print(command_line, "No matching game for %s" % slug)
@ -650,9 +651,10 @@ class Application(Gtk.Application):
except (KeyError, IndexError):
file_name = os.path.basename(installer_file)
file_path = os.path.join(tempfile.gettempdir(), file_name)
self._print(command_line, _("download {url} to {file} started").format(
url=installer_file, file=file_path))
with open(file_path, 'wb') as dest_file:
self._print(
command_line, _("download {url} to {file} started").format(url=installer_file, file=file_path)
)
with open(file_path, "wb") as dest_file:
dest_file.write(request.content)
installer_file = file_path
action = "install"
@ -677,8 +679,9 @@ class Application(Gtk.Application):
elif action == "install":
# Installers can use game or installer slugs
self.quit_on_game_exit = True
db_game = games_db.get_game_by_field(game_slug, "slug") \
or games_db.get_game_by_field(game_slug, "installer_slug")
db_game = games_db.get_game_by_field(game_slug, "slug") or games_db.get_game_by_field(
game_slug, "installer_slug"
)
else:
# Dazed and confused, try anything that might works
db_game = (
@ -838,7 +841,7 @@ class Application(Gtk.Application):
"action": None,
"service": None,
"appid": None,
"launch_config_name": None
"launch_config_name": None,
}
if url:
@ -872,15 +875,10 @@ class Application(Gtk.Application):
"platform": game["platform"] or None,
"year": game["year"] or None,
"directory": game["directory"] or None,
"playtime": (
str(timedelta(hours=game["playtime"]))
if game["playtime"] else None
),
"lastplayed": (
str(datetime.fromtimestamp(game["lastplayed"]))
if game["lastplayed"] else None
)
} for game in game_list
"playtime": (str(timedelta(hours=game["playtime"])) if game["playtime"] else None),
"lastplayed": (str(datetime.fromtimestamp(game["lastplayed"])) if game["lastplayed"] else None),
}
for game in game_list
]
self._print(command_line, json.dumps(games, indent=2))
@ -906,8 +904,9 @@ class Application(Gtk.Application):
"service": game["service"],
"appid": game["appid"],
"installed": game["installed"],
"details": game["details"]
} for game in game_list
"details": game["details"],
}
for game in game_list
]
self._print(command_line, json.dumps(games, indent=2))
@ -1000,11 +999,13 @@ class Application(Gtk.Application):
system.remove_folder(runner_path)
print(f"Wine version '{version}' has been removed.")
else:
print(f"""
print(
f"""
Specified version of Wine is not installed: {version}.
Please check if the Wine Runner and specified version are installed (for that use --list-wine-versions).
Also, check that the version specified is in the correct format.
""")
"""
)
def install_cli(self, runner_name):
"""

View File

@ -27,18 +27,14 @@ class AccountsBox(BaseConfigBox):
self.add(self.get_section_label(_("Steam accounts")))
self.add(
self.get_description_label(
_(
"Select which Steam account is used for Lutris integration and creating Steam shortcuts."
)
_("Select which Steam account is used for Lutris integration and creating Steam shortcuts.")
)
)
self.frame = Gtk.Frame(visible=True, shadow_type=Gtk.ShadowType.ETCHED_IN)
self.frame.get_style_context().add_class("info-frame")
self.pack_start(self.frame, False, False, 0)
self.accounts_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=6, visible=True
)
self.accounts_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, visible=True)
self.frame.add(self.accounts_box)
def space_widget(self, widget, top=16, bottom=16):
@ -79,9 +75,7 @@ class AccountsBox(BaseConfigBox):
box.add(self.get_user_box())
checkbutton = Gtk.CheckButton.new_with_label(
_("Keep your game library synced with Lutris.net")
)
checkbutton = Gtk.CheckButton.new_with_label(_("Keep your game library synced with Lutris.net"))
checkbutton.set_active(settings.read_bool_setting("library_sync_enabled"))
checkbutton.connect("toggled", self.on_sync_toggled)
checkbutton.show()
@ -110,9 +104,7 @@ class AccountsBox(BaseConfigBox):
for account in steam_users:
steamid64 = account["steamid64"]
name = account.get("PersonaName") or f"#{steamid64}"
radio_button = Gtk.RadioButton.new_with_label_from_widget(
main_radio_button, name
)
radio_button = Gtk.RadioButton.new_with_label_from_widget(main_radio_button, name)
self.space_widget(radio_button)
radio_button.show()
radio_button.set_active(active_steam_account == steamid64)
@ -151,10 +143,12 @@ class AccountsBox(BaseConfigBox):
def on_sync_toggled(self, checkbutton):
if not settings.read_setting("last_library_sync_at"):
sync_warn_dialog = QuestionDialog({
"title": _("Synchronize library?"),
"question": _("Enable library sync and run a full sync with lutris.net?")
})
sync_warn_dialog = QuestionDialog(
{
"title": _("Synchronize library?"),
"question": _("Enable library sync and run a full sync with lutris.net?"),
}
)
if sync_warn_dialog.result == Gtk.ResponseType.YES:
AsyncCall(sync_local_library, None)
settings.write_setting("library_sync_enabled", checkbutton.get_active())

View File

@ -40,28 +40,39 @@ class BaseConfigBox(VBox):
list_box.add(Gtk.ListBoxRow(child=item, visible=True, activatable=False))
return frame
def get_setting_box(self, setting_key: str, label: str,
default: bool = False,
warning_markup: str = None,
warning_condition: Callable[[bool], bool] = None,
extra_widget: Gtk.Widget = None) -> Gtk.Box:
def get_setting_box(
self,
setting_key: str,
label: str,
default: bool = False,
warning_markup: str = None,
warning_condition: Callable[[bool], bool] = None,
extra_widget: Gtk.Widget = None,
) -> Gtk.Box:
setting_value = settings.read_bool_setting(setting_key, default=default)
if not warning_markup and not extra_widget:
box = self._get_inner_settings_box(setting_key, setting_value, label)
else:
if warning_markup:
def update_warning(active):
visible = warning_condition(active) if bool(warning_condition) else active
warning_box.show_markup(warning_markup if visible else None)
warning_box = UnderslungMessageBox("dialog-warning", margin_left=0, margin_right=0, margin_bottom=0)
update_warning(setting_value)
inner_box = self._get_inner_settings_box(setting_key, setting_value, label, margin=0,
when_setting_changed=update_warning)
inner_box = self._get_inner_settings_box(
setting_key, setting_value, label, margin=0, when_setting_changed=update_warning
)
else:
warning_box = None
inner_box = self._get_inner_settings_box(setting_key, setting_value, label, margin=0, )
inner_box = self._get_inner_settings_box(
setting_key,
setting_value,
label,
margin=0,
)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, visible=True)
box.pack_start(inner_box, False, False, 0)
@ -76,10 +87,14 @@ class BaseConfigBox(VBox):
box.set_margin_right(12)
return box
def _get_inner_settings_box(self, setting_key: str, setting_value: bool,
label: str,
margin: int = 12,
when_setting_changed: Callable[[bool], None] = None):
def _get_inner_settings_box(
self,
setting_key: str,
setting_value: bool,
label: str,
margin: int = 12,
when_setting_changed: Callable[[bool], None] = None,
):
checkbox = Gtk.Switch(visible=True, valign=Gtk.Align.CENTER)
checkbox.set_active(setting_value)
checkbox.connect("state-set", self.on_setting_change, setting_key, when_setting_changed)
@ -91,19 +106,16 @@ class BaseConfigBox(VBox):
return self.get_listed_widget_box(label, checkbox, margin=margin)
def get_listed_widget_box(self, label: str, widget: Gtk.Widget, margin: int = 12) -> Gtk.Box:
box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL,
spacing=12, margin=margin,
visible=True
)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12, margin=margin, visible=True)
label = Gtk.Label(label, visible=True, wrap=True)
label.set_alignment(0, 0.5)
box.pack_start(label, True, True, 0)
box.pack_end(widget, False, False, 0)
return box
def on_setting_change(self, _widget, state: bool, setting_key: str,
when_setting_changed: Callable[[bool], None] = None) -> None:
def on_setting_change(
self, _widget, state: bool, setting_key: str, when_setting_changed: Callable[[bool], None] = None
) -> None:
"""Save a setting when an option is toggled"""
settings.write_setting(setting_key, state)
self.get_toplevel().emit("settings-changed", state, setting_key)

View File

@ -208,8 +208,7 @@ class ConfigBox(VBox):
if value != default and option_key not in self.raw_config:
helptext = helptext + "\n\n" if helptext else ""
helptext += _(
"<i>(Italic indicates that this option is "
"modified in a lower configuration level.)</i>"
"<i>(Italic indicates that this option is " "modified in a lower configuration level.)</i>"
)
if helptext:
self.wrapper.props.has_tooltip = True
@ -495,7 +494,7 @@ class ConfigBox(VBox):
warn_if_non_writable_parent=warn_if_non_writable_parent,
text=path,
default_path=chooser_default_path,
shell_quoting=shell_quoting
shell_quoting=shell_quoting,
)
# file_chooser.set_size_request(200, 30)
@ -538,7 +537,7 @@ class ConfigBox(VBox):
action=Gtk.FileChooserAction.SELECT_FOLDER,
warn_if_non_writable_parent=warn_if_non_writable_parent,
text=path,
default_path=chooser_default_path
default_path=chooser_default_path,
)
directory_chooser.connect("changed", self._on_chooser_file_set, option_name)
directory_chooser.set_valign(Gtk.Align.CENTER)
@ -763,10 +762,9 @@ class RunnerBox(ConfigBox):
self.options = self.runner.get_runner_options()
if lutris_config.level == "game":
self.generate_top_info_box(_(
"If modified, these options supersede the same options from "
"the base runner configuration."
))
self.generate_top_info_box(
_("If modified, these options supersede the same options from " "the base runner configuration.")
)
class SystemConfigBox(ConfigBox):
@ -784,24 +782,31 @@ class SystemConfigBox(ConfigBox):
self.options = sysoptions.system_options
if lutris_config.game_config_id and runner_slug:
self.generate_top_info_box(_(
"If modified, these options supersede the same options from "
"the base runner configuration, which themselves supersede "
"the global preferences."
))
self.generate_top_info_box(
_(
"If modified, these options supersede the same options from "
"the base runner configuration, which themselves supersede "
"the global preferences."
)
)
elif runner_slug:
self.generate_top_info_box(_(
"If modified, these options supersede the same options from "
"the global preferences."
))
self.generate_top_info_box(
_("If modified, these options supersede the same options from " "the global preferences.")
)
class UnderslungMessageBox(Gtk.Box):
"""A box to display a message with an icon inside the configuration dialog."""
def __init__(self, icon_name, margin_left=18, margin_right=18, margin_bottom=6):
super().__init__(spacing=6, visible=False, margin_left=margin_left, margin_right=margin_right,
margin_bottom=margin_bottom, no_show_all=True)
super().__init__(
spacing=6,
visible=False,
margin_left=margin_left,
margin_right=margin_right,
margin_bottom=margin_bottom,
no_show_all=True,
)
image = Gtk.Image(visible=True)
image.set_from_icon_name(icon_name, Gtk.IconSize.DND)

View File

@ -13,13 +13,13 @@ class EditCategoryGamesDialog(SavableModelessDialog):
"""Games assigned to category dialog."""
def __init__(self, parent, category):
super().__init__(_("Configure %s") % category['name'], parent=parent, border_width=10)
super().__init__(_("Configure %s") % category["name"], parent=parent, border_width=10)
self.category = category['name']
self.category_id = category['id']
self.available_games = [Game(x['id']) for x in games_db.get_games(sorts=[("installed", "DESC"),
("name", "COLLATE NOCASE ASC")
])]
self.category = category["name"]
self.category_id = category["id"]
self.available_games = [
Game(x["id"]) for x in games_db.get_games(sorts=[("installed", "DESC"), ("name", "COLLATE NOCASE ASC")])
]
self.category_games = [Game(x) for x in categories_db.get_game_ids_for_categories([self.category])]
self.grid = Gtk.Grid()
@ -64,8 +64,9 @@ class EditCategoryGamesDialog(SavableModelessDialog):
{
"title": _("Do you want to delete the category '%s'?") % self.category,
"question": _(
"This will permanently destroy the category, but the games themselves will not be deleted."),
"parent": self
"This will permanently destroy the category, but the games themselves will not be deleted."
),
"parent": self,
}
)
if dlg.result == Gtk.ResponseType.YES:
@ -84,7 +85,7 @@ class EditCategoryGamesDialog(SavableModelessDialog):
for game_checkbox in self.grid.get_children():
label = game_checkbox.get_label()
game_id = games_db.get_game_by_field(label, 'name')['id']
game_id = games_db.get_game_by_field(label, "name")["id"]
if label in category_games_names:
if not game_checkbox.get_active():
removed_games.append(game_id)
@ -107,9 +108,10 @@ class EditCategoryGamesDialog(SavableModelessDialog):
{
"title": _("Merge the category '%s' into '%s'?") % (self.category, new_name),
"question": _(
"If you rename this category, it will be combined with '%s'. "
"Do you want to merge them?") % new_name,
"parent": self
"If you rename this category, it will be combined with '%s'. " "Do you want to merge them?"
)
% new_name,
"parent": self,
}
)
if dlg.result != Gtk.ResponseType.YES:
@ -121,7 +123,7 @@ class EditCategoryGamesDialog(SavableModelessDialog):
for game_checkbox in self.grid.get_children():
if game_checkbox.get_active():
label = game_checkbox.get_label()
game_id = games_db.get_game_by_field(label, 'name')['id']
game_id = games_db.get_game_by_field(label, "name")["id"]
added_games.append(game_id)
for game_id in added_games:

View File

@ -34,10 +34,12 @@ class EditGameCategoriesDialog(SavableModelessDialog):
# frame.set_label("Categories") # probably too much redundancy
sw = Gtk.ScrolledWindow()
row = Gtk.VBox()
categories = sorted([c for c in categories_db.get_categories() if c['name'] != 'favorite'],
key=lambda c: locale.strxfrm(c['name']))
categories = sorted(
[c for c in categories_db.get_categories() if c["name"] != "favorite"],
key=lambda c: locale.strxfrm(c["name"]),
)
for category in categories:
label = category['name']
label = category["name"]
checkbutton_option = Gtk.CheckButton(label)
if label in self.game_categories:
checkbutton_option.set_active(True)

View File

@ -29,6 +29,7 @@ from lutris.util.strings import gtk_safe, parse_playtime, slugify
# pylint: disable=too-many-instance-attributes, no-member
class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
"""Base class for config dialogs"""
no_runner_label = _("Select a runner in the Game Info tab")
def __init__(self, title, config_level, parent=None):
@ -398,8 +399,9 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
self._set_image(image_type, image_button)
def on_move_clicked(self, _button):
new_location = DirectoryDialog("Select new location for the game",
default_path=self.game.directory, parent=self)
new_location = DirectoryDialog(
"Select new location for the game", 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, parent=self)
@ -431,39 +433,42 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
if self.game and self.runner_name:
self.game.runner_name = self.runner_name
self.game_box = self._build_options_tab(_("Game options"),
lambda: GameBox(self.config_level, self.lutris_config, self.game),
advanced=has_advanced(self.game),
searchable=is_searchable(self.game))
self.game_box = self._build_options_tab(
_("Game options"),
lambda: GameBox(self.config_level, self.lutris_config, self.game),
advanced=has_advanced(self.game),
searchable=is_searchable(self.game),
)
elif self.runner_name:
game = Game(None)
game.runner_name = self.runner_name
self.game_box = self._build_options_tab(_("Game options"),
lambda: GameBox(self.config_level, self.lutris_config, game),
advanced=has_advanced(game),
searchable=is_searchable(game))
self.game_box = self._build_options_tab(
_("Game options"),
lambda: GameBox(self.config_level, self.lutris_config, game),
advanced=has_advanced(game),
searchable=is_searchable(game),
)
else:
self._build_missing_options_tab(self.no_runner_label, _("Game options"))
def _build_runner_tab(self):
if self.runner_name:
self.runner_box = self._build_options_tab(_("Runner options"),
lambda: RunnerBox(self.config_level, self.lutris_config))
self.runner_box = self._build_options_tab(
_("Runner options"), lambda: RunnerBox(self.config_level, self.lutris_config)
)
else:
self._build_missing_options_tab(self.no_runner_label, _("Runner options"))
def _build_system_tab(self):
self.system_box = self._build_options_tab(_("System options"),
lambda: SystemConfigBox(self.config_level, self.lutris_config))
self.system_box = self._build_options_tab(
_("System options"), lambda: SystemConfigBox(self.config_level, self.lutris_config)
)
def _build_options_tab(self, notebook_label, box_factory, advanced=True, searchable=True):
if not self.lutris_config:
raise RuntimeError("Lutris config not loaded yet")
config_box = box_factory()
page_index = self._add_notebook_tab(
self.build_scrolled_window(config_box),
notebook_label
)
page_index = self._add_notebook_tab(self.build_scrolled_window(config_box), notebook_label)
if page_index == 0:
config_box.generate_widgets()
@ -490,10 +495,7 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
self.search_entry.show_all()
# Advanced settings toggle
switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
spacing=5,
no_show_all=True,
visible=True)
switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, no_show_all=True, visible=True)
switch_box.set_tooltip_text(_("Show advanced options"))
switch_label = Gtk.Label(_("Advanced"), no_show_all=True, visible=True)
@ -549,12 +551,12 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
dlg = QuestionDialog(
{
"parent": self,
"question":
_("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."),
"title":
_("Confirm runner change"),
"question": _(
"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."
),
"title": _("Confirm runner change"),
}
)
@ -791,8 +793,7 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
download_lutris_media(slug)
return image_type
service_media.trash_media(slug,
completion_function=on_trashed)
service_media.trash_media(slug, completion_function=on_trashed)
def refresh_image_cb(self, image_type, error):
return image_type

View File

@ -16,13 +16,9 @@ class InterfacePreferencesBox(BaseConfigBox):
"discord_rpc": _("Enable Discord Rich Presence for Available Games"),
}
settings_accelerators = {
"hide_badges_on_icons": "<Primary>p"
}
settings_accelerators = {"hide_badges_on_icons": "<Primary>p"}
settings_availability = {
"show_tray_icon": supports_status_icon
}
settings_availability = {"show_tray_icon": supports_status_icon}
def __init__(self, accelerators):
super().__init__()

View File

@ -49,55 +49,31 @@ class PreferencesDialog(GameDialogCommon):
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(InterfacePreferencesBox(self.accelerators)),
"prefs-stack"
)
self.stack.add_named(self.build_scrolled_window(InterfacePreferencesBox(self.accelerators)), "prefs-stack")
self.runners_box = RunnersBox()
self.page_generators["runners-stack"] = self.runners_box.populate_runners
self.stack.add_named(
self.build_scrolled_window(self.runners_box),
"runners-stack"
)
self.stack.add_named(self.build_scrolled_window(self.runners_box), "runners-stack")
services_box = ServicesBox()
self.page_generators["services-stack"] = services_box.populate_services
self.stack.add_named(
self.build_scrolled_window(services_box),
"services-stack"
)
self.stack.add_named(self.build_scrolled_window(services_box), "services-stack")
accounts_box = AccountsBox()
self.page_generators["accounts-stack"] = accounts_box.populate_steam_accounts
self.stack.add_named(
self.build_scrolled_window(accounts_box),
"accounts-stack"
)
self.stack.add_named(self.build_scrolled_window(accounts_box), "accounts-stack")
self.stack.add_named(
self.build_scrolled_window(UpdatesBox()),
"updates-stack"
)
self.stack.add_named(self.build_scrolled_window(UpdatesBox()), "updates-stack")
sysinfo_box = SystemBox()
self.page_generators["sysinfo-stack"] = sysinfo_box.populate
self.stack.add_named(
self.build_scrolled_window(sysinfo_box),
"sysinfo-stack"
)
self.stack.add_named(self.build_scrolled_window(sysinfo_box), "sysinfo-stack")
self.stack.add_named(
self.build_scrolled_window(StorageBox()),
"storage-stack"
)
self.stack.add_named(self.build_scrolled_window(StorageBox()), "storage-stack")
self.system_box = SystemConfigBox(self.config_level, self.lutris_config)
self.page_generators["system-stack"] = self.system_box.generate_widgets
self.stack.add_named(
self.build_scrolled_window(self.system_box),
"system-stack"
)
self.stack.add_named(self.build_scrolled_window(self.system_box), "system-stack")
def on_sidebar_activated(self, _listbox, row):
stack_id = row.get_children()[0].stack_id

View File

@ -28,9 +28,9 @@ class RunnerBox(Gtk.Box):
self.set_margin_right(12)
self.runner = runners.import_runner(runner_name)()
runner_icon = ScaledImage.get_runtime_icon_image(self.runner.name,
scale_factor=self.get_scale_factor(),
visible=True)
runner_icon = ScaledImage.get_runtime_icon_image(
self.runner.name, scale_factor=self.get_scale_factor(), visible=True
)
runner_icon.set_margin_right(12)
self.pack_start(runner_icon, False, True, 6)
@ -109,11 +109,11 @@ class RunnerBox(Gtk.Box):
{
"parent": self.get_toplevel(),
"title": _("Do you want to uninstall %s?") % self.runner.human_name,
"question": _("This will remove <b>%s</b> and all associated data." % self.runner.human_name)
"question": _("This will remove <b>%s</b> and all associated data." % self.runner.human_name),
}
)
if Gtk.ResponseType.YES == dialog.result:
def on_runner_uninstalled():
self.emit("runner-removed")

View File

@ -18,10 +18,11 @@ class RunnersBox(BaseConfigBox):
self.search_entry_placeholder_text = ""
self.add(self.get_section_label(_("Add, remove or configure runners")))
self.add(self.get_description_label(
_("Runners are programs such as emulators, engines or "
"translation layers capable of running games.")
))
self.add(
self.get_description_label(
_("Runners are programs such as emulators, engines or " "translation layers capable of running games.")
)
)
self.search_failed_label = Gtk.Label(_("No runners matched the search"))
self.pack_start(self.search_failed_label, False, False, 0)
self.runner_list_frame = Gtk.Frame(visible=True, shadow_type=Gtk.ShadowType.ETCHED_IN)

View File

@ -16,10 +16,11 @@ class ServicesBox(BaseConfigBox):
def __init__(self):
super().__init__()
self.add(self.get_section_label(_("Enable integrations with game sources")))
self.add(self.get_description_label(
_("Access your game libraries from various sources. "
"Changes require a restart to take effect.")
))
self.add(
self.get_description_label(
_("Access your game libraries from various sources. " "Changes require a restart to take effect.")
)
)
self.frame = Gtk.Frame(visible=True, shadow_type=Gtk.ShadowType.ETCHED_IN)
self.listbox = Gtk.ListBox(visible=True)
self.frame.add(self.listbox)
@ -44,9 +45,9 @@ class ServicesBox(BaseConfigBox):
)
service = SERVICES[service_key]
icon = ScaledImage.get_runtime_icon_image(service.icon, service.id,
scale_factor=self.get_scale_factor(),
visible=True)
icon = ScaledImage.get_runtime_icon_image(
service.icon, service.id, scale_factor=self.get_scale_factor(), visible=True
)
box.pack_start(icon, False, False, 0)
service_label_box = Gtk.VBox(visible=True)
label = Gtk.Label(visible=True)
@ -61,8 +62,7 @@ class ServicesBox(BaseConfigBox):
box.pack_start(service_label_box, True, True, 0)
checkbox = Gtk.Switch(visible=True)
if settings.read_setting(service_key,
section="services").lower() == "true":
if settings.read_setting(service_key, section="services").lower() == "true":
checkbox.set_active(True)
checkbox.connect("state-set", self._on_service_change, service_key)
alignment = Gtk.Alignment.new(0.5, 0.5, 0, 0)

View File

@ -26,7 +26,7 @@ class StorageBox(BaseConfigBox):
"setting": "game_path",
"default": os.path.expanduser("~/Games"),
"value": base_runner.default_path,
"help": _("The default folder where you install your games.")
"help": _("The default folder where you install your games."),
},
{
"name": "Installer cache",
@ -36,8 +36,8 @@ class StorageBox(BaseConfigBox):
"help": _(
"If provided, files downloaded during game installs will be kept there\n"
"\nOtherwise, all downloaded files are discarded."
)
}
),
},
]
for path_setting in path_settings:
widgets.append(self.get_directory_chooser(path_setting))
@ -55,7 +55,7 @@ class StorageBox(BaseConfigBox):
action=Gtk.FileChooserAction.SELECT_FOLDER,
warn_if_non_writable_parent=True,
text=path_setting["value"],
default_path=default_path
default_path=default_path,
)
directory_chooser.connect("changed", self.on_file_chooser_changed, path_setting)
wrapper.pack_start(label, False, False, 0)

View File

@ -28,28 +28,11 @@ class SystemBox(BaseConfigBox):
"name": _("Wine installed"),
"callable": is_installed_systemwide,
},
{
"name": _("Gamescope"),
"callable": system.can_find_executable,
"args": ("gamescope",)
},
{
"name": _("Mangohud"),
"callable": system.can_find_executable,
"args": ("mangohud",)
},
{
"name": _("Gamemode"),
"callable": linux.LINUX_SYSTEM.gamemode_available
},
{
"name": _("Steam"),
"callable": linux.LINUX_SYSTEM.has_steam
},
{
"name": _("In Flatpak"),
"callable": linux.LINUX_SYSTEM.is_flatpak
},
{"name": _("Gamescope"), "callable": system.can_find_executable, "args": ("gamescope",)},
{"name": _("Mangohud"), "callable": system.can_find_executable, "args": ("mangohud",)},
{"name": _("Gamemode"), "callable": linux.LINUX_SYSTEM.gamemode_available},
{"name": _("Steam"), "callable": linux.LINUX_SYSTEM.has_steam},
{"name": _("In Flatpak"), "callable": linux.LINUX_SYSTEM.is_flatpak},
]
def __init__(self):
@ -101,9 +84,7 @@ class SystemBox(BaseConfigBox):
grid.attach(header_label, 0, row, 2, 1)
else:
name, text = item
name_label = Gtk.Label(name + ":",
visible=True, xalign=0, yalign=0,
margin_right=30)
name_label = Gtk.Label(name + ":", visible=True, xalign=0, yalign=0, margin_right=30)
grid.attach(name_label, 0, row, 1, 1)
markup_label = Gtk.Label(visible=True, xalign=0, selectable=True)

View File

@ -18,7 +18,6 @@ from lutris.util.strings import gtk_safe
class UpdatesBox(BaseConfigBox):
def __init__(self):
super().__init__()
self.add(self.get_section_label(_("Wine update channel")))
@ -26,55 +25,53 @@ class UpdatesBox(BaseConfigBox):
update_channel_radio_buttons = self.get_update_channel_radio_buttons()
update_label_text, update_button_text = self.get_wine_update_texts()
self.update_runners_box = UpdateButtonBox(update_label_text,
update_button_text,
clicked=self.on_runners_update_clicked)
self.update_runners_box = UpdateButtonBox(
update_label_text, update_button_text, clicked=self.on_runners_update_clicked
)
self.pack_start(self._get_framed_options_list_box(update_channel_radio_buttons), False, False, 0)
self.pack_start(self._get_framed_options_list_box([self.update_runners_box]), False, False, 0)
self.add(self.get_section_label(_("Runtime updates")))
self.add(self.get_description_label(
_("Runtime components include DXVK, VKD3D and Winetricks.")
))
self.update_runtime_box = UpdateButtonBox("",
_("Check for Updates"),
clicked=self.on_runtime_update_clicked)
self.add(self.get_description_label(_("Runtime components include DXVK, VKD3D and Winetricks.")))
self.update_runtime_box = UpdateButtonBox("", _("Check for Updates"), clicked=self.on_runtime_update_clicked)
update_runtime_box = self.get_setting_box(
"auto_update_runtime",
_("Automatically Update the Lutris runtime"),
default=True,
extra_widget=self.update_runtime_box
extra_widget=self.update_runtime_box,
)
self.pack_start(self._get_framed_options_list_box([update_runtime_box]), False, False, 0)
self.add(self.get_section_label(_("Media updates")))
self.update_media_box = UpdateButtonBox("",
_("Download Missing Media"),
clicked=self.on_download_media_clicked)
self.update_media_box = UpdateButtonBox("", _("Download Missing Media"), clicked=self.on_download_media_clicked)
self.pack_start(self._get_framed_options_list_box([self.update_media_box]), False, False, 0)
def get_update_channel_radio_buttons(self):
update_channel = settings.read_setting("wine-update-channel", UPDATE_CHANNEL_STABLE)
markup = _("<b>Stable</b>:\n"
"Wine-GE updates are downloaded automatically and the latest version "
"is always used unless overridden in the settings.\n"
"\n"
"This allows us to keep track of regressions more efficiently and provide "
"fixes more reliably.")
stable_channel_radio_button = self._get_radio_button(markup,
active=update_channel == UPDATE_CHANNEL_STABLE,
group=None)
markup = _(
"<b>Stable</b>:\n"
"Wine-GE updates are downloaded automatically and the latest version "
"is always used unless overridden in the settings.\n"
"\n"
"This allows us to keep track of regressions more efficiently and provide "
"fixes more reliably."
)
stable_channel_radio_button = self._get_radio_button(
markup, active=update_channel == UPDATE_CHANNEL_STABLE, group=None
)
markup = _("<b>Self-maintained</b>:\n"
"Wine updates are no longer delivered automatically and you have full responsibility "
"of your Wine versions.\n"
"\n"
"Please note that this mode is <b>fully unsupported</b>. In order to submit issues on Github "
"or ask for help on Discord, switch back to the <b>Stable channel</b>.")
unsupported_channel_radio_button = self._get_radio_button(markup,
active=update_channel == UPDATE_CHANNEL_UNSUPPORTED,
group=stable_channel_radio_button)
markup = _(
"<b>Self-maintained</b>:\n"
"Wine updates are no longer delivered automatically and you have full responsibility "
"of your Wine versions.\n"
"\n"
"Please note that this mode is <b>fully unsupported</b>. In order to submit issues on Github "
"or ask for help on Discord, switch back to the <b>Stable channel</b>."
)
unsupported_channel_radio_button = self._get_radio_button(
markup, active=update_channel == UPDATE_CHANNEL_UNSUPPORTED, group=stable_channel_radio_button
)
# Safer to connect these after the active property has been initialized on all radio buttons
stable_channel_radio_button.connect("toggled", self.on_update_channel_toggled, UPDATE_CHANNEL_STABLE)
unsupported_channel_radio_button.connect("toggled", self.on_update_channel_toggled, UPDATE_CHANNEL_UNSUPPORTED)
@ -84,22 +81,22 @@ class UpdatesBox(BaseConfigBox):
wine_version_info = get_default_wine_runner_version_info()
wine_version = f"{wine_version_info['version']}-{wine_version_info['architecture']}"
if system.path_exists(os.path.join(settings.RUNNER_DIR, "wine", wine_version)):
update_label_text = _(
"Your wine version is up to date. Using: <b>%s</b>\n"
"<i>Last checked %s.</i>"
) % (wine_version_info['version'], get_runtime_versions_date_time_ago())
update_label_text = _("Your wine version is up to date. Using: <b>%s</b>\n" "<i>Last checked %s.</i>") % (
wine_version_info["version"],
get_runtime_versions_date_time_ago(),
)
update_button_text = _("Check again")
elif not system.path_exists(os.path.join(settings.RUNNER_DIR, "wine")):
update_label_text = _(
"You don't have any Wine version installed.\n"
"We recommend <b>%s</b>"
) % wine_version_info['version']
update_button_text = _("Download %s") % wine_version_info['version']
update_label_text = (
_("You don't have any Wine version installed.\n" "We recommend <b>%s</b>")
% wine_version_info["version"]
)
update_button_text = _("Download %s") % wine_version_info["version"]
else:
update_label_text = _(
"You don't have the recommended Wine version: <b>%s</b>"
) % wine_version_info['version']
update_button_text = _("Download %s") % wine_version_info['version']
update_label_text = (
_("You don't have the recommended Wine version: <b>%s</b>") % wine_version_info["version"]
)
update_button_text = _("Download %s") % wine_version_info["version"]
return update_label_text, update_button_text
def apply_wine_update_texts(self, completion_markup: str = "") -> None:
@ -165,18 +162,21 @@ class UpdatesBox(BaseConfigBox):
updater.update_runners = True
component_updaters = updater.create_component_updaters()
if component_updaters:
def on_complete(_result):
self.apply_wine_update_texts()
started = window.install_runtime_component_updates(component_updaters, updater,
completion_function=on_complete,
error_function=self.update_runners_box.show_error)
started = window.install_runtime_component_updates(
component_updaters,
updater,
completion_function=on_complete,
error_function=self.update_runners_box.show_error,
)
if started:
self.update_runners_box.show_running_markup(_("<i>Downloading...</i>"))
else:
NoticeDialog(_("Updates are already being downloaded and installed."),
parent=self.get_toplevel())
NoticeDialog(_("Updates are already being downloaded and installed."), parent=self.get_toplevel())
else:
self.apply_wine_update_texts(_("No updates are required at this time."))
@ -188,8 +188,7 @@ class UpdatesBox(BaseConfigBox):
self._trigger_updates(get_updater, self.update_runtime_box)
def _trigger_updates(self, updater_factory: Callable,
update_box: 'UpdateButtonBox') -> None:
def _trigger_updates(self, updater_factory: Callable, update_box: "UpdateButtonBox") -> None:
window = self._get_main_window()
if not window:
return
@ -197,35 +196,36 @@ class UpdatesBox(BaseConfigBox):
updater = updater_factory()
component_updaters = updater.create_component_updaters()
if component_updaters:
def on_complete(_result):
if len(component_updaters) == 1:
update_box.show_completion_markup("", _("1 component has been updated."))
else:
update_box.show_completion_markup("",
_("%d components have been updated.") % len(component_updaters))
update_box.show_completion_markup(
"", _("%d components have been updated.") % len(component_updaters)
)
started = window.install_runtime_component_updates(component_updaters, updater,
completion_function=on_complete,
error_function=update_box.show_error)
started = window.install_runtime_component_updates(
component_updaters, updater, completion_function=on_complete, error_function=update_box.show_error
)
if started:
update_box.show_running_markup(_("<i>Checking for updates...</i>"))
else:
NoticeDialog(_("Updates are already being downloaded and installed."),
parent=self.get_toplevel())
NoticeDialog(_("Updates are already being downloaded and installed."), parent=self.get_toplevel())
else:
update_box.show_completion_markup("", _("No updates are required at this time."))
def on_update_channel_toggled(self, checkbox, value):
"""Update setting when update channel is toggled
"""
"""Update setting when update channel is toggled"""
if not checkbox.get_active():
return
last_setting = settings.read_setting("wine-update-channel", UPDATE_CHANNEL_STABLE)
if last_setting == UPDATE_CHANNEL_STABLE and value == UPDATE_CHANNEL_UNSUPPORTED:
NoticeDialog(_(
"Without the Wine-GE updates enabled, we can no longer provide support on Github and Discord."
), parent=self.get_toplevel())
NoticeDialog(
_("Without the Wine-GE updates enabled, we can no longer provide support on Github and Discord."),
parent=self.get_toplevel(),
)
settings.write_setting("wine-update-channel", value)

View File

@ -6,8 +6,8 @@ from typing import Callable, Union
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, GLib, GObject, Gtk
@ -26,9 +26,14 @@ class Dialog(Gtk.Dialog):
the response for you via 'response_type' or 'confirmed' and destory this
dialog if it isn't NONE."""
def __init__(self, title: str = None, parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0, buttons: Gtk.ButtonsType = None,
**kwargs):
def __init__(
self,
title: str = None,
parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0,
buttons: Gtk.ButtonsType = None,
**kwargs,
):
super().__init__(title, parent, flags, buttons, **kwargs)
self._response_type = Gtk.ResponseType.NONE
self.connect("response", self.on_response)
@ -75,16 +80,14 @@ class Dialog(Gtk.Dialog):
idle_source_id = GLib.idle_add(idle_destroy)
on_destroy_id = self.connect("destroy", on_destroy)
def add_styled_button(self, button_text: str, response_id: Gtk.ResponseType,
css_class: str):
def add_styled_button(self, button_text: str, response_id: Gtk.ResponseType, css_class: str):
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: str, response_id: Gtk.ResponseType,
css_class: str = "suggested-action"):
def add_default_button(self, button_text: str, response_id: Gtk.ResponseType, css_class: str = "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)
@ -98,9 +101,14 @@ class ModalDialog(Dialog):
Unlike plain Gtk.Dialog, these destroy themselves (at idle-time) after
you call run(), even if you forget to. They aren't meant to be reused."""
def __init__(self, title: str = None, parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0, buttons: Gtk.ButtonsType = None,
**kwargs):
def __init__(
self,
title: str = None,
parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0,
buttons: Gtk.ButtonsType = None,
**kwargs,
):
super().__init__(title, parent, flags | Gtk.DialogFlags.MODAL, buttons, **kwargs)
self.set_destroy_with_parent(True)
@ -120,9 +128,14 @@ class ModelessDialog(Dialog):
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: str = None, parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0, buttons: Gtk.ButtonsType = None,
**kwargs):
def __init__(
self,
title: str = None,
parent: Gtk.Widget = None,
flags: Gtk.DialogFlags = 0,
buttons: Gtk.ButtonsType = None,
**kwargs,
):
super().__init__(title, parent, flags, buttons, **kwargs)
# These are not stuck above the 'main' window, but can be
# re-ordered freely.
@ -265,9 +278,13 @@ class WarningDialog(Gtk.MessageDialog):
class ErrorDialog(Gtk.MessageDialog):
"""Display an error message."""
def __init__(self, error: Union[str, BaseException],
message_markup: str = None, secondary: str = None,
parent: Gtk.Window = None):
def __init__(
self,
error: Union[str, BaseException],
message_markup: str = None,
secondary: str = None,
parent: Gtk.Window = None,
):
super().__init__(message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, parent=parent)
if isinstance(error, BaseException):
@ -436,7 +453,6 @@ class FileDialog:
class InstallOrPlayDialog(ModalDialog):
def __init__(self, game_name, parent=None):
super().__init__(title=_("%s is already installed") % game_name, parent=parent, border_width=10)
self.action = "play"
@ -619,8 +635,10 @@ class MoveDialog(ModelessDialog):
def _move_game_cb(self, _result, error):
if error and isinstance(error, InvalidGameMoveError):
secondary = _("Do you want to change the game location anyway? No files can be moved, "
"and the game configuration may need to be adjusted.")
secondary = _(
"Do you want to change the game location anyway? No files can be moved, "
"and the game configuration may need to be adjusted."
)
dlg = WarningDialog(message_markup=error, secondary=secondary, parent=self)
if dlg.result == Gtk.ResponseType.OK:
self.new_directory = self.game.set_location(self.destination)
@ -651,22 +669,24 @@ class HumbleBundleCookiesDialog(ModalDialog):
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 6)
self.get_content_area().add(vbox)
label = Gtk.Label()
label.set_markup(_(
"<b>Humble Bundle Authentication via cookie import</b>\n"
"\n"
"<b>In Firefox</b>\n"
"- Install the following extension: "
"<a href='https://addons.mozilla.org/en-US/firefox/addon/export-cookies-txt/'>"
"https://addons.mozilla.org/en-US/firefox/addon/export-cookies-txt/"
"</a>\n"
"- Open a tab to humblebundle.com and make sure you are logged in.\n"
"- Click the cookie icon in the top right corner, next to the settings menu\n"
"- Check 'Prefix HttpOnly cookies' and click 'humblebundle.com'\n"
"- Open the generated file and paste the contents below. Click OK to finish.\n"
"- You can delete the cookies file generated by Firefox\n"
"- Optionally, <a href='https://support.humblebundle.com/hc/en-us/requests/new'>"
"open a support ticket</a> to ask Humble Bundle to fix their configuration."
))
label.set_markup(
_(
"<b>Humble Bundle Authentication via cookie import</b>\n"
"\n"
"<b>In Firefox</b>\n"
"- Install the following extension: "
"<a href='https://addons.mozilla.org/en-US/firefox/addon/export-cookies-txt/'>"
"https://addons.mozilla.org/en-US/firefox/addon/export-cookies-txt/"
"</a>\n"
"- Open a tab to humblebundle.com and make sure you are logged in.\n"
"- Click the cookie icon in the top right corner, next to the settings menu\n"
"- Check 'Prefix HttpOnly cookies' and click 'humblebundle.com'\n"
"- Open the generated file and paste the contents below. Click OK to finish.\n"
"- You can delete the cookies file generated by Firefox\n"
"- Optionally, <a href='https://support.humblebundle.com/hc/en-us/requests/new'>"
"open a support ticket</a> to ask Humble Bundle to fix their configuration."
)
)
vbox.pack_start(label, False, False, 24)
self.textview = Gtk.TextView()
self.textview.set_left_margin(12)

View File

@ -9,12 +9,7 @@ from lutris.gui.widgets.common import FileChooserEntry
class CacheConfigurationDialog(ModalDialog):
def __init__(self, parent=None):
super().__init__(
_("Download cache configuration"),
parent=parent,
flags=Gtk.DialogFlags.MODAL,
border_width=10
)
super().__init__(_("Download cache configuration"), parent=parent, flags=Gtk.DialogFlags.MODAL, border_width=10)
self.timer_id = None
self.set_size_request(480, 150)
@ -44,7 +39,7 @@ class CacheConfigurationDialog(ModalDialog):
action=Gtk.FileChooserAction.SELECT_FOLDER,
warn_if_non_writable_parent=True,
text=self.cache_path,
activates_default=True
activates_default=True,
)
path_chooser.connect("changed", self._on_cache_path_set)
box.pack_start(path_chooser, True, True, 0)
@ -52,11 +47,13 @@ class CacheConfigurationDialog(ModalDialog):
prefs_box.pack_start(box, False, False, 6)
cache_help_label = Gtk.Label(visible=True)
cache_help_label.set_size_request(400, -1)
cache_help_label.set_markup(_(
"If provided, this location will be used by installers to cache "
"downloaded files locally for future re-use. \nIf left empty, the "
"installer files are discarded after the install completion."
))
cache_help_label.set_markup(
_(
"If provided, this location will be used by installers to cache "
"downloaded files locally for future re-use. \nIf left empty, the "
"installer files are discarded after the install completion."
)
)
prefs_box.pack_start(cache_help_label, False, False, 6)
return prefs_box

View File

@ -115,19 +115,23 @@ class DialogInstallUIDelegate(InstallUIDelegate):
"""This provides UI for runner installation via dialogs."""
def show_install_yesno_inquiry(self, question, title):
dialog = dialogs.QuestionDialog({
"parent": self,
"question": question,
"title": title,
})
dialog = dialogs.QuestionDialog(
{
"parent": self,
"question": question,
"title": title,
}
)
return Gtk.ResponseType.YES == dialog.result
def show_install_file_inquiry(self, question, title, message):
dlg = dialogs.QuestionDialog({
"parent": self,
"question": question,
"title": title,
})
dlg = dialogs.QuestionDialog(
{
"parent": self,
"question": question,
"title": title,
}
)
if dlg.result == dlg.YES:
dlg = dialogs.FileDialog(message)
return dlg.filename
@ -199,12 +203,7 @@ class DialogLaunchUIDelegate(LaunchUIDelegate):
config_index = get_preferred_config_index()
if config_index is None:
dlg = dialogs.LaunchConfigSelectDialog(
game,
configs,
title=_("Select game to launch"),
parent=self
)
dlg = dialogs.LaunchConfigSelectDialog(game, configs, title=_("Select game to launch"), parent=self)
if not dlg.confirmed:
return None # no error here- the user cancelled out

View File

@ -20,11 +20,7 @@ from lutris.util.system import get_md5_hash, get_md5_in_zip
class ImportGameDialog(ModelessDialog):
def __init__(self, files, parent=None) -> None:
super().__init__(
_("Import a game"),
parent=parent,
border_width=10
)
super().__init__(_("Import a game"), parent=parent, border_width=10)
self.files = files
self.progress_labels = {}
self.checksum_labels = {}
@ -41,9 +37,7 @@ class ImportGameDialog(ModelessDialog):
scrolledwindow = Gtk.ScrolledWindow(child=self.get_file_labels_listbox(files))
scrolledwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
frame = Gtk.Frame(
shadow_type=Gtk.ShadowType.ETCHED_IN,
child=scrolledwindow)
frame = Gtk.Frame(shadow_type=Gtk.ShadowType.ETCHED_IN, child=scrolledwindow)
self.get_content_area().pack_start(frame, True, True, 6)
self.close_button = self.add_button(Gtk.STOCK_STOP, Gtk.ResponseType.CANCEL)
@ -204,8 +198,7 @@ class ImportGameDialog(ModelessDialog):
except Exception as ex:
logger.exception(_("Failed to import a ROM: %s"), ex)
error_label = self.error_labels[filename]
error_label.set_markup(
"<span style=\"italic\" foreground=\"red\">%s</span>" % gtk_safe(str(ex)))
error_label.set_markup('<span style="italic" foreground="red">%s</span>' % gtk_safe(str(ex)))
error_label.show()
return False
@ -256,7 +249,8 @@ class ImportGameDialog(ModelessDialog):
installer = deepcopy(DEFAULT_INSTALLERS[self.platform])
except KeyError as error:
raise RuntimeError(
_("Lutris does not have a default installer for the '%s' platform.") % self.platform) from error
_("Lutris does not have a default installer for the '%s' platform.") % self.platform
) from error
for key, value in installer["game"].items():
if value == "rom":
@ -270,7 +264,7 @@ class ImportGameDialog(ModelessDialog):
directory="",
installed=1,
installer_slug="%s-%s" % (slug, installer["runner"]),
configpath=configpath
configpath=configpath,
)
download_lutris_media(slug)
return game_id

View File

@ -24,11 +24,13 @@ class IssueReportWindow(BaseApplicationWindow):
self.vbox.add(title_label)
self.vbox.add(Gtk.HSeparator())
issue_entry_label = Gtk.Label(_(
"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 also save this information locally if you are offline."
))
issue_entry_label = Gtk.Label(
_(
"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 also save this information locally if you are offline."
)
)
issue_entry_label.set_max_width_chars(80)
issue_entry_label.set_property("wrap", True)
self.vbox.add(issue_entry_label)
@ -56,8 +58,8 @@ class IssueReportWindow(BaseApplicationWindow):
def get_issue_info(self):
buffer = self.textview.get_buffer()
return {
'comment': buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True),
'system': gather_system_info()
"comment": buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True),
"system": gather_system_info(),
}
def on_save(self, _button):
@ -81,7 +83,7 @@ class IssueReportWindow(BaseApplicationWindow):
return
issue_path = os.path.join(target_path, "lutris-issue-report.json")
issue_info = self.get_issue_info()
with open(issue_path, "w", encoding='utf-8') as issue_file:
with open(issue_path, "w", encoding="utf-8") as issue_file:
json.dump(issue_info, issue_file, indent=2)
dialog.destroy()
NoticeDialog(_("Issue saved in %s") % issue_path)

View File

@ -11,7 +11,6 @@ from lutris.util import datapath
class LogWindow(GObject.Object):
def __init__(self, game, buffer, application=None):
super().__init__()
ui_filename = os.path.join(datapath.get(), "ui/log-window.ui")
@ -53,18 +52,12 @@ class LogWindow(GObject.Object):
now = datetime.now()
log_filename = "%s (%s).log" % (self.title, now.strftime("%Y-%m-%d-%H-%M"))
file_dialog = FileDialog(
message="Save the logs to...",
default_path=os.path.expanduser("~/%s" % log_filename),
mode="save"
message="Save the logs to...", default_path=os.path.expanduser("~/%s" % log_filename), mode="save"
)
log_path = file_dialog.filename
if not log_path:
return
text = self.buffer.get_text(
self.buffer.get_start_iter(),
self.buffer.get_end_iter(),
True
)
with open(log_path, "w", encoding='utf-8') as log_file:
text = self.buffer.get_text(self.buffer.get_start_iter(), self.buffer.get_end_iter(), True)
with open(log_path, "w", encoding="utf-8") as log_file:
log_file.write(text)

View File

@ -30,10 +30,7 @@ def get_installed_versions(runner_directory):
"""List versions available locally"""
if not os.path.exists(runner_directory):
return set()
return {
parse_version_architecture(p)
for p in os.listdir(runner_directory)
}
return {parse_version_architecture(p) for p in os.listdir(runner_directory)}
def get_usage_stats(runner_name):
@ -93,6 +90,7 @@ class ShowAppsDialog(ModelessDialog):
class RunnerInstallDialog(ModelessDialog):
"""Dialog displaying available runner version and downloads them"""
COL_VER = 0
COL_ARCH = 1
COL_URL = 2
@ -100,8 +98,9 @@ class RunnerInstallDialog(ModelessDialog):
COL_PROGRESS = 4
COL_USAGE = 5
INSTALLED_ICON_NAME = "software-installed-symbolic" \
if has_stock_icon("software-installed-symbolic") else "wine-symbolic"
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, border_width=10)
@ -134,11 +133,13 @@ class RunnerInstallDialog(ModelessDialog):
remote_versions = {(v["version"], v["architecture"]) for v in runner_info["versions"]}
local_versions = get_installed_versions(runner_directory)
for local_version in local_versions - remote_versions:
runner_info["versions"].append({
"version": local_version[0],
"architecture": local_version[1],
"url": "",
})
runner_info["versions"].append(
{
"version": local_version[0],
"architecture": local_version[1],
"url": "",
}
)
return runner_info, RunnerInstallDialog.fetch_runner_store(runner_info)
@ -152,7 +153,8 @@ class RunnerInstallDialog(ModelessDialog):
ordered = sorted(runner_info["versions"], key=RunnerInstallDialog.get_version_sort_key)
for version_info in reversed(ordered):
is_installed = os.path.exists(
get_runner_path(runner_directory, version_info["version"], version_info["architecture"]))
get_runner_path(runner_directory, version_info["version"], version_info["architecture"])
)
games_using = version_usage.get("%(version)s-%(architecture)s" % version_info)
runner_store.append(
{
@ -161,7 +163,7 @@ class RunnerInstallDialog(ModelessDialog):
"url": version_info["url"],
"is_installed": is_installed,
"progress": 0,
"game_count": len(games_using) if games_using else 0
"game_count": len(games_using) if games_using else 0,
}
)
return runner_store
@ -233,11 +235,7 @@ class RunnerInstallDialog(ModelessDialog):
# Check if there are apps installed, if so, show the view apps button
app_count = runner["game_count"] or 0
if app_count > 0:
usage_button_text = gettext.ngettext(
"View %d game",
"View %d games",
app_count
) % app_count
usage_button_text = gettext.ngettext("View %d game", "View %d games", app_count) % app_count
usage_button = Gtk.LinkButton.new_with_label(usage_button_text)
usage_button.set_valign(Gtk.Align.CENTER)
@ -347,9 +345,7 @@ class RunnerInstallDialog(ModelessDialog):
def on_error(error):
ErrorDialog(error, parent=self)
system.remove_folder(runner_path,
completion_function=on_complete,
error_function=on_error)
system.remove_folder(runner_path, completion_function=on_complete, error_function=on_error)
def on_install_runner(self, _widget, row):
self.install_runner(row)
@ -435,6 +431,7 @@ class RunnerInstallDialog(ModelessDialog):
if self.runner_name == "wine":
logger.debug("Clearing wine version cache")
from lutris.util.wine.wine import get_installed_wine_versions
get_installed_wine_versions.cache_clear()
def on_response(self, dialog, response: Gtk.ResponseType) -> None:

View File

@ -76,10 +76,7 @@ class UninstallDialog(Gtk.Dialog):
self.any_protected = False
def is_shared(directory: str) -> bool:
dir_users = set(
str(g["id"])
for g in get_games(filters={"directory": directory, "installed": 1})
)
dir_users = set(str(g["id"]) for g in get_games(filters={"directory": directory, "installed": 1}))
for g in self.games:
dir_users.discard(g.id)
return bool(dir_users)
@ -87,9 +84,7 @@ class UninstallDialog(Gtk.Dialog):
for row in self.uninstall_game_list.get_children():
game = row.game
if game.is_installed and game.directory:
if game.config and is_removeable(
game.directory, game.config.system_config
):
if game.config and is_removeable(game.directory, game.config.system_config):
shared_dir = is_shared(game.directory)
self.any_shared = self.any_shared or shared_dir
row.can_delete_files = not shared_dir
@ -147,11 +142,7 @@ class UninstallDialog(Gtk.Dialog):
messages = []
if to_uninstall:
messages.append(
_(
"After you uninstall these games, you won't be able play them in Lutris."
)
)
messages.append(_("After you uninstall these games, you won't be able play them in Lutris."))
messages.append(
_(
"Uninstalled games that you remove from the library will no longer appear in the "
@ -159,12 +150,7 @@ class UninstallDialog(Gtk.Dialog):
)
)
else:
messages.append(
_(
"After you remove these games, they will no longer "
"appear in the 'Games' view."
)
)
messages.append(_("After you remove these games, they will no longer " "appear in the 'Games' view."))
if self.any_shared:
messages.append(
@ -175,11 +161,7 @@ class UninstallDialog(Gtk.Dialog):
)
if self.any_protected:
messages.append(
_(
"Some of the game directories cannot be removed because they are protected."
)
)
messages.append(_("Some of the game directories cannot be removed because they are protected."))
if messages:
self.message_label.set_markup("\n\n".join(messages))
@ -213,9 +195,7 @@ class UninstallDialog(Gtk.Dialog):
checkbox.set_active(set_count > 0)
checkbox.set_inconsistent(set_count > 0 and unset_count > 0)
checkbox.set_visible(
(set_count + unset_count) > 1 and (set_count > 0 or unset_count > 0)
)
checkbox.set_visible((set_count + unset_count) > 1 and (set_count > 0 or unset_count > 0))
if not self._setting_all_checkboxes:
self._setting_all_checkboxes = True
@ -254,9 +234,7 @@ class UninstallDialog(Gtk.Dialog):
self._apply_all_checkbox(self.remove_all_games_checkbox, update_row)
def _apply_all_checkbox(
self, checkbox, row_updater: Callable[["GameRemovalRow", bool], None]
):
def _apply_all_checkbox(self, checkbox, row_updater: Callable[["GameRemovalRow", bool], None]):
"""Sets the state of the checkboxes on all rows to agree with 'checkbox';
the actual change is performed by row_updater, so this can be used for
either checkbox."""
@ -277,24 +255,17 @@ class UninstallDialog(Gtk.Dialog):
@GtkTemplate.Callback
def on_remove_button_clicked(self, _widget) -> None:
rows = list(self.uninstall_game_list.get_children())
dirs_to_delete = list(
set(
row.game.directory
for row in rows
if row.delete_files
)
)
dirs_to_delete = list(set(row.game.directory for row in rows if row.delete_files))
if dirs_to_delete:
if len(dirs_to_delete) == 1:
question = _(
"Please confirm.\nEverything under <b>%s</b>\n"
"will be moved to the trash."
) % gtk_safe(dirs_to_delete[0])
question = _("Please confirm.\nEverything under <b>%s</b>\n" "will be moved to the trash.") % gtk_safe(
dirs_to_delete[0]
)
else:
question = _(
"Please confirm.\nAll the files for %d games will be moved to the trash."
) % len(dirs_to_delete)
question = _("Please confirm.\nAll the files for %d games will be moved to the trash.") % len(
dirs_to_delete
)
dlg = QuestionDialog(
{
@ -379,9 +350,7 @@ class GameRemovalRow(Gtk.ListBoxRow):
label = Gtk.Label(game.name, selectable=True)
hbox.pack_start(label, False, False, 0)
self.remove_from_library_checkbox = Gtk.CheckButton(
_("Remove from Library"), halign=Gtk.Align.START
)
self.remove_from_library_checkbox = Gtk.CheckButton(_("Remove from Library"), halign=Gtk.Align.START)
self.remove_from_library_checkbox.set_sensitive(game.is_installed)
self.remove_from_library_checkbox.set_active(True)
self.remove_from_library_checkbox.connect("toggled", self.on_checkbox_toggled)
@ -403,9 +372,7 @@ class GameRemovalRow(Gtk.ListBoxRow):
margin_right=6,
height_request=16,
)
self.directory_label = Gtk.Label(
halign=Gtk.Align.START, selectable=True, valign=Gtk.Align.START
)
self.directory_label = Gtk.Label(halign=Gtk.Align.START, selectable=True, valign=Gtk.Align.START)
self.directory_label.set_markup(self._get_directory_markup())
dir_box.pack_start(self.directory_label, False, False, 0)

View File

@ -3,6 +3,7 @@ import os
from gettext import gettext as _
import gi
try:
gi.require_version("WebKit2", "4.1")
except ValueError:
@ -19,7 +20,6 @@ class WebConnectDialog(ModalDialog):
"""Login form for external services"""
def __init__(self, service, parent=None):
self.context = WebKit2.WebContext.new()
if "http_proxy" in os.environ:
proxy = WebKit2.NetworkProxySettings.new(os.environ["http_proxy"])
@ -103,7 +103,7 @@ class WebPopupDialog(ModalDialog):
def __init__(self, webview, uri, parent=None):
# pylint: disable=no-member
self.parent = parent
super().__init__(title=_('Loading...'), parent=parent)
super().__init__(title=_("Loading..."), parent=parent)
self.webview = webview
self.webview.connect("ready-to-show", self.on_ready_webview)
self.webview.connect("notify::title", self.on_available_webview_title)

View File

@ -15,6 +15,7 @@ from lutris.util.log import logger
class DownloadQueue(Gtk.ScrolledWindow):
"""This class is a widget that displays a stack of progress boxes, which you can create
and destroy with its methods."""
__gtype_name__ = "DownloadQueue"
download_box: Gtk.Box = GtkTemplate.Child()
@ -90,11 +91,14 @@ class DownloadQueue(Gtk.ScrolledWindow):
if not self.progress_boxes:
self.revealer.set_reveal_child(False)
def start(self, operation: Callable[[], Any],
progress_function: ProgressBox.ProgressFunction,
completion_function: CompletionFunction = None,
error_function: ErrorFunction = None,
operation_name: str = None) -> bool:
def start(
self,
operation: Callable[[], Any],
progress_function: ProgressBox.ProgressFunction,
completion_function: CompletionFunction = None,
error_function: ErrorFunction = None,
operation_name: str = None,
) -> bool:
"""Runs 'operation' on a thread, while displaying a progress bar. The 'progress_function'
controls this progress bar, and it is removed when the 'operation' completes.
@ -109,16 +113,22 @@ class DownloadQueue(Gtk.ScrolledWindow):
error_function: Called on the main threa don error, with exception
operation_name: Name of operation, to prevent duplicate queued work."""
return self.start_multiple(operation, [progress_function],
completion_function=completion_function,
error_function=error_function,
operation_names=[operation_name] if operation_name else None)
return self.start_multiple(
operation,
[progress_function],
completion_function=completion_function,
error_function=error_function,
operation_names=[operation_name] if operation_name else None,
)
def start_multiple(self, operation: Callable[[], Any],
progress_functions: Iterable[ProgressBox.ProgressFunction],
completion_function: CompletionFunction = None,
error_function: ErrorFunction = None,
operation_names: List[str] = None) -> bool:
def start_multiple(
self,
operation: Callable[[], Any],
progress_functions: Iterable[ProgressBox.ProgressFunction],
completion_function: CompletionFunction = None,
error_function: ErrorFunction = None,
operation_names: List[str] = None,
) -> bool:
"""Runs 'operation' on a thread, while displaying a set of progress bars. The
'progress_functions' control these progress bars, and they are removed when the
'operation' completes.

View File

@ -70,10 +70,8 @@ class InstallerFileBox(Gtk.VBox):
# InstallerFileCollection should not have steam provider
if self.provider == "steam":
if isinstance(self.installer_file, InstallerFileCollection):
raise RuntimeError(
"Installer file is type InstallerFileCollection and do not support 'steam' provider")
steam_installer = SteamInstaller(self.installer_file.url,
self.installer_file.id)
raise RuntimeError("Installer file is type InstallerFileCollection and do not support 'steam' provider")
steam_installer = SteamInstaller(self.installer_file.url, self.installer_file.id)
steam_installer.connect("steam-game-installed", self.on_download_complete)
steam_installer.connect("steam-state-changed", self.on_state_changed)
self.start_func = steam_installer.install_steam_game
@ -81,9 +79,7 @@ class InstallerFileBox(Gtk.VBox):
steam_box = Gtk.HBox(spacing=6)
info_box = Gtk.VBox(spacing=6)
steam_label = InstallerLabel(_("Steam game <b>{appid}</b>").format(
appid=steam_installer.appid
))
steam_label = InstallerLabel(_("Steam game <b>{appid}</b>").format(appid=steam_installer.appid))
info_box.add(steam_label)
self.state_label = InstallerLabel("")
info_box.add(self.state_label)
@ -92,7 +88,7 @@ class InstallerFileBox(Gtk.VBox):
raise ValueError("Invalid provider %s" % self.provider)
def get_combobox_model(self):
""""Return the combobox's model"""
""" "Return the combobox's model"""
model = Gtk.ListStore(str, str)
if "download" in self.installer_file.providers:
model.append(["download", _("Download")])
@ -153,10 +149,7 @@ class InstallerFileBox(Gtk.VBox):
label = InstallerLabel(self.installer_file.get_label())
label.props.can_focus = True
box.pack_start(label, False, False, 0)
location_entry = FileChooserEntry(
self.installer_file.human_url,
Gtk.FileChooserAction.OPEN
)
location_entry = FileChooserEntry(self.installer_file.human_url, Gtk.FileChooserAction.OPEN)
location_entry.connect("changed", self.on_location_changed)
location_entry.show()
box.pack_start(location_entry, False, False, 0)
@ -170,11 +163,7 @@ class InstallerFileBox(Gtk.VBox):
def get_widgets(self):
"""Return the widget with the source of the file and a way to change its source"""
box = Gtk.HBox(
spacing=12,
margin_top=6,
margin_bottom=6
)
box = Gtk.HBox(spacing=12, margin_top=6, margin_bottom=6)
self.file_provider_widget = self.get_file_provider_label()
box.pack_start(self.file_provider_widget, True, True, 0)
source_box = Gtk.HBox()

View File

@ -10,8 +10,8 @@ class InstallerFilesBox(Gtk.ListBox):
max_downloads = 3
__gsignals__ = {
"files-ready": (GObject.SIGNAL_RUN_LAST, None, (bool, )),
"files-available": (GObject.SIGNAL_RUN_LAST, None, ())
"files-ready": (GObject.SIGNAL_RUN_LAST, None, (bool,)),
"files-available": (GObject.SIGNAL_RUN_LAST, None, ()),
}
def __init__(self):

View File

@ -6,7 +6,7 @@ from lutris.gui.installer.script_box import InstallerScriptBox
class InstallerPicker(Gtk.ListBox):
"""List box to pick between several installers"""
__gsignals__ = {"installer-selected": (GObject.SIGNAL_RUN_FIRST, None, (str, ))}
__gsignals__ = {"installer-selected": (GObject.SIGNAL_RUN_FIRST, None, (str,))}
def __init__(self, scripts):
super().__init__()
@ -14,7 +14,7 @@ class InstallerPicker(Gtk.ListBox):
for script in scripts:
self.add(InstallerScriptBox(script, parent=self, revealed=revealed))
revealed = False # Only reveal the first installer.
self.connect('row-selected', self.on_activate)
self.connect("row-selected", self.on_activate)
self.show_all()
@staticmethod

View File

@ -32,18 +32,11 @@ class MarkupLabel(Gtk.Label):
"""Label for installer window"""
def __init__(self, markup=None, **kwargs):
super().__init__(
label=markup,
use_markup=True,
wrap=True,
max_width_chars=80,
**kwargs)
super().__init__(label=markup, use_markup=True, wrap=True, max_width_chars=80, **kwargs)
self.set_alignment(0.5, 0)
class InstallerWindow(ModelessDialog,
DialogInstallUIDelegate,
ScriptInterpreter.InterpreterUIDelegate): # pylint: disable=too-many-public-methods
class InstallerWindow(ModelessDialog, DialogInstallUIDelegate, ScriptInterpreter.InterpreterUIDelegate): # pylint: disable=too-many-public-methods
"""GUI for the install process.
This window is divided into pages; as you go through the install each page
@ -57,14 +50,7 @@ class InstallerWindow(ModelessDialog,
uses a create_X_page() function to create the page the first time it is visited.
"""
def __init__(
self,
installers,
service=None,
appid=None,
installation_kind=InstallationKind.INSTALL,
**kwargs
):
def __init__(self, installers, service=None, appid=None, installation_kind=InstallationKind.INSTALL, **kwargs):
ModelessDialog.__init__(self, use_header_bar=True, **kwargs)
ScriptInterpreter.InterpreterUIDelegate.__init__(self, service, appid)
self.set_default_size(740, 460)
@ -124,10 +110,11 @@ class InstallerWindow(ModelessDialog,
self.menu_button.set_popover(Gtk.Popover(child=self.menu_box, can_focus=False, relative_to=self.menu_button))
self.get_header_bar().pack_end(self.menu_button)
self.cache_button = self.add_menu_button(_("Configure download cache"),
self.on_cache_clicked,
tooltip=_(
"Change where Lutris downloads game installer files."))
self.cache_button = self.add_menu_button(
_("Configure download cache"),
self.on_cache_clicked,
tooltip=_("Change where Lutris downloads game installer files."),
)
self.source_button = self.add_menu_button(_("View installer source"), self.on_source_clicked)
@ -146,7 +133,7 @@ class InstallerWindow(ModelessDialog,
Gtk.FileChooserAction.SELECT_FOLDER,
warn_if_non_empty=True,
warn_if_non_writable_parent=True,
warn_if_ntfs=True
warn_if_ntfs=True,
)
self.location_entry.connect("changed", self.on_location_entry_changed)
@ -225,10 +212,13 @@ class InstallerWindow(ModelessDialog,
widgets = []
remove_checkbox = Gtk.CheckButton.new_with_label(_("Remove game files"))
if self.interpreter and self.interpreter.target_path and \
self.interpreter.game_dir_created and \
self.installation_kind == InstallationKind.INSTALL and \
is_removeable(self.interpreter.target_path, LutrisConfig().system_config):
if (
self.interpreter
and self.interpreter.target_path
and self.interpreter.game_dir_created
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()
widgets.append(remove_checkbox)
@ -238,7 +228,7 @@ class InstallerWindow(ModelessDialog,
"parent": self,
"question": _("Are you sure you want to cancel the installation?"),
"title": _("Cancel installation?"),
"widgets": widgets
"widgets": widgets,
}
)
if confirm_cancel_dialog.result != Gtk.ResponseType.YES:
@ -256,11 +246,7 @@ class InstallerWindow(ModelessDialog,
self.destroy()
def on_source_clicked(self, _button):
InstallerSourceDialog(
self.interpreter.installer.script_pretty,
self.interpreter.installer.game_name,
self
)
InstallerSourceDialog(self.interpreter.installer.script_pretty, self.interpreter.installer.game_name, self)
def on_signal_error(self, error):
self._handle_callback_error(error)
@ -307,7 +293,13 @@ class InstallerWindow(ModelessDialog,
GLib.idle_add(self.load_log_page)
def begin_disc_prompt(self, message, requires, installer, callback):
GLib.idle_add(self.load_ask_for_disc_page, message, requires, installer, callback, )
GLib.idle_add(
self.load_ask_for_disc_page,
message,
requires,
installer,
callback,
)
def begin_input_menu(self, alias, options, preselect, callback):
GLib.idle_add(self.load_input_menu_page, alias, options, preselect, callback)
@ -327,10 +319,7 @@ class InstallerWindow(ModelessDialog,
installer_picker = InstallerPicker(self.installers)
installer_picker.connect("installer-selected", self.on_installer_selected)
return Gtk.ScrolledWindow(
hexpand=True,
vexpand=True,
child=installer_picker,
shadow_type=Gtk.ShadowType.ETCHED_IN
hexpand=True, vexpand=True, child=installer_picker, shadow_type=Gtk.ShadowType.ETCHED_IN
)
def present_choose_installer_page(self):
@ -434,15 +423,18 @@ class InstallerWindow(ModelessDialog,
self.set_status(_("Select installation directory"))
self.stack.present_page("destination")
self.display_continue_button(self.on_destination_confirmed,
extra_buttons=[self.cache_button, self.source_button])
self.display_continue_button(
self.on_destination_confirmed, extra_buttons=[self.cache_button, self.source_button]
)
def on_destination_confirmed(self, _button=None):
"""Let the interpreter take charge of the next stages."""
self.load_spinner_page(_("Preparing Lutris for installation"),
cancellable=False,
extra_buttons=[self.cache_button, self.source_button])
self.load_spinner_page(
_("Preparing Lutris for installation"),
cancellable=False,
extra_buttons=[self.cache_button, self.source_button],
)
GLib.idle_add(self.launch_install)
def launch_install(self):
@ -521,11 +513,7 @@ class InstallerWindow(ModelessDialog,
treeview.append_column(label_column)
return Gtk.ScrolledWindow(
hexpand=True,
vexpand=True,
child=treeview,
visible=True,
shadow_type=Gtk.ShadowType.ETCHED_IN
hexpand=True, vexpand=True, child=treeview, visible=True, shadow_type=Gtk.ShadowType.ETCHED_IN
)
def present_extras_page(self):
@ -535,10 +523,12 @@ class InstallerWindow(ModelessDialog,
def on_continue(_button):
self.on_extras_confirmed(self.extras_tree_store)
self.set_status(_(
"This game has extra content. \nSelect which one you want and "
"they will be available in the 'extras' folder where the game is installed."
))
self.set_status(
_(
"This game has extra content. \nSelect which one you want and "
"they will be available in the 'extras' folder where the game is installed."
)
)
self.stack.present_page("extras")
self.display_continue_button(on_continue, extra_buttons=[self.cache_button, self.source_button])
@ -600,8 +590,9 @@ class InstallerWindow(ModelessDialog,
else:
patch_version = None
AsyncCall(self.interpreter.installer.prepare_game_files,
self.on_files_prepared, self.selected_extras, patch_version)
AsyncCall(
self.interpreter.installer.prepare_game_files, self.on_files_prepared, self.selected_extras, patch_version
)
def on_files_prepared(self, _result, error):
if error:
@ -623,14 +614,13 @@ class InstallerWindow(ModelessDialog,
vexpand=True,
child=self.installer_files_box,
visible=True,
shadow_type=Gtk.ShadowType.ETCHED_IN
shadow_type=Gtk.ShadowType.ETCHED_IN,
)
def present_installer_files_page(self):
"""Show installer screen with the file picker / downloader"""
logger.debug("Presenting installer files page")
self.set_status(_(
"Please review the files needed for the installation then click 'Install'"))
self.set_status(_("Please review the files needed for the installation then click 'Install'"))
self.stack.present_page("installer_files")
self.display_install_button(self.on_files_confirmed, sensitive=self.installer_files_box.is_ready)
@ -714,12 +704,7 @@ class InstallerWindow(ModelessDialog,
def create_log_page(self):
log_textview = LogTextView(self.log_buffer)
return Gtk.ScrolledWindow(
hexpand=True,
vexpand=True,
child=log_textview,
shadow_type=Gtk.ShadowType.ETCHED_IN
)
return Gtk.ScrolledWindow(hexpand=True, vexpand=True, child=log_textview, shadow_type=Gtk.ShadowType.ETCHED_IN)
def present_log_page(self):
"""Creates a TextBuffer and attach it to a command"""
@ -899,9 +884,7 @@ class InstallerWindow(ModelessDialog,
def present_finished_page(self, game_id, status):
self.set_status(status)
self.stack.present_page("nothing")
self.display_continue_button(self.on_launch_clicked,
continue_button_label=_("_Launch"),
suggested_action=False)
self.display_continue_button(self.on_launch_clicked, continue_button_label=_("_Launch"), suggested_action=False)
def on_launch_clicked(self, button):
"""Launch a game after it's been installed."""
@ -932,11 +915,9 @@ class InstallerWindow(ModelessDialog,
xdgshortcuts.create_launcher(game_slug, game_id, game_name, menu=True)
# Buttons
def display_continue_button(self, handler,
continue_button_label=_("_Continue"),
sensitive=True,
suggested_action=True,
extra_buttons=None):
def display_continue_button(
self, handler, continue_button_label=_("_Continue"), sensitive=True, suggested_action=True, extra_buttons=None
):
"""This shows the continue button, the close button, and any extra buttons you
indicate. This will also set the label and sensitivity of the continue button.
@ -969,9 +950,9 @@ class InstallerWindow(ModelessDialog,
def display_install_button(self, handler, sensitive=True):
"""Displays the continue button, but labels it 'Install'."""
self.display_continue_button(handler, continue_button_label=_(
"_Install"), sensitive=sensitive,
extra_buttons=[self.source_button])
self.display_continue_button(
handler, continue_button_label=_("_Install"), sensitive=sensitive, extra_buttons=[self.source_button]
)
def display_cancel_button(self, extra_buttons=None):
self.display_buttons(extra_buttons or [])
@ -991,9 +972,7 @@ class InstallerWindow(ModelessDialog,
self.cancel_button.set_tooltip_text("")
style_context.remove_class("destructive-action")
all_buttons = [self.cache_button,
self.source_button,
self.continue_button]
all_buttons = [self.cache_button, self.source_button, self.continue_button]
for b in all_buttons:
b.set_visible(b in buttons)

View File

@ -43,9 +43,7 @@ from lutris.util.system import update_desktop_icons
@GtkTemplate(ui=os.path.join(datapath.get(), "ui", "lutris-window.ui"))
class LutrisWindow(Gtk.ApplicationWindow,
DialogLaunchUIDelegate,
DialogInstallUIDelegate): # pylint: disable=too-many-public-methods
class LutrisWindow(Gtk.ApplicationWindow, DialogLaunchUIDelegate, DialogInstallUIDelegate): # pylint: disable=too-many-public-methods
"""Handler class for main window signals."""
default_view_type = "grid"
@ -74,7 +72,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
name="lutris",
icon_name="lutris",
application=application,
**kwargs
**kwargs,
)
update_desktop_icons()
load_icon_theme()
@ -172,26 +170,26 @@ class LutrisWindow(Gtk.ApplicationWindow,
self.on_toggle_badges,
type="b",
default=settings.read_setting("hide_badges_on_icons"),
accel="<Primary>p"
accel="<Primary>p",
),
"icon-type": Action(self.on_icontype_state_change, type="s", default=self.icon_type),
"view-sorting": Action(
self.on_view_sorting_state_change,
type="s",
default=self.view_sorting,
enabled=lambda: self.is_view_sort_sensitive
enabled=lambda: self.is_view_sort_sensitive,
),
"view-sorting-installed-first": Action(
self.on_view_sorting_installed_first_change,
type="b",
default=self.view_sorting_installed_first,
enabled=lambda: self.is_view_sort_sensitive
enabled=lambda: self.is_view_sort_sensitive,
),
"view-reverse-order": Action(
self.on_view_sorting_direction_change,
type="b",
default=self.view_reverse_order,
enabled=lambda: self.is_view_sort_sensitive
enabled=lambda: self.is_view_sort_sensitive,
),
"show-side-panel": Action(
self.on_side_panel_state_change,
@ -227,6 +225,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
action.connect("change-state", value.callback)
self.actions[name] = action
if value.enabled:
def updater(action=action, value=value):
action.props.enabled = value.enabled()
@ -272,9 +271,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
def load_filters(self):
"""Load the initial filters when creating the view"""
# The main sidebar-category filter will be populated when the sidebar row is selected, after this
return {
"installed": self.filter_installed
}
return {"installed": self.filter_installed}
@property
def is_show_hidden_sensitive(self):
@ -382,6 +379,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
installation_flag = not installation_flag
return [installation_flag, value]
return value
reverse = self.view_reverse_order if self.view_sorting == "name" else not self.view_reverse_order
return sorted(items, key=get_sort_value, reverse=reverse)
@ -407,12 +405,8 @@ class LutrisWindow(Gtk.ApplicationWindow,
def get_recent_games(self):
"""Return a list of currently running games"""
searches, _filters, excludes = self.get_sql_filters()
games = games_db.get_games(searches=searches, filters={'installed': '1'}, excludes=excludes)
return sorted(
games,
key=lambda game: max(game["installed_at"] or 0, game["lastplayed"] or 0),
reverse=True
)
games = games_db.get_games(searches=searches, filters={"installed": "1"}, excludes=excludes)
return sorted(games, key=lambda game: max(game["installed_at"] or 0, game["lastplayed"] or 0), reverse=True)
def game_matches(self, game):
if self.filters.get("installed"):
@ -452,10 +446,9 @@ class LutrisWindow(Gtk.ApplicationWindow,
lutris_games = {g["service_id"]: g for g in games_db.get_games(filters={"service": self.service.id})}
return [
self.combine_games(game, lutris_games.get(game["appid"])) for game in self.apply_view_sort(
service_games,
lambda game: lutris_games.get(game["appid"]) or game
) if self.game_matches(game)
self.combine_games(game, lutris_games.get(game["appid"]))
for game in self.apply_view_sort(service_games, lambda game: lutris_games.get(game["appid"]) or game)
if self.game_matches(game)
]
def get_games_from_filters(self):
@ -474,11 +467,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
category_game_ids = categories_db.get_game_ids_for_categories(included, excluded)
searches, filters, excludes = self.get_sql_filters()
games = games_db.get_games(
searches=searches,
filters=filters,
excludes=excludes
)
games = games_db.get_games(searches=searches, filters=filters, excludes=excludes)
games = [game for game in games if game["id"] in category_game_ids]
return self.apply_view_sort(games)
@ -535,11 +524,11 @@ class LutrisWindow(Gtk.ApplicationWindow,
if self.filters.get("category") == "favorite":
self.show_label(_("Add a game matching '%s' to your favorites to see it here.") % filter_text)
elif self.filters.get("category") == ".hidden":
self.show_label(
_("No hidden games matching '%s' found.") % filter_text)
self.show_label(_("No hidden games matching '%s' found.") % filter_text)
elif self.filters.get("installed") and has_uninstalled_games:
self.show_label(
_("No installed games matching '%s' found. Press Ctrl+I to show uninstalled games.") % filter_text)
_("No installed games matching '%s' found. Press Ctrl+I to show uninstalled games.") % filter_text
)
else:
self.show_label(_("No games matching '%s' found ") % filter_text)
else:
@ -662,7 +651,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
side_splash.set_alignment(0, 0)
center_splash = Gtk.Image(visible=True)
center_splash.set_alignment(.5, .5)
center_splash.set_alignment(0.5, 0.5)
center_splash.set_from_file(os.path.join(datapath.get(), "media/splash-%s.svg" % theme))
splash_box = Gtk.HBox(visible=True, margin_top=24)
@ -719,9 +708,12 @@ class LutrisWindow(Gtk.ApplicationWindow,
return Gtk.ApplicationWindow.do_key_press_event(self, event)
if ( # pylint: disable=too-many-boolean-expressions
not Gdk.KEY_0 <= event.keyval <= Gdk.KEY_z or event.state & Gdk.ModifierType.CONTROL_MASK
or event.state & Gdk.ModifierType.SHIFT_MASK or event.state & Gdk.ModifierType.META_MASK
or event.state & Gdk.ModifierType.MOD1_MASK or self.search_entry.has_focus()
not Gdk.KEY_0 <= event.keyval <= Gdk.KEY_z
or event.state & Gdk.ModifierType.CONTROL_MASK
or event.state & Gdk.ModifierType.SHIFT_MASK
or event.state & Gdk.ModifierType.META_MASK
or event.state & Gdk.ModifierType.MOD1_MASK
or self.search_entry.has_focus()
):
return Gtk.ApplicationWindow.do_key_press_event(self, event)
self.search_entry.grab_focus()
@ -735,10 +727,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
setting_key = "icon_type_%sview" % self.current_view_type
if self.service and self.service.id != "lutris":
setting_key += "_%s" % self.service.id
self.icon_type = settings.read_setting(
setting_key,
default=default_icon_types.get(setting_key, "")
)
self.icon_type = settings.read_setting(setting_key, default=default_icon_types.get(setting_key, ""))
return self.icon_type
def save_icon_type(self, icon_type):
@ -762,8 +751,7 @@ class LutrisWindow(Gtk.ApplicationWindow,
self.game_store = GameStore(self.service, self.service_media)
if view_type == "grid":
self.current_view = GameGridView(
self.game_store,
hide_text=settings.read_bool_setting("hide_text_under_icons")
self.game_store, hide_text=settings.read_bool_setting("hide_text_under_icons")
)
else:
self.current_view = GameListView(self.game_store)
@ -804,9 +792,8 @@ class LutrisWindow(Gtk.ApplicationWindow,
def update_view_settings(self):
if self.current_view and self.current_view_type == "grid":
show_badges = settings.read_setting("hide_badges_on_icons") != 'True'
self.current_view.show_badges = show_badges and not bool(
self.filters.get("platform"))
show_badges = settings.read_setting("hide_badges_on_icons") != "True"
self.current_view.show_badges = show_badges and not bool(self.filters.get("platform"))
def set_viewtype_icon(self, view_type):
self.viewtype_icon.set_from_icon_name("view-%s-symbolic" % view_type, Gtk.IconSize.BUTTON)
@ -920,14 +907,14 @@ class LutrisWindow(Gtk.ApplicationWindow,
@GtkTemplate.Callback
def on_search_entry_key_press(self, widget, event):
if event.keyval == Gdk.KEY_Down:
if self.current_view_type == 'grid':
self.current_view.select_path(Gtk.TreePath('0')) # needed for gridview only
if self.current_view_type == "grid":
self.current_view.select_path(Gtk.TreePath("0")) # needed for gridview only
# if game_bar is alive at this point it can mess grid item selection up
# for some unknown reason,
# it is safe to close it here, it will be reopened automatically.
if self.game_bar:
self.game_bar.destroy() # for gridview only
self.current_view.set_cursor(Gtk.TreePath('0'), None, False) # needed for both view types
self.current_view.set_cursor(Gtk.TreePath("0"), None, False) # needed for both view types
self.current_view.grab_focus()
@GtkTemplate.Callback
@ -1147,10 +1134,13 @@ class LutrisWindow(Gtk.ApplicationWindow,
AsyncCall(create_runtime_updater, create_runtime_updater_cb)
def install_runtime_component_updates(self, updaters: List[ComponentUpdater],
runtime_updater: RuntimeUpdater,
completion_function: DownloadQueue.CompletionFunction = None,
error_function: DownloadQueue.ErrorFunction = None) -> bool:
def install_runtime_component_updates(
self,
updaters: List[ComponentUpdater],
runtime_updater: RuntimeUpdater,
completion_function: DownloadQueue.CompletionFunction = None,
error_function: DownloadQueue.ErrorFunction = None,
) -> bool:
"""Installs a list of component updates. This displays progress bars
in the sidebar as it installs updates, one at a time."""
@ -1163,10 +1153,13 @@ class LutrisWindow(Gtk.ApplicationWindow,
for updater in updaters:
updater.join()
return queue.start_multiple(install_updates, (u.get_progress for u in updaters),
completion_function=completion_function,
error_function=error_function,
operation_names=operation_names)
return queue.start_multiple(
install_updates,
(u.get_progress for u in updaters),
completion_function=completion_function,
error_function=error_function,
operation_names=operation_names,
)
def _handle_esynclimiterror(error: EsyncLimitError, parent: Gtk.Window) -> None:
@ -1174,7 +1167,8 @@ def _handle_esynclimiterror(error: EsyncLimitError, parent: Gtk.Window) -> None:
"Your limits are not set correctly."
" Please increase them as described here:"
" <a href='https://github.com/lutris/docs/blob/master/HowToEsync.md'>"
"How-to:-Esync (https://github.com/lutris/docs/blob/master/HowToEsync.md)</a>")
"How-to:-Esync (https://github.com/lutris/docs/blob/master/HowToEsync.md)</a>"
)
ErrorDialog(error, message_markup=message, parent=parent)

View File

@ -8,9 +8,21 @@ from gi.repository import Gdk, Gtk, Pango
# Lutris Modules
from lutris import settings
from lutris.gui.views import (
COL_ID, COL_INSTALLED, COL_INSTALLED_AT, COL_INSTALLED_AT_TEXT, COL_LASTPLAYED, COL_LASTPLAYED_TEXT,
COL_MEDIA_PATHS, COL_NAME, COL_PLATFORM, COL_PLAYTIME, COL_PLAYTIME_TEXT, COL_RUNNER_HUMAN_NAME, COL_SORTNAME,
COL_YEAR, COLUMN_NAMES
COL_ID,
COL_INSTALLED,
COL_INSTALLED_AT,
COL_INSTALLED_AT_TEXT,
COL_LASTPLAYED,
COL_LASTPLAYED_TEXT,
COL_MEDIA_PATHS,
COL_NAME,
COL_PLATFORM,
COL_PLAYTIME,
COL_PLAYTIME_TEXT,
COL_RUNNER_HUMAN_NAME,
COL_SORTNAME,
COL_YEAR,
COLUMN_NAMES,
)
from lutris.gui.views.base import GameView
from lutris.gui.views.store import sort_func
@ -31,10 +43,9 @@ class GameListView(Gtk.TreeView, GameView):
# Image column
if settings.SHOW_MEDIA:
self.image_renderer = GridViewCellRendererImage()
self.media_column = Gtk.TreeViewColumn("", self.image_renderer,
media_paths=COL_MEDIA_PATHS,
is_installed=COL_INSTALLED,
game_id=COL_ID)
self.media_column = Gtk.TreeViewColumn(
"", self.image_renderer, media_paths=COL_MEDIA_PATHS, is_installed=COL_INSTALLED, game_id=COL_ID
)
self.media_column.set_reorderable(True)
self.media_column.set_sort_indicator(False)
self.media_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
@ -99,7 +110,7 @@ class GameListView(Gtk.TreeView, GameView):
column.set_visible(is_visible == "True" or always_visible if is_visible else True)
self.append_column(column)
column.connect("notify::width", self.on_column_width_changed)
column.get_button().connect('button-press-event', self.on_column_header_button_pressed)
column.get_button().connect("button-press-event", self.on_column_header_button_pressed)
return column
def set_column_sort(self, col):
@ -169,7 +180,6 @@ class GameListView(Gtk.TreeView, GameView):
class GameListColumnToggleMenu(Gtk.Menu):
def __init__(self, columns):
super().__init__()
self.columns = columns

View File

@ -15,16 +15,14 @@ def download_media(media_urls, service_media):
num_workers = 5
with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor:
future_downloads = {
executor.submit(service_media.download, slug, url): slug
for slug, url in media_urls.items()
if url
executor.submit(service_media.download, slug, url): slug for slug, url in media_urls.items() if url
}
for future in concurrent.futures.as_completed(future_downloads):
slug = future_downloads[future]
try:
path = future.result()
except Exception as ex: # pylint: disable=broad-except
logger.exception('%r failed: %s', slug, ex)
logger.exception("%r failed: %s", slug, ex)
path = None
if system.path_exists(path):
icons[slug] = path

View File

@ -11,9 +11,22 @@ from lutris.gui.views.store_item import StoreItem
from lutris.util.strings import gtk_safe
from . import (
COL_ID, COL_INSTALLED, COL_INSTALLED_AT, COL_INSTALLED_AT_TEXT, COL_LASTPLAYED, COL_LASTPLAYED_TEXT,
COL_MEDIA_PATHS, COL_NAME, COL_PLATFORM, COL_PLAYTIME, COL_PLAYTIME_TEXT, COL_RUNNER, COL_RUNNER_HUMAN_NAME,
COL_SLUG, COL_SORTNAME, COL_YEAR
COL_ID,
COL_INSTALLED,
COL_INSTALLED_AT,
COL_INSTALLED_AT_TEXT,
COL_LASTPLAYED,
COL_LASTPLAYED_TEXT,
COL_MEDIA_PATHS,
COL_NAME,
COL_PLATFORM,
COL_PLAYTIME,
COL_PLAYTIME_TEXT,
COL_RUNNER,
COL_RUNNER_HUMAN_NAME,
COL_SLUG,
COL_SORTNAME,
COL_YEAR,
)
@ -190,19 +203,10 @@ class GameStore(GObject.Object):
db_games = sql.filtered_query(
settings.DB_PATH,
"service_games",
filters=({
"service": self.service_media.service,
"appid": game.appid
})
filters=({"service": self.service_media.service, "appid": game.appid}),
)
else:
db_games = sql.filtered_query(
settings.DB_PATH,
"games",
filters=({
"id": game.id
})
)
db_games = sql.filtered_query(settings.DB_PATH, "games", filters=({"id": game.id}))
for db_game in db_games:
GLib.idle_add(self.update, db_game)

View File

@ -36,8 +36,7 @@ class StoreItem:
appid = self._game_data.get("appid")
service_id = self._game_data.get("service")
if appid and service_id:
self._cached_installed_game_data = games.get_game_for_service(service_id,
appid) or {}
self._cached_installed_game_data = games.get_game_for_service(service_id, appid) or {}
self._cached_installed_game_data_loaded = True
return self._cached_installed_game_data
@ -150,10 +149,7 @@ class StoreItem:
@property
def installed_at_text(self):
"""Date of install (textual representation)"""
return gtk_safe(
time.strftime("%X %x", time.localtime(self.installed_at)) if
self.installed_at else ""
)
return gtk_safe(time.strftime("%X %x", time.localtime(self.installed_at)) if self.installed_at else "")
@property
def lastplayed(self):
@ -163,12 +159,7 @@ class StoreItem:
@property
def lastplayed_text(self):
"""Date of last play (textual representation)"""
return gtk_safe(
time.strftime(
"%X %x",
time.localtime(self.lastplayed)
) if self.lastplayed else ""
)
return gtk_safe(time.strftime("%X %x", time.localtime(self.lastplayed)) if self.lastplayed else "")
@property
def playtime(self):

View File

@ -6,14 +6,18 @@ from math import floor
import gi
gi.require_version('PangoCairo', '1.0')
gi.require_version("PangoCairo", "1.0")
import cairo
from gi.repository import Gdk, GLib, GObject, Gtk, Pango, PangoCairo
from lutris.exceptions import MissingMediaError
from lutris.gui.widgets.utils import (
MEDIA_CACHE_INVALIDATED, get_default_icon_path, get_runtime_icon_path, get_scaled_surface_by_path, get_surface_size
MEDIA_CACHE_INVALIDATED,
get_default_icon_path,
get_runtime_icon_path,
get_scaled_surface_by_path,
get_surface_size,
)
from lutris.services.service_media import resolve_media_path
from lutris.util.path_cache import MISSING_GAMES
@ -233,8 +237,7 @@ class GridViewCellRendererImage(Gtk.CellRenderer):
if not surface:
# The default icon needs to be scaled to fill the cell space.
path = get_default_icon_path((media_width, media_height))
surface = self._get_cached_surface_by_path(widget, path,
preserve_aspect_ratio=False)
surface = self._get_cached_surface_by_path(widget, path, preserve_aspect_ratio=False)
if surface:
media_area = self.get_media_area(surface, cell_area)
self.select_badge_metrics(surface)
@ -325,7 +328,7 @@ class GridViewCellRendererImage(Gtk.CellRenderer):
data = surface.get_data()
offset = (y * stride) + x * 4
pixel = data[offset: offset + 4]
pixel = data[offset : offset + 4]
for channel in pixel:
if channel < 128:
@ -535,5 +538,4 @@ class GridViewCellRendererImage(Gtk.CellRenderer):
def _get_surface_by_path(self, widget, path, size=None, preserve_aspect_ratio=True):
cell_size = size or (self.media_width, self.media_height)
scale_factor = widget.get_scale_factor() if widget else 1
return get_scaled_surface_by_path(path, cell_size, scale_factor,
preserve_aspect_ratio=preserve_aspect_ratio)
return get_scaled_surface_by_path(path, cell_size, scale_factor, preserve_aspect_ratio=preserve_aspect_ratio)

View File

@ -9,13 +9,13 @@ from gettext import gettext as _
from gi.repository import GLib, GObject, Gtk, Pango
from lutris.gui.widgets.utils import open_uri
# Lutris Modules
from lutris.util import system
from lutris.util.linux import LINUX_SYSTEM
class SlugEntry(Gtk.Entry, Gtk.Editable):
def do_insert_text(self, new_text, length, position):
"""Filter inserted characters to only accept alphanumeric and dashes"""
new_text = "".join([c for c in new_text if c.isalnum() or c == "-"]).lower()
@ -25,7 +25,6 @@ class SlugEntry(Gtk.Entry, Gtk.Editable):
class NumberEntry(Gtk.Entry, Gtk.Editable):
def do_insert_text(self, new_text, length, position):
"""Filter inserted characters to only accept numbers"""
new_text = "".join([c for c in new_text if c.isnumeric()])
@ -54,13 +53,9 @@ class FileChooserEntry(Gtk.Box):
warn_if_non_writable_parent=False,
warn_if_ntfs=False,
activates_default=False,
shell_quoting=False
shell_quoting=False,
): # pylint: disable=too-many-arguments
super().__init__(
orientation=Gtk.Orientation.VERTICAL,
spacing=0,
visible=True
)
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=0, visible=True)
self.title = title
self.action = action
self.warn_if_non_empty = warn_if_non_empty
@ -228,27 +223,27 @@ class FileChooserEntry(Gtk.Box):
warning_image.set_from_icon_name("dialog-warning", Gtk.IconSize.DND)
ntfs_box.add(warning_image)
ntfs_label = Gtk.Label(visible=True)
ntfs_label.set_markup(_(
"<b>Warning!</b> The selected path is located on a drive formatted by Windows.\n"
"Games and programs installed on Windows drives <b>don't work</b>."
))
ntfs_label.set_markup(
_(
"<b>Warning!</b> The selected path is located on a drive formatted by Windows.\n"
"Games and programs installed on Windows drives <b>don't work</b>."
)
)
ntfs_box.add(ntfs_label)
self.pack_end(ntfs_box, False, False, 10)
if self.warn_if_non_empty and os.path.exists(path) and os.listdir(path):
non_empty_label = Gtk.Label(visible=True)
non_empty_label.set_markup(_(
"<b>Warning!</b> The selected path "
"contains files. Installation will not work properly."
))
non_empty_label.set_markup(
_("<b>Warning!</b> The selected path " "contains files. Installation will not work properly.")
)
self.pack_end(non_empty_label, False, False, 10)
if self.warn_if_non_writable_parent:
parent = system.get_existing_parent(path)
if parent is not None and not os.access(parent, os.W_OK):
non_writable_destination_label = Gtk.Label(visible=True)
non_writable_destination_label.set_markup(_(
"<b>Warning</b> The destination folder "
"is not writable by the current user."
))
non_writable_destination_label.set_markup(
_("<b>Warning</b> The destination folder " "is not writable by the current user.")
)
self.pack_end(non_writable_destination_label, False, False, 10)
self.open_button.set_sensitive(bool(self.get_open_directory()))
@ -280,8 +275,8 @@ class FileChooserEntry(Gtk.Box):
original_path = self.get_path()
path = original_path.strip("\r\n")
if path.startswith('file:///'):
path = urllib.parse.unquote(path[len('file://'):])
if path.startswith("file:///"):
path = urllib.parse.unquote(path[len("file://") :])
path = os.path.expanduser(path)

View File

@ -172,9 +172,7 @@ class DownloadCollectionProgressBox(Gtk.Box):
self.progressbar.set_fraction(progress)
self.update_speed_and_time()
megabytes = 1024 * 1024
progress_text = _(
"{downloaded} / {size} ({speed:0.2f}MB/s), {time} remaining"
).format(
progress_text = _("{downloaded} / {size} ({speed:0.2f}MB/s), {time} remaining").format(
downloaded=human_size(downloaded_size),
size=human_size(self.full_size),
speed=float(self.avg_speed) / megabytes,

View File

@ -13,9 +13,9 @@ class DownloadProgressBox(Gtk.Box):
"""Progress bar used to monitor a file download."""
__gsignals__ = {
"complete": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, )),
"complete": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,)),
"cancel": (GObject.SignalFlags.RUN_LAST, None, ()),
"error": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, )),
"error": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,)),
}
def __init__(self, params, cancelable=True, downloader=None):
@ -119,9 +119,7 @@ class DownloadProgressBox(Gtk.Box):
return False
self.progressbar.set_fraction(progress)
megabytes = 1024 * 1024
progress_text = _(
"{downloaded} / {size} ({speed:0.2f}MB/s), {time} remaining"
).format(
progress_text = _("{downloaded} / {size} ({speed:0.2f}MB/s), {time} remaining").format(
downloaded=human_size(self.downloader.downloaded_size),
size=human_size(self.downloader.full_size),
speed=float(self.downloader.average_speed) / megabytes,

View File

@ -14,12 +14,15 @@ from lutris.util.strings import gtk_safe
class GameBar(Gtk.Box):
def __init__(self, db_game, application, window):
"""Create the game bar with a database row"""
super().__init__(orientation=Gtk.Orientation.VERTICAL, visible=True,
margin_top=12,
margin_left=12,
margin_bottom=12,
margin_right=12,
spacing=6)
super().__init__(
orientation=Gtk.Orientation.VERTICAL,
visible=True,
margin_top=12,
margin_left=12,
margin_bottom=12,
margin_right=12,
spacing=6,
)
self.application = application
self.window = window
@ -268,10 +271,7 @@ class GameBar(Gtk.Box):
def on_game_state_changed(self, game):
"""Handler called when the game has changed state"""
if (
(self.game.is_db_stored and game.id == self.game.id)
or (self.appid and game.appid == self.appid)
):
if (self.game.is_db_stored and game.id == self.game.id) or (self.appid and game.appid == self.appid):
self.game = game
elif self.game != game:
return True

View File

@ -51,7 +51,7 @@ def _connect_func(builder, obj, signal_name, handler_name, connect_object, flags
if connect_object is None:
extra = ()
else:
extra = (connect_object, )
extra = (connect_object,)
# The handler name refers to an attribute on the template instance,
# so ask GtkBuilder for the template instance
@ -60,8 +60,8 @@ def _connect_func(builder, obj, signal_name, handler_name, connect_object, flags
if template_inst is None: # This should never happen
errmsg = (
"Internal error: cannot find template instance! obj: %s; "
"signal: %s; handler: %s; connect_obj: %s; class: %s" %
(obj, signal_name, handler_name, connect_object, cls)
"signal: %s; handler: %s; connect_obj: %s; class: %s"
% (obj, signal_name, handler_name, connect_object, cls)
)
warnings.warn(errmsg, GtkTemplateWarning)
return
@ -93,7 +93,6 @@ def _register_template(cls, template_bytes):
# Walk the class, find marked callbacks and child attributes
for name in dir(cls):
o = getattr(cls, name, None)
if inspect.ismethod(o):
@ -154,9 +153,9 @@ def _init_template(self, cls, base_init_template):
class _Child:
"""
Assign this to an attribute in your class definition and it will
be replaced with a widget defined in the UI file when init_template
is called
Assign this to an attribute in your class definition and it will
be replaced with a widget defined in the UI file when init_template
is called
"""
__slots__ = []
@ -176,47 +175,47 @@ class _Child:
class _GtkTemplate:
"""
Use this class decorator to signify that a class is a composite
widget which will receive widgets and connect to signals as
defined in a UI template. You must call init_template to
cause the widgets/signals to be initialized from the template::
Use this class decorator to signify that a class is a composite
widget which will receive widgets and connect to signals as
defined in a UI template. You must call init_template to
cause the widgets/signals to be initialized from the template::
@GtkTemplate(ui='foo.ui')
class Foo(Gtk.Box):
@GtkTemplate(ui='foo.ui')
class Foo(Gtk.Box):
def __init__(self):
super().__init__()
self.init_template()
def __init__(self):
super().__init__()
self.init_template()
The 'ui' parameter can either be a file path or a GResource resource
path::
The 'ui' parameter can either be a file path or a GResource resource
path::
@GtkTemplate(ui='/org/example/foo.ui')
class Foo(Gtk.Box):
pass
@GtkTemplate(ui='/org/example/foo.ui')
class Foo(Gtk.Box):
pass
To connect a signal to a method on your instance, do::
To connect a signal to a method on your instance, do::
@GtkTemplate.Callback
def on_thing_happened(self, widget):
pass
@GtkTemplate.Callback
def on_thing_happened(self, widget):
pass
To create a child attribute that is retrieved from your template,
add this to your class definition::
To create a child attribute that is retrieved from your template,
add this to your class definition::
@GtkTemplate(ui='foo.ui')
class Foo(Gtk.Box):
@GtkTemplate(ui='foo.ui')
class Foo(Gtk.Box):
widget = GtkTemplate.Child()
widget = GtkTemplate.Child()
Note: This is implemented as a class decorator, but if it were
included with PyGI I suspect it might be better to do this
in the GObject metaclass (or similar) so that init_template
can be called automatically instead of forcing the user to do it.
Note: This is implemented as a class decorator, but if it were
included with PyGI I suspect it might be better to do this
in the GObject metaclass (or similar) so that init_template
can be called automatically instead of forcing the user to do it.
.. note:: Due to limitations in PyGObject, you may not inherit from
python objects that use the GtkTemplate decorator.
.. note:: Due to limitations in PyGObject, you may not inherit from
python objects that use the GtkTemplate decorator.
"""
__ui_path__ = None
@ -224,8 +223,8 @@ class _GtkTemplate:
@staticmethod
def Callback(f):
"""
Decorator that designates a method to be attached to a signal from
the template
Decorator that designates a method to be attached to a signal from
the template
"""
f._gtk_callback = True # pylint: disable=protected-access
return f
@ -235,15 +234,15 @@ class _GtkTemplate:
@staticmethod
def set_ui_path(*path):
"""
If using file paths instead of resources, call this *before*
loading anything that uses GtkTemplate, or it will fail to load
your template file
If using file paths instead of resources, call this *before*
loading anything that uses GtkTemplate, or it will fail to load
your template file
:param path: one or more path elements, will be joined together
to create the final path
:param path: one or more path elements, will be joined together
to create the final path
TODO: Alternatively, could wait until first class instantiation
before registering templates? Would need a metaclass...
TODO: Alternatively, could wait until first class instantiation
before registering templates? Would need a metaclass...
"""
_GtkTemplate.__ui_path__ = abspath(join(*path)) # pylint: disable=no-value-for-parameter
@ -251,7 +250,6 @@ class _GtkTemplate:
self.ui = ui
def __call__(self, cls):
if not issubclass(cls, Gtk.Widget):
raise TypeError("Can only use @GtkTemplate on Widgets")

View File

@ -51,8 +51,9 @@ class LogTextView(Gtk.TextView):
# Found nothing try from the beginning
if next_occurence is None:
next_occurence = self.props.buffer.get_start_iter(
).forward_search(searched_entry.get_text(), Gtk.TextSearchFlags.CASE_INSENSITIVE, None)
next_occurence = self.props.buffer.get_start_iter().forward_search(
searched_entry.get_text(), Gtk.TextSearchFlags.CASE_INSENSITIVE, None
)
# Highlight if result
if next_occurence is not None:
@ -71,8 +72,9 @@ class LogTextView(Gtk.TextView):
# Found nothing ? Try from the end
if previous_occurence is None:
previous_occurence = self.props.buffer.get_end_iter(
).backward_search(searched_entry.get_text(), Gtk.TextSearchFlags.CASE_INSENSITIVE, None)
previous_occurence = self.props.buffer.get_end_iter().backward_search(
searched_entry.get_text(), Gtk.TextSearchFlags.CASE_INSENSITIVE, None
)
# Highlight if result
if previous_occurence is not None:

View File

@ -19,7 +19,7 @@ class ProgressInfo:
self.has_ended = False
@classmethod
def ended(cls, label_markup: str = "") -> 'ProgressInfo':
def ended(cls, label_markup: str = "") -> "ProgressInfo":
"""Creates a ProgressInfo whose has_ended flag is set, to indicate that
the monitored process is over."""
info = cls(1.0, label_markup=label_markup)
@ -45,22 +45,17 @@ class ProgressBox(Gtk.Box):
This class needs only a function that returns a Progress object, which describes the current
progress and optionally can stop the update."""
ProgressFunction = Callable[[], 'ProgressInfo']
ProgressFunction = Callable[[], "ProgressInfo"]
def __init__(self,
progress_function: ProgressFunction,
**kwargs):
def __init__(self, progress_function: ProgressFunction, **kwargs):
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, no_show_all=True, spacing=6, **kwargs)
self.progress_function = progress_function
self.progress = ProgressInfo(0.0)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, visible=True, spacing=6,
valign=Gtk.Align.CENTER)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, visible=True, spacing=6, valign=Gtk.Align.CENTER)
self.label = Gtk.Label("", visible=False,
wrap=True, ellipsize=Pango.EllipsizeMode.MIDDLE,
xalign=0)
self.label = Gtk.Label("", visible=False, wrap=True, ellipsize=Pango.EllipsizeMode.MIDDLE, xalign=0)
vbox.pack_start(self.label, False, False, 0)
self.progressbar = Gtk.ProgressBar(pulse_step=0.4, visible=True)

View File

@ -2,7 +2,11 @@ from gi.repository import Gtk
from lutris.exceptions import MissingMediaError
from lutris.gui.widgets.utils import (
ICON_SIZE, get_default_icon_path, get_pixbuf_by_path, get_runtime_icon_path, has_stock_icon
ICON_SIZE,
get_default_icon_path,
get_pixbuf_by_path,
get_runtime_icon_path,
has_stock_icon,
)
@ -10,7 +14,8 @@ class ScaledImage(Gtk.Image):
"""This class provides a rather basic feature the GtkImage doesn't offer - the ability
to scale the image rendered. Scaling a pixbuf is not the same thing - that discards
pixel data. This will preserve it on high-DPI displays by scaling only at drawing time."""
__gtype_name__ = 'ScaledImage'
__gtype_name__ = "ScaledImage"
def __init__(self, scale_factor, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -11,7 +11,7 @@ class SearchableCombobox(Gtk.Bin):
"""
__gsignals__ = {
"changed": (GObject.SIGNAL_RUN_FIRST, None, (str, )),
"changed": (GObject.SIGNAL_RUN_FIRST, None, (str,)),
}
def __init__(self, choice_func, initial=None):

View File

@ -32,6 +32,7 @@ SERVICE_INDICES = {name: index for index, name in enumerate(SERVICES.keys())}
class SidebarRow(Gtk.ListBoxRow):
"""A row in the sidebar containing possible action buttons"""
MARGIN = 9
SPACING = 6
@ -124,14 +125,8 @@ class SidebarRow(Gtk.ListBoxRow):
class ServiceSidebarRow(SidebarRow):
def __init__(self, service):
super().__init__(
service.id,
"service",
service.name,
LutrisSidebar.get_sidebar_icon(service.icon)
)
super().__init__(service.id, "service", service.name, LutrisSidebar.get_sidebar_icon(service.icon))
self.service = service
@property
@ -142,12 +137,8 @@ class ServiceSidebarRow(SidebarRow):
"""Return the definition of buttons to be added to the row"""
displayed_buttons = []
if self.service.is_launchable():
displayed_buttons.append(
("media-playback-start-symbolic", _("Run"), self.on_service_run, "run")
)
displayed_buttons.append(
("view-refresh-symbolic", _("Reload"), self.on_refresh_clicked, "refresh")
)
displayed_buttons.append(("media-playback-start-symbolic", _("Run"), self.on_service_run, "run"))
displayed_buttons.append(("view-refresh-symbolic", _("Reload"), self.on_refresh_clicked, "refresh"))
return displayed_buttons
def on_service_run(self, button):
@ -182,7 +173,7 @@ class OnlineServiceSidebarRow(ServiceSidebarRow):
"run": (("media-playback-start-symbolic", _("Run"), self.on_service_run, "run")),
"refresh": ("view-refresh-symbolic", _("Reload"), self.on_refresh_clicked, "refresh"),
"disconnect": ("system-log-out-symbolic", _("Disconnect"), self.on_connect_clicked, "disconnect"),
"connect": ("avatar-default-symbolic", _("Connect"), self.on_connect_clicked, "connect")
"connect": ("avatar-default-symbolic", _("Connect"), self.on_connect_clicked, "connect"),
}
def get_actions(self):
@ -220,12 +211,9 @@ class RunnerSidebarRow(SidebarRow):
return entries
if runner.multiple_versions:
entries.append((
"system-software-install-symbolic",
_("Manage Versions"),
self.on_manage_versions,
"manage-versions"
))
entries.append(
("system-software-install-symbolic", _("Manage Versions"), self.on_manage_versions, "manage-versions")
)
if runner.runnable_alone:
entries.append(("media-playback-start-symbolic", _("Run"), self.on_run_runner, "run"))
entries.append(("emblem-system-symbolic", _("Configure"), self.on_configure_runner, "configure"))
@ -248,29 +236,25 @@ class RunnerSidebarRow(SidebarRow):
"""Manage runner versions"""
runner = self.get_runner()
dlg_title = _("Manage %s versions") % runner.human_name
self.application.show_window(RunnerInstallDialog, title=dlg_title,
runner=runner, parent=self.get_toplevel())
self.application.show_window(RunnerInstallDialog, title=dlg_title, runner=runner, parent=self.get_toplevel())
class CategorySidebarRow(SidebarRow):
def __init__(self, category, application):
super().__init__(
category['name'],
category["name"],
"user_category",
category['name'],
category["name"],
Gtk.Image.new_from_icon_name("folder-symbolic", Gtk.IconSize.MENU),
application=application
application=application,
)
self.category = category
self._sort_name = locale.strxfrm(category['name'])
self._sort_name = locale.strxfrm(category["name"])
def get_actions(self):
"""Return the definition of buttons to be added to the row"""
return [
("applications-system-symbolic", _("Edit Games"), self.on_category_clicked, "manage-category-games")
]
return [("applications-system-symbolic", _("Edit Games"), self.on_category_clicked, "manage-category-games")]
def on_category_clicked(self, button):
self.application.show_window(EditCategoryGamesDialog, category=self.category, parent=self.get_toplevel())
@ -278,13 +262,13 @@ class CategorySidebarRow(SidebarRow):
def __lt__(self, other):
if not isinstance(other, CategorySidebarRow):
raise ValueError('Cannot compare %s to %s' % (self.__class__.__name__, other.__class__.__name__))
raise ValueError("Cannot compare %s to %s" % (self.__class__.__name__, other.__class__.__name__))
return self._sort_name < other._sort_name
def __gt__(self, other):
if not isinstance(other, CategorySidebarRow):
raise ValueError('Cannot compare %s to %s' % (self.__class__.__name__, other.__class__.__name__))
raise ValueError("Cannot compare %s to %s" % (self.__class__.__name__, other.__class__.__name__))
return self._sort_name > other._sort_name
@ -311,7 +295,7 @@ class SidebarHeader(Gtk.Box):
self.show_all()
class DummyRow():
class DummyRow:
"""Dummy class for rows that may not be initialized."""
def show(self):
@ -396,7 +380,7 @@ class LutrisSidebar(Gtk.ListBox):
"all",
"category",
_("Games"),
Gtk.Image.new_from_icon_name("applications-games-symbolic", Gtk.IconSize.MENU)
Gtk.Image.new_from_icon_name("applications-games-symbolic", Gtk.IconSize.MENU),
)
)
@ -405,7 +389,7 @@ class LutrisSidebar(Gtk.ListBox):
"recent",
"dynamic_category",
_("Recent"),
Gtk.Image.new_from_icon_name("document-open-recent-symbolic", Gtk.IconSize.MENU)
Gtk.Image.new_from_icon_name("document-open-recent-symbolic", Gtk.IconSize.MENU),
)
)
@ -414,7 +398,7 @@ class LutrisSidebar(Gtk.ListBox):
"favorite",
"category",
_("Favorites"),
Gtk.Image.new_from_icon_name("favorite-symbolic", Gtk.IconSize.MENU)
Gtk.Image.new_from_icon_name("favorite-symbolic", Gtk.IconSize.MENU),
)
)
@ -422,7 +406,7 @@ class LutrisSidebar(Gtk.ListBox):
".hidden",
"category",
_("Hidden"),
Gtk.Image.new_from_icon_name("action-unavailable-symbolic", Gtk.IconSize.MENU)
Gtk.Image.new_from_icon_name("action-unavailable-symbolic", Gtk.IconSize.MENU),
)
self.add(self.hidden_row)
@ -430,7 +414,7 @@ class LutrisSidebar(Gtk.ListBox):
"missing",
"dynamic_category",
_("Missing"),
Gtk.Image.new_from_icon_name("dialog-warning-symbolic", Gtk.IconSize.MENU)
Gtk.Image.new_from_icon_name("dialog-warning-symbolic", Gtk.IconSize.MENU),
)
self.add(self.missing_row)
@ -438,7 +422,7 @@ class LutrisSidebar(Gtk.ListBox):
"running",
"dynamic_category",
_("Running"),
Gtk.Image.new_from_icon_name("media-playback-start-symbolic", Gtk.IconSize.MENU)
Gtk.Image.new_from_icon_name("media-playback-start-symbolic", Gtk.IconSize.MENU),
)
# I wanted this to be on top but it really messes with the headers when showing/hiding the row.
self.add(self.running_row)
@ -481,7 +465,8 @@ class LutrisSidebar(Gtk.ListBox):
if runner_name not in self.runner_visibility_cache:
runner_config = LutrisConfig(runner_slug=row.id)
self.runner_visibility_cache[runner_name] = runner_config.runner_config.get(
"visible_in_side_panel", True)
"visible_in_side_panel", True
)
return self.runner_visibility_cache[runner_name]
if not row or not row.id or row.type in ("category", "dynamic_category"):
@ -581,7 +566,7 @@ class LutrisSidebar(Gtk.ListBox):
"runner",
runner.human_name,
self.get_sidebar_icon(icon_name),
application=self.application
application=self.application,
)
self.runner_rows[runner_name] = runner_row
insert_row(runner_row)
@ -590,11 +575,7 @@ class LutrisSidebar(Gtk.ListBox):
if platform not in self.platform_rows:
icon_name = platform.lower().replace(" ", "").replace("/", "_") + "-symbolic"
platform_row = SidebarRow(
platform,
"platform",
platform,
self.get_sidebar_icon(icon_name),
application=self.application
platform, "platform", platform, self.get_sidebar_icon(icon_name), application=self.application
)
self.platform_rows[platform] = platform_row
insert_row(platform_row)

View File

@ -9,7 +9,7 @@ from lutris.game import Game
from lutris.util import cache_single
try:
gi.require_version('AppIndicator3', '0.1')
gi.require_version("AppIndicator3", "0.1")
from gi.repository import AppIndicator3 as AppIndicator
APP_INDICATOR_SUPPORTED = True

View File

@ -163,7 +163,8 @@ def get_runtime_icon_path(icon_name):
"icons/hicolor/24x24/apps",
"icons",
"icons/hicolor/scalable/apps",
"icons/hicolor/symbolic/apps"]
"icons/hicolor/symbolic/apps",
]
extensions = [".png", ".svg"]
for search_dir in search_directories:
for ext in extensions:
@ -189,7 +190,7 @@ def convert_to_background(background_path, target_size=(320, 1080)):
coverart = coverart.crop((offset, 0, target_width + offset, image_height))
# Resize canvas of coverart by putting transparent pixels on the bottom
coverart_bg = Image.new('RGBA', (target_width, target_height), (0, 0, 0, 0))
coverart_bg = Image.new("RGBA", (target_width, target_height), (0, 0, 0, 0))
coverart_bg.paste(coverart, (0, 0, target_width, image_height))
# Apply a tint to the base image
@ -197,7 +198,7 @@ def convert_to_background(background_path, target_size=(320, 1080)):
# coverart = Image.blend(coverart, tint, 0.6)
# Paste coverart on transparent image while applying a gradient mask
background = Image.new('RGBA', (target_width, target_height), (0, 0, 0, 0))
background = Image.new("RGBA", (target_width, target_height), (0, 0, 0, 0))
mask = Image.open(os.path.join(datapath.get(), "media/mask.png"))
background.paste(coverart_bg, mask=mask)
@ -230,20 +231,14 @@ def paste_overlay(base_image, overlay_image, position=0.7):
offset_x = int((base_width - overlay_width) / 2)
offset_y = int((base_height - overlay_height) / 2)
base_image.paste(
overlay_image, (
offset_x,
offset_y,
overlay_width + offset_x,
overlay_height + offset_y
),
mask=overlay_image
overlay_image, (offset_x, offset_y, overlay_width + offset_x, overlay_height + offset_y), mask=overlay_image
)
return base_image
def image2pixbuf(image):
"""Converts a PIL Image to a GDK Pixbuf"""
image_array = array.array('B', image.tobytes())
image_array = array.array("B", image.tobytes())
width, height = image.size
return GdkPixbuf.Pixbuf.new_from_data(image_array, GdkPixbuf.Colorspace.RGB, True, 8, width, height, width * 4)

View File

@ -21,7 +21,7 @@ class InstallationKind(enum.Enum):
def read_script(filename):
"""Return scripts from a local file"""
logger.debug("Loading script(s) from %s", filename)
with open(filename, "r", encoding='utf-8') as local_file:
with open(filename, "r", encoding="utf-8") as local_file:
script = yaml.safe_load(local_file.read())
if isinstance(script, list):
return script

View File

@ -71,14 +71,16 @@ class CommandsMixin:
if not param_present:
raise ScriptingError(
_("One of {params} parameter is mandatory for the {cmd} command").format(
params=_(" or ").join(param), cmd=command_name),
params=_(" or ").join(param), cmd=command_name
),
command_data,
)
else:
if param not in command_data:
raise ScriptingError(
_("The {param} parameter is mandatory for the {cmd} command").format(
param=param, cmd=command_name),
param=param, cmd=command_name
),
command_data,
)
@ -107,8 +109,7 @@ class CommandsMixin:
self._check_required_params([("file", "command")], data, "execute")
if "command" in data and "file" in data:
raise ScriptingError(
_("Parameters file and command can't be used "
"at the same time for the execute command"),
_("Parameters file and command can't be used " "at the same time for the execute command"),
data,
)
@ -231,16 +232,20 @@ class CommandsMixin:
requires = data.get("requires")
message = data.get(
"message",
_("Insert or mount game disc and click Autodetect or\n"
"use Browse if the disc is mounted on a non standard location."),
_(
"Insert or mount game disc and click Autodetect or\n"
"use Browse if the disc is mounted on a non standard location."
),
)
message += (
_("\n\nLutris is looking for a mounted disk drive or image \n"
"containing the following file or folder:\n"
"<i>%s</i>") % requires
_(
"\n\nLutris is looking for a mounted disk drive or image \n"
"containing the following file or folder:\n"
"<i>%s</i>"
)
% requires
)
self.interpreter_ui_delegate.begin_disc_prompt(message, requires, self.installer,
self._find_matching_disc)
self.interpreter_ui_delegate.begin_disc_prompt(message, requires, self.installer, self._find_matching_disc)
return "STOP"
def _find_matching_disc(self, _widget, requires, extra_path=None):
@ -369,8 +374,8 @@ class CommandsMixin:
filename = self._substitute(data["file"])
logger.debug("Substituting variables for file %s", filename)
tmp_filename = filename + ".tmp"
with open(filename, "r", encoding='utf-8') as source_file:
with open(tmp_filename, "w", encoding='utf-8') as dest_file:
with open(filename, "r", encoding="utf-8") as source_file:
with open(tmp_filename, "w", encoding="utf-8") as dest_file:
line = "."
while line:
line = source_file.readline()
@ -404,12 +409,8 @@ class CommandsMixin:
if runner_name.startswith("wine"):
data["wine_path"] = self.get_wine_path()
data["prefix"] = data.get("prefix") \
or self.installer.script.get("game", {}).get("prefix") \
or "$GAMEDIR"
data["arch"] = data.get("arch") \
or self.installer.script.get("game", {}).get("arch") \
or WINE_DEFAULT_ARCH
data["prefix"] = data.get("prefix") or self.installer.script.get("game", {}).get("prefix") or "$GAMEDIR"
data["arch"] = data.get("arch") or self.installer.script.get("game", {}).get("arch") or WINE_DEFAULT_ARCH
if task_name == "wineexec":
data["env"] = self.script_env
@ -461,7 +462,7 @@ class CommandsMixin:
if not mode.startswith(("a", "w")):
raise ScriptingError(_("Wrong value for write_file mode: '%s'") % mode)
with open(dest_file_path, mode, encoding='utf-8') as dest_file:
with open(dest_file_path, mode, encoding="utf-8") as dest_file:
dest_file.write(self._substitute(params["content"]))
def write_json(self, params):
@ -480,7 +481,7 @@ class CommandsMixin:
# create an empty file if it doesn't exist
Path(filename).touch(exist_ok=True)
with open(filename, "r+" if merge else "w", encoding='utf-8') as json_file:
with open(filename, "r+" if merge else "w", encoding="utf-8") as json_file:
json_data = {}
if merge:
try:
@ -547,11 +548,7 @@ class CommandsMixin:
return result
def _extract_gog_game(self, file_id):
self.extract({
"src": file_id,
"dst": "$GAMEDIR",
"extractor": "innoextract"
})
self.extract({"src": file_id, "dst": "$GAMEDIR", "extractor": "innoextract"})
app_path = os.path.join(self.target_path, "app")
if system.path_exists(app_path):
for app_content in os.listdir(app_path):
@ -566,18 +563,14 @@ class CommandsMixin:
def _get_scummvm_arguments(self, gog_config_path):
"""Return a ScummVM configuration from the GOG config files"""
with open(gog_config_path, encoding='utf-8') as gog_config_file:
with open(gog_config_path, encoding="utf-8") as gog_config_file:
gog_config = json.loads(gog_config_file.read())
game_tasks = [task for task in gog_config["playTasks"] if task["category"] == "game"]
arguments = game_tasks[0]["arguments"]
game_id = arguments.split()[-1]
arguments = " ".join(arguments.split()[:-1])
base_dir = os.path.dirname(gog_config_path)
return {
"game_id": game_id,
"path": base_dir,
"arguments": arguments
}
return {"game_id": game_id, "path": base_dir, "arguments": arguments}
def autosetup_gog_game(self, file_id, silent=False):
"""Automatically guess the best way to install a GOG game by inspecting its contents.
@ -640,12 +633,7 @@ class CommandsMixin:
if silent:
args += " /SUPPRESSMSGBOXES /VERYSILENT /NOGUI"
self.installer.is_gog = True
return self.task({
"name": "wineexec",
"prefix": "$GAMEDIR",
"executable": file_id,
"args": args
})
return self.task({"name": "wineexec", "prefix": "$GAMEDIR", "executable": file_id, "args": args})
def autosetup_amazon(self, file_and_dir_dict):
files = file_and_dir_dict["files"]
@ -657,7 +645,7 @@ class CommandsMixin:
# move installed files from CACHE to game folder
for file_hash, file in self.game_files.items():
file_dir = os.path.dirname(files[file_hash]['path'])
file_dir = os.path.dirname(files[file_hash]["path"])
self.move({"src": file, "dst": f"$GAMEDIR/drive_c/game/{file_dir}"})
def install_or_extract(self, file_id):
@ -667,15 +655,9 @@ class CommandsMixin:
if runner != "wine":
raise ScriptingError(_("install_or_extract only works with wine!"))
if file_path.endswith(".exe"):
params = {
"name": "wineexec",
"executable": file_id
}
params = {"name": "wineexec", "executable": file_id}
return self.task(params)
slug = self.installer.game_slug
params = {
"file": file_id,
"dst": f"$GAMEDIR/drive_c/{slug}"
}
params = {"file": file_id, "dst": f"$GAMEDIR/drive_c/{slug}"}
return self.extract(params)

View File

@ -47,7 +47,7 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
self.extends = self.script.get("extends")
self.game_id = self.get_game_id()
self.is_gog = False
self.discord_id = installer.get('discord_id')
self.discord_id = installer.get("discord_id")
def get_service(self, initial=None):
if initial:
@ -108,14 +108,9 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
if not self.script.get("installer"):
# No command can affect files
return False
if (
self.script_files
or self.script.get("game", {}).get("gog")
or self.script.get("game", {}).get("prefix")
):
if self.script_files or self.script.get("game", {}).get("gog") or self.script.get("game", {}).get("prefix"):
return True
command_names = [self.interpreter._get_command_name_and_params(c)[0]
for c in self.script.get("installer", [])]
command_names = [self.interpreter._get_command_name_and_params(c)[0] for c in self.script.get("installer", [])]
if "insert_disc" in command_names:
return True
return False
@ -190,10 +185,9 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
else:
# Failed to get the service game, put back a user provided file
logger.debug("Unable to get files from service. Setting %s to manual.", installer_file_id)
files.insert(0, InstallerFile(self.game_slug, installer_file_id, {
"url": installer_file_url,
"filename": ""
}))
files.insert(
0, InstallerFile(self.game_slug, installer_file_id, {"url": installer_file_url, "filename": ""})
)
# Commit changes only at the end; this is more robust in this method is runner
# my two threads concurrently- the GIL can probably save us. It's not desirable
@ -210,9 +204,7 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
self.script["installer"] = []
for extra_file in self.extra_file_paths:
self.script["installer"].append(
{"copy": {"src": extra_file, "dst": "$GAMEDIR/extras"}}
)
self.script["installer"].append({"copy": {"src": extra_file, "dst": "$GAMEDIR/extras"}})
def _substitute_config(self, script_config):
"""Substitute values such as $GAMEDIR in a config dict."""
@ -221,15 +213,12 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
if not isinstance(key, str):
raise ScriptingError(_("Game config key must be a string"), key)
value = script_config[key]
if str(value).lower() == 'true':
if str(value).lower() == "true":
value = True
if str(value).lower() == 'false':
if str(value).lower() == "false":
value = False
if key == "launch_configs":
config[key] = [
{k: self.interpreter._substitute(v) for (k, v) in _conf.items()}
for _conf in value
]
config[key] = [{k: self.interpreter._substitute(v) for (k, v) in _conf.items()} for _conf in value]
elif isinstance(value, list):
config[key] = [self.interpreter._substitute(i) for i in value]
elif isinstance(value, dict):
@ -249,9 +238,7 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
required_game = get_game_by_field(self.requires, field="slug")
if not required_game:
raise ValueError("No game matched '%s' on installer_slug or slug" % self.requires)
base_config = LutrisConfig(
runner_slug=self.runner, game_config_id=required_game["configpath"]
)
base_config = LutrisConfig(runner_slug=self.runner, game_config_id=required_game["configpath"])
config = base_config.game_level
else:
config = {"game": {}}
@ -272,8 +259,7 @@ class LutrisInstaller: # pylint: disable=too-many-instance-attributes
raise ScriptingError(_("Invalid 'game' section"), self.script["game"]) from err
config["game"] = self._substitute_config(config["game"])
if AUTO_ELF_EXE in config["game"].get("exe", ""):
config["game"]["exe"] = find_linux_game_executable(self.interpreter.target_path,
make_executable=True)
config["game"]["exe"] = find_linux_game_executable(self.interpreter.target_path, make_executable=True)
elif AUTO_WIN32_EXE in config["game"].get("exe", ""):
config["game"]["exe"] = find_windows_game_executable(self.interpreter.target_path)

View File

@ -200,11 +200,9 @@ class InstallerFile:
os.makedirs(self.cache_path)
def create_download_progress_box(self):
return DownloadProgressBox({
"url": self.url,
"dest": self.dest_file,
"referer": self.referer
}, downloader=self.downloader)
return DownloadProgressBox(
{"url": self.url, "dest": self.dest_file, "referer": self.referer}, downloader=self.downloader
)
def check_hash(self):
"""Checks the checksum of `file` and compare it to `value`
@ -217,7 +215,7 @@ class InstallerFile:
if not self.checksum or not self.dest_file:
return
try:
hash_type, expected_hash = self.checksum.split(':', 1)
hash_type, expected_hash = self.checksum.split(":", 1)
except ValueError as err:
raise ScriptingError(_("Invalid checksum, expected format (type:hash) "), self.checksum) from err
@ -261,10 +259,7 @@ class InstallerFile:
def remove_previous(self):
"""Remove file at already at destination, prior to starting the download."""
if (
not self.uses_pga_cache()
and system.path_exists(self.dest_file)
):
if not self.uses_pga_cache() and system.path_exists(self.dest_file):
# If we've previously downloaded a directory, we'll need to get rid of it
# to download a file now. Since we are not using the cache, we don't keep
# these files anyway - so it should be safe to just nuke and pave all this.

View File

@ -13,7 +13,7 @@ AMAZON_DOMAIN = "a2z.com"
class InstallerFileCollection:
"""Representation of a collection of files in the `files` sections of an installer.
Store files in a folder"""
Store files in a folder"""
def __init__(self, game_slug, file_id, files_list, dest_file=None):
self.game_slug = game_slug

View File

@ -87,9 +87,7 @@ class ScriptInterpreter(GObject.Object, CommandsMixin):
self.service = self.installer.service
script_errors = self.installer.get_errors()
if script_errors:
raise ScriptingError(
_("Invalid script: \n{}").format("\n".join(script_errors)), self.installer.script
)
raise ScriptingError(_("Invalid script: \n{}").format("\n".join(script_errors)), self.installer.script)
self._check_binary_dependencies()
self._check_dependency()
@ -135,8 +133,8 @@ class ScriptInterpreter(GObject.Object, CommandsMixin):
variable as set for the game during the install process.
"""
return {
key: self._substitute(value) for key, value in
self.installer.script.get('system', {}).get('env', {}).items()
key: self._substitute(value)
for key, value in self.installer.script.get("system", {}).get("env", {}).items()
}
@staticmethod
@ -161,8 +159,7 @@ class ScriptInterpreter(GObject.Object, CommandsMixin):
for dependency in binary_dependencies:
if isinstance(dependency, tuple):
installed_binaries = {
dependency_option: system.can_find_executable(dependency_option)
for dependency_option in dependency
dependency_option: system.can_find_executable(dependency_option) for dependency_option in dependency
}
if not any(installed_binaries.values()):
raise ScriptingError(_("This installer requires %s on your system") % _(" or ").join(dependency))
@ -377,13 +374,16 @@ class ScriptInterpreter(GObject.Object, CommandsMixin):
and not os.path.isfile(path)
and self.installer.runner not in ("web", "browser")
):
status = _(
"The executable at path %s can't be found, please check the destination folder.\n"
"Some parts of the installation process may have not completed successfully."
) % path
status = (
_(
"The executable at path %s can't be found, please check the destination folder.\n"
"Some parts of the installation process may have not completed successfully."
)
% path
)
logger.warning("No executable found at specified location %s", path)
else:
status = (self.installer.script.get("install_complete_text") or _("Installation completed!"))
status = self.installer.script.get("install_complete_text") or _("Installation completed!")
AsyncCall(download_lutris_media, None, self.installer.game_slug)
self.interpreter_ui_delegate.report_finished(game_id, status)

View File

@ -17,8 +17,8 @@ class SteamInstaller(GObject.Object):
"""Handles installation of Steam games"""
__gsignals__ = {
"steam-game-installed": (GObject.SIGNAL_RUN_FIRST, None, (str, )),
"steam-state-changed": (GObject.SIGNAL_RUN_FIRST, None, (str, )),
"steam-game-installed": (GObject.SIGNAL_RUN_FIRST, None, (str,)),
"steam-state-changed": (GObject.SIGNAL_RUN_FIRST, None, (str,)),
}
def __init__(self, steam_uri, file_id):
@ -86,16 +86,12 @@ class SteamInstaller(GObject.Object):
if not data_path or not os.path.exists(data_path):
logger.info("No path found for Steam game %s", self.appid)
return ""
return os.path.abspath(
os.path.join(data_path, self.steam_rel_path)
)
return os.path.abspath(os.path.join(data_path, self.steam_rel_path))
def _monitor_steam_game_install(self):
if self.cancelled:
return False
states = get_app_state_log(
self.runner.steam_data_dir, self.appid, self.install_start_time
)
states = get_app_state_log(self.runner.steam_data_dir, self.appid, self.install_start_time)
if states and states != self.prev_states:
self.state = states[-1].split(",")[-1]
logger.debug("Steam installation status: %s", states)

View File

@ -7,14 +7,20 @@ MIGRATION_VERSION = 14 # Never decrease this number
# Replace deprecated migrations with empty lists
MIGRATIONS = [
[], [], [], [], [], [], [],
[],
[],
[],
[],
[],
[],
[],
["mess_to_mame"],
["migrate_hidden_ids"],
["migrate_steam_appids"],
["migrate_banners"],
["retrieve_discord_appids"],
["migrate_sortname"],
["migrate_hidden_category"]
["migrate_hidden_category"],
]

View File

@ -20,4 +20,4 @@ def migrate():
for game_id in game_ids:
game = Game(game_id)
game.mark_as_hidden(True)
settings.write_setting("library_ignores", '', section="lutris")
settings.write_setting("library_ignores", "", section="lutris")

View File

@ -8,14 +8,9 @@ from lutris.util.log import logger
def migrate():
"""Add blank sortname field to games that do not yet have one"""
logger.info("Adding blank sortname field to database")
slugs_to_update = [game['slug'] for game in get_games()]
slugs_to_update = [game["slug"] for game in get_games()]
games = get_api_games(slugs_to_update)
for game in games:
if 'sortname' not in game.keys() or game['sortname'] is None:
sql.db_update(
settings.DB_PATH,
"games",
{"sortname": ""},
{"slug": game['slug']}
)
logger.info("Added blank sortname for %s", game['name'])
if "sortname" not in game.keys() or game["sortname"] is None:
sql.db_update(settings.DB_PATH, "games", {"sortname": ""}, {"slug": game["slug"]})
logger.info("Added blank sortname for %s", game["name"])

View File

@ -12,8 +12,5 @@ def migrate():
continue
print("Migrating Steam game %s" % game["name"])
sql.db_update(
settings.DB_PATH,
"games",
{"service": "steam", "service_id": game["steamid"]},
{"id": game["id"]}
settings.DB_PATH, "games", {"service": "steam", "service_id": game["steamid"]}, {"id": game["id"]}
)

View File

@ -10,17 +10,12 @@ def migrate():
"""
logger.info("Updating Games Discord APP ID's")
# Get Slugs from all games
slugs_to_update = [game['slug'] for game in get_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']:
if not game["discord_id"]:
continue
sql.db_update(
settings.DB_PATH,
"games",
{"discord_id": game['discord_id']},
{"slug": game['slug']}
)
logger.info("Updated %s", game['name'])
sql.db_update(settings.DB_PATH, "games", {"discord_id": game["discord_id"]}, {"slug": game["slug"]})
logger.info("Updated %s", game["name"])

View File

@ -77,8 +77,7 @@ def get_launch_parameters(runner, gameplay_info):
game_ld_library_path = gameplay_info.get("ld_library_path")
if game_ld_library_path:
ld_library_path = env.get("LD_LIBRARY_PATH")
env["LD_LIBRARY_PATH"] = os.pathsep.join(filter(None, [
game_ld_library_path, ld_library_path]))
env["LD_LIBRARY_PATH"] = os.pathsep.join(filter(None, [game_ld_library_path, ld_library_path]))
# Feral gamemode
gamemode = system_config.get("gamemode") and LINUX_SYSTEM.gamemode_available()
@ -169,7 +168,7 @@ def export_bash_script(runner, gameplay_info, script_path):
script_content += "\n# Command\n"
script_content += " ".join([shlex.quote(c) for c in command])
with open(script_path, "w", encoding='utf-8') as script_file:
with open(script_path, "w", encoding="utf-8") as script_file:
script_file.write(script_content)
os.chmod(script_path, os.stat(script_path).st_mode | stat.S_IEXEC)

View File

@ -85,7 +85,6 @@ class atari800(Runner):
]
def install(self, install_ui_delegate, version=None, callback=None):
def on_runner_installed(*_args):
config_path = system.create_folder("~/.atari800")
bios_archive = os.path.join(config_path, "atari800-bioses.zip")
@ -103,7 +102,7 @@ class atari800(Runner):
super().install(install_ui_delegate, version, on_runner_installed)
def find_good_bioses(self, bios_path):
""" Check for correct bios files """
"""Check for correct bios files"""
good_bios = {}
for filename in os.listdir(bios_path):
real_hash = system.get_md5_hash(os.path.join(bios_path, filename))

View File

@ -2,6 +2,7 @@
from gettext import gettext as _
from lutris.exceptions import DirectoryNotFoundError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system
@ -21,7 +22,8 @@ class cemu(Runner):
"label": _("Game directory"),
"help": _(
"The directory in which the game lives. "
"If installed into Cemu, this will be in the mlc directory, such as mlc/usr/title/00050000/101c9500."),
"If installed into Cemu, this will be in the mlc directory, such as mlc/usr/title/00050000/101c9500."
),
}
]
runner_options = [
@ -30,23 +32,27 @@ class cemu(Runner):
"label": _("Fullscreen"),
"type": "bool",
"default": True,
}, {
},
{
"option": "mlc",
"label": _("Custom mlc folder location"),
"type": "directory_chooser",
}, {
},
{
"option": "ud",
"label": _("Render in upside down mode"),
"type": "bool",
"default": False,
"advanced": True,
}, {
},
{
"option": "nsight",
"label": _("NSight debugging options"),
"type": "bool",
"default": False,
"advanced": True,
}, {
},
{
"option": "legacy",
"label": _("Intel legacy graphics mode"),
"type": "bool",

View File

@ -41,7 +41,7 @@ def dosexec(config_file=None, executable=None, args=None, close_on_exit=True, wo
def makeconfig(path, drives, commands):
system.create_folder(os.path.dirname(path))
with open(path, "w", encoding='utf-8') as config_file:
with open(path, "w", encoding="utf-8") as config_file:
config_file.write("[autoexec]\n")
for drive in drives:
config_file.write('mount {} "{}"\n'.format(drive, drives[drive]))

View File

@ -14,8 +14,13 @@ from lutris.util.strings import split_arguments
from lutris.util.wine.cabinstall import CabInstaller
from lutris.util.wine.prefix import WinePrefixManager
from lutris.util.wine.wine import (
WINE_DEFAULT_ARCH, WINE_DIR, detect_arch, get_overrides_env, get_real_executable, is_installed_systemwide,
is_prefix_directory
WINE_DEFAULT_ARCH,
WINE_DIR,
detect_arch,
get_overrides_env,
get_real_executable,
is_installed_systemwide,
is_prefix_directory,
)
@ -41,7 +46,7 @@ def set_regedit(
}
# Make temporary reg file
reg_path = os.path.join(settings.CACHE_DIR, "winekeys.reg")
with open(reg_path, "w", encoding='utf-8') as reg_file:
with open(reg_path, "w", encoding="utf-8") as reg_file:
reg_file.write('REGEDIT4\n\n[%s]\n"%s"=%s\n' % (path, key, formatted_value[type]))
logger.debug("Setting [%s]:%s=%s", path, key, formatted_value[type])
set_regedit_file(reg_path, wine_path=wine_path, prefix=prefix, arch=arch)
@ -79,13 +84,7 @@ def delete_registry_key(key, wine_path=None, prefix=None, arch=WINE_DEFAULT_ARCH
def create_prefix( # noqa: C901
prefix,
wine_path=None,
arch=WINE_DEFAULT_ARCH,
overrides=None,
install_gecko=None,
install_mono=None,
runner=None
prefix, wine_path=None, arch=WINE_DEFAULT_ARCH, overrides=None, install_gecko=None, install_mono=None, runner=None
):
"""Create a new Wine prefix."""
# pylint: disable=too-many-locals
@ -114,12 +113,11 @@ def create_prefix( # noqa: C901
wine_path = runner.get_executable()
logger.info("Winepath: %s", wine_path)
if ('Proton' not in wine_path) or ('lutris' in wine_path and 'Proton' in wine_path):
if ("Proton" not in wine_path) or ("lutris" in wine_path and "Proton" in wine_path):
wineboot_path = os.path.join(os.path.dirname(wine_path), "wineboot")
if not system.path_exists(wineboot_path):
logger.error(
"No wineboot executable found in %s, "
"your wine installation is most likely broken",
"No wineboot executable found in %s, " "your wine installation is most likely broken",
wine_path,
)
return
@ -139,7 +137,7 @@ def create_prefix( # noqa: C901
wineenv["WINE_SKIP_MONO_INSTALLATION"] = "1"
overrides["mscoree"] = "disabled"
if ('Proton' not in wine_path) or ('lutris' in wine_path and 'Proton' in wine_path):
if ("Proton" not in wine_path) or ("lutris" in wine_path and "Proton" in wine_path):
system.execute([wineboot_path], env=wineenv)
for loop_index in range(1000):
time.sleep(0.5)
@ -233,7 +231,7 @@ def wineexec( # noqa: C901
disable_runtime=False,
env=None,
overrides=None,
runner=None
runner=None,
):
"""
Execute a Wine command.
@ -316,7 +314,7 @@ def wineexec( # noqa: C901
if overrides:
wineenv["WINEDLLOVERRIDES"] = get_overrides_env(overrides)
if 'Proton' in wine_path:
if "Proton" in wine_path:
# TODO: Determine and insert GAMEID and STORE
wineenv["GAMEID"] = "ulwgl-foo"
wineenv["PROTONPATH"] = os.path.abspath(os.path.join(os.path.dirname(wine_path), "../../"))
@ -325,7 +323,7 @@ def wineexec( # noqa: C901
baseenv.update(wineenv)
baseenv.update(env)
if 'Proton' in wine_path:
if "Proton" in wine_path:
ulwgl_path = os.path.join(os.path.join(settings.RUNTIME_DIR, "ulwgl"), "ulwgl-run")
wine_path = ulwgl_path
@ -385,7 +383,7 @@ def winetricks(
env=None,
disable_runtime=False,
system_winetricks=False,
runner=None
runner=None,
):
"""Execute winetricks."""
winetricks_path, working_dir, env = find_winetricks(env, system_winetricks)
@ -397,7 +395,7 @@ def winetricks(
runner = import_runner("wine")()
winetricks_wine = runner.get_executable()
# We only need to perform winetricks if not using ulwgl/proton. ulwgl uses protonfixes
if ('Proton' not in wine_path) or ('lutris' in wine_path and 'Proton' in wine_path):
if ("Proton" not in wine_path) or ("lutris" in wine_path and "Proton" in wine_path):
if arch not in ("win32", "win64"):
arch = detect_arch(prefix, winetricks_wine)
args = app
@ -415,7 +413,7 @@ def winetricks(
config=config,
env=env,
disable_runtime=disable_runtime,
runner=runner
runner=runner,
)
@ -435,7 +433,7 @@ def winecfg(wine_path=None, prefix=None, arch=WINE_DEFAULT_ARCH, config=None, en
config=config,
env=env,
include_processes=["winecfg.exe"],
runner=runner
runner=runner,
)

View File

@ -2,6 +2,7 @@
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system
@ -13,7 +14,9 @@ class dolphin(Runner):
description = _("GameCube and Wii emulator")
human_name = _("Dolphin")
platforms = PLATFORMS
require_libs = ["libOpenGL.so.0", ]
require_libs = [
"libOpenGL.so.0",
]
runnable_alone = True
runner_executable = "dolphin/dolphin-emu"
flatpak_id = "org.DolphinEmu.dolphin-emu"

View File

@ -5,6 +5,7 @@ from gettext import gettext as _
from lutris import settings
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.commands.dosbox import dosexec, makeconfig # NOQA pylint: disable=unused-import
from lutris.runners.runner import Runner
@ -102,9 +103,7 @@ class dosbox(Runner):
def get_run_data(self):
env = self.get_env()
env["LD_LIBRARY_PATH"] = os.pathsep.join(filter(None, [
self.libs_dir,
env.get("LD_LIBRARY_PATH")]))
env["LD_LIBRARY_PATH"] = os.pathsep.join(filter(None, [self.libs_dir, env.get("LD_LIBRARY_PATH")]))
return {"env": env, "command": self.get_command()}
@property

View File

@ -3,6 +3,7 @@ from gettext import gettext as _
from os import path
from lutris.exceptions import DirectoryNotFoundError, GameConfigError, MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
@ -21,7 +22,7 @@ class easyrpg(Runner):
"option": "project_path",
"type": "directory_chooser",
"label": _("Game directory"),
"help": _("Select the directory of the game. <b>(required)</b>")
"help": _("Select the directory of the game. <b>(required)</b>"),
},
{
"option": "encoding",
@ -49,7 +50,7 @@ class easyrpg(Runner):
(_("Baltic"), "1257"),
(_("Thai"), "874"),
],
"default": ""
"default": "",
},
{
"option": "engine",
@ -66,7 +67,7 @@ class easyrpg(Runner):
(_("RPG Maker 2003 engine (v1.05 - v1.09a)"), "rpg2k3v105"),
(_("RPG Maker 2003 (English release) engine"), "rpg2k3e"),
],
"default": ""
"default": "",
},
{
"option": "patch",
@ -76,23 +77,21 @@ class easyrpg(Runner):
"help": _(
"Instead of autodetecting patches used by this game, force emulation of certain patches.\n"
"\nAvailable patches:\n"
"<b>common-this</b>: \"This Event\" in common events"
'<b>common-this</b>: "This Event" in common events'
"<b>dynrpg</b>: DynRPG patch by Cherry"
"<b>key-patch</b>: Key Patch by Ineluki"
"<b>maniac</b>: Maniac Patch by BingShan"
"<b>pic-unlock</b>: Pictures are not blocked by messages"
"<b>rpg2k3-cmds</b>: Support all RPG Maker 2003 event commands in any version of the engine"
"\n\nYou can provide multiple patches or use 'none' to disable all engine patches."
)
),
},
{
"option": "language",
"type": "string",
"advanced": True,
"label": _("Language"),
"help": _(
"Load the game translation in the language/LANG directory."
),
"help": _("Load the game translation in the language/LANG directory."),
},
{
"option": "save_path",
@ -102,33 +101,30 @@ class easyrpg(Runner):
"help": _(
"Instead of storing save files in the game directory they are stored in the specified path. "
"The directory must exist."
)
),
},
{
"option": "new_game",
"type": "bool",
"label": _("New game"),
"help": _("Skip the title scene and start a new game directly."),
"default": False
"default": False,
},
{
"option": "load_game_id",
"type": "range",
"label": _("Load game ID"),
"help": _(
"Skip the title scene and load SaveXX.lsd.\n"
"Set to 0 to disable."
),
"help": _("Skip the title scene and load SaveXX.lsd.\n" "Set to 0 to disable."),
"min": 0,
"max": 99,
"default": 0
"default": 0,
},
{
"option": "record_input",
"type": "file",
"advanced": True,
"label": _("Record input"),
"help": _("Records all button input to the specified log file.")
"help": _("Records all button input to the specified log file."),
},
{
"option": "replay_input",
@ -139,7 +135,7 @@ class easyrpg(Runner):
"Replays button input from the specified log file, as generated by 'Record input'.\n"
"If the RNG seed and the state of the save file directory is also the same as it was "
"when the log was recorded, this should reproduce an identical run to the one recorded."
)
),
},
{
"option": "test_play",
@ -148,7 +144,7 @@ class easyrpg(Runner):
"section": _("Debug"),
"label": _("Test play"),
"help": _("Enable TestPlay (debug) mode."),
"default": False
"default": False,
},
{
"option": "hide_title",
@ -157,7 +153,7 @@ class easyrpg(Runner):
"section": _("Debug"),
"label": _("Hide title"),
"help": _("Hide the title background image and center the command menu."),
"default": False
"default": False,
},
{
"option": "start_map_id",
@ -172,7 +168,7 @@ class easyrpg(Runner):
),
"min": 0,
"max": 9999,
"default": 0
"default": 0,
},
{
"option": "start_position",
@ -184,7 +180,7 @@ class easyrpg(Runner):
"Overwrite the party start position and move the party to the specified position.\n"
"Provide two numbers separated by a space.\n\n"
"Incompatible with 'Load game ID'."
)
),
},
{
"option": "start_party",
@ -196,7 +192,7 @@ class easyrpg(Runner):
"Overwrite the starting party members with the actors with the specified IDs.\n"
"Provide one to four numbers separated by spaces.\n\n"
"Incompatible with 'Load game ID'."
)
),
},
{
"option": "battle_test",
@ -204,8 +200,8 @@ class easyrpg(Runner):
"advanced": True,
"section": _("Debug"),
"label": _("Battle test"),
"help": _("Start a battle test with the specified monster party.")
}
"help": _("Start a battle test with the specified monster party."),
},
]
runner_options = [
@ -227,7 +223,7 @@ class easyrpg(Runner):
(_("RPG_RT+"), "RPG_RT+"),
(_("ATTACK"), "ATTACK"),
],
"default": ""
"default": "",
},
{
"option": "enemyai_algo",
@ -245,7 +241,7 @@ class easyrpg(Runner):
(_("RPG_RT"), "RPG_RT"),
(_("RPG_RT+"), "RPG_RT+"),
],
"default": ""
"default": "",
},
{
"option": "seed",
@ -253,13 +249,10 @@ class easyrpg(Runner):
"advanced": True,
"section": _("Engine"),
"label": _("RNG seed"),
"help": _(
"Seeds the random number generator.\n"
"Use -1 to disable."
),
"help": _("Seeds the random number generator.\n" "Use -1 to disable."),
"min": -1,
"max": 2147483647,
"default": -1
"default": -1,
},
{
"option": "audio",
@ -267,7 +260,7 @@ class easyrpg(Runner):
"section": _("Audio"),
"label": _("Enable audio"),
"help": _("Switch off to disable audio."),
"default": True
"default": True,
},
{
"option": "music_volume",
@ -277,7 +270,7 @@ class easyrpg(Runner):
"help": _("Volume of the background music."),
"min": 0,
"max": 100,
"default": 100
"default": 100,
},
{
"option": "sound_volume",
@ -287,7 +280,7 @@ class easyrpg(Runner):
"help": _("Volume of the sound effects."),
"min": 0,
"max": 100,
"default": 100
"default": 100,
},
{
"option": "soundfont",
@ -295,7 +288,7 @@ class easyrpg(Runner):
"advanced": True,
"section": _("Audio"),
"label": _("Soundfont"),
"help": _("Soundfont in sf2 format to use when playing MIDI files.")
"help": _("Soundfont in sf2 format to use when playing MIDI files."),
},
{
"option": "fullscreen",
@ -303,7 +296,7 @@ class easyrpg(Runner):
"section": _("Graphics"),
"label": _("Fullscreen"),
"help": _("Start in fullscreen mode."),
"default": False
"default": False,
},
{
"option": "game_resolution",
@ -312,15 +305,14 @@ class easyrpg(Runner):
"advanced": True,
"label": _("Game resolution"),
"help": _(
"Force a different game resolution.\n\n"
"This is experimental and can cause glitches or break games!"
"Force a different game resolution.\n\n" "This is experimental and can cause glitches or break games!"
),
"choices": [
(_("320×240 (4:3, Original)"), "original"),
(_("416×240 (16:9, Widescreen)"), "widescreen"),
(_("560×240 (21:9, Ultrawide)"), "ultrawide"),
],
"default": "original"
"default": "original",
},
{
"option": "scaling",
@ -338,7 +330,7 @@ class easyrpg(Runner):
(_("Integer"), "integer"),
(_("Bilinear"), "bilinear"),
],
"default": "bilinear"
"default": "bilinear",
},
{
"option": "stretch",
@ -346,7 +338,7 @@ class easyrpg(Runner):
"section": _("Graphics"),
"label": _("Stretch"),
"help": _("Ignore the aspect ratio and stretch video output to the entire width of the screen."),
"default": False
"default": False,
},
{
"option": "vsync",
@ -354,7 +346,7 @@ class easyrpg(Runner):
"section": _("Graphics"),
"label": _("Enable VSync"),
"help": _("Switch off to disable VSync and use the FPS limit."),
"default": True
"default": True,
},
{
"option": "fps_limit",
@ -368,7 +360,7 @@ class easyrpg(Runner):
),
"min": 0,
"max": 9999,
"default": 60
"default": 60,
},
{
"option": "show_fps",
@ -381,7 +373,7 @@ class easyrpg(Runner):
(_("Fullscreen & title bar"), "on"),
(_("Fullscreen, title bar & window"), "full"),
],
"default": "off"
"default": "off",
},
{
"option": "rtp",
@ -389,34 +381,28 @@ class easyrpg(Runner):
"section": _("Runtime Package"),
"label": _("Enable RTP"),
"help": _("Switch off to disable support for the Runtime Package (RTP)."),
"default": True
"default": True,
},
{
"option": "rpg2k_rtp_path",
"type": "directory_chooser",
"section": _("Runtime Package"),
"label": _("RPG2000 RTP location"),
"help": _(
"Full path to a directory containing an extracted "
"RPG Maker 2000 Run-Time-Package (RTP)."
)
"help": _("Full path to a directory containing an extracted " "RPG Maker 2000 Run-Time-Package (RTP)."),
},
{
"option": "rpg2k3_rtp_path",
"type": "directory_chooser",
"section": _("Runtime Package"),
"label": _("RPG2003 RTP location"),
"help": _(
"Full path to a directory containing an extracted "
"RPG Maker 2003 Run-Time-Package (RTP)."
)
"help": _("Full path to a directory containing an extracted " "RPG Maker 2003 Run-Time-Package (RTP)."),
},
{
"option": "rpg_rtp_path",
"type": "directory_chooser",
"section": _("Runtime Package"),
"label": _("Fallback RTP location"),
"help": _("Full path to a directory containing a combined RTP.")
"help": _("Full path to a directory containing a combined RTP."),
},
]
@ -546,7 +532,7 @@ class easyrpg(Runner):
cmd.extend(("--engine", engine))
patches = self.game_config.get("patches")
if patches == 'none':
if patches == "none":
cmd.append("--no-patch")
elif patches:
cmd.extend(("--patches", *patches.split()))

View File

@ -24,52 +24,44 @@ class flatpak(Runner):
human_name = _("Flatpak")
runnable_alone = False
system_options_override = [{"option": "disable_runtime", "default": True}]
install_locations = {
"system": "var/lib/flatpak/app/",
"user": f"{Path.home()}/.local/share/flatpak/app/"
}
install_locations = {"system": "var/lib/flatpak/app/", "user": f"{Path.home()}/.local/share/flatpak/app/"}
game_options = [
{
"option": "appid",
"type": "string",
"label": _("Application ID"),
"help": _("The application's unique three-part identifier (tld.domain.app).")
"help": _("The application's unique three-part identifier (tld.domain.app)."),
},
{
"option": "arch",
"type": "string",
"label": _("Architecture"),
"help": _("The architecture to run. "
"See flatpak --supported-arches for architectures supported by the host."),
"advanced": True
},
{
"option": "branch",
"type": "string",
"label": _("Branch"),
"help": _("The branch to use."),
"advanced": True
"help": _(
"The architecture to run. " "See flatpak --supported-arches for architectures supported by the host."
),
"advanced": True,
},
{"option": "branch", "type": "string", "label": _("Branch"), "help": _("The branch to use."), "advanced": True},
{
"option": "install_type",
"type": "string",
"label": _("Install type"),
"help": _("Can be system or user."),
"advanced": True
"advanced": True,
},
{
"option": "args",
"type": "string",
"label": _("Args"),
"help": _("Arguments to be passed to the application.")
"help": _("Arguments to be passed to the application."),
},
{
"option": "fcommand",
"type": "string",
"label": _("Command"),
"help": _("The command to run instead of the one listed in the application metadata."),
"advanced": True
"advanced": True,
},
{
"option": "working_dir",
@ -77,17 +69,18 @@ class flatpak(Runner):
"label": _("Working directory"),
"warn_if_non_writable_parent": True,
"help": _("The directory to run the command in. Note that this must be a directory inside the sandbox."),
"advanced": True
"advanced": True,
},
{
"option": "env_vars",
"type": "string",
"label": _("Environment variables"),
"help": _("Set an environment variable in the application. "
"This overrides to the Context section from the application metadata."),
"advanced": True
}
"help": _(
"Set an environment variable in the application. "
"This overrides to the Context section from the application metadata."
),
"advanced": True,
},
]
def is_installed(self, flatpak_allowed: bool = True) -> bool:
@ -101,8 +94,10 @@ class flatpak(Runner):
def install(self, install_ui_delegate, version=None, callback=None):
raise NonInstallableRunnerError(
_("Flatpak installation is not handled by Lutris.\n"
"Install Flatpak with the package provided by your distribution.")
_(
"Flatpak installation is not handled by Lutris.\n"
"Install Flatpak with the package provided by your distribution."
)
)
def can_uninstall(self):
@ -127,7 +122,7 @@ class flatpak(Runner):
[self.get_executable(), f"uninstall --app --noninteractive {app_id}"],
runner=self,
env=self.get_env(),
title=f"Uninstalling Flatpak App: {app_id}"
title=f"Uninstalling Flatpak App: {app_id}",
)
command.start()
@ -145,8 +140,9 @@ class flatpak(Runner):
raise GameConfigError(_("No application specified."))
if appid.count(".") < 2:
raise GameConfigError(_("Application ID is not specified in correct format."
"Must be something like: tld.domain.app"))
raise GameConfigError(
_("Application ID is not specified in correct format." "Must be something like: tld.domain.app")
)
if any(x in appid for x in ("--", "/")):
raise GameConfigError(_("Application ID field must not contain options or arguments."))

View File

@ -14,53 +14,21 @@ AMIGAS = {
"891e9a547772fe0c6c19b610baf8bc4ea7fcb785",
"c39bd9094d4e5f4e28c1411f3086950406062e87",
"90933936cce43ca9bc6bf375662c076b27e3c458",
]
},
"A500+": {
"name": _("Amiga 500+"),
"bios_sha1": [
"c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"
]
},
"A600": {
"name": _("Amiga 600"),
"bios_sha1": [
"02843c4253bbd29aba535b0aa3bd9a85034ecde4"
]
},
"A1200": {
"name": _("Amiga 1200"),
"bios_sha1": [
"e21545723fe8374e91342617604f1b3d703094f1"
]
},
"A3000": {
"name": _("Amiga 3000"),
"bios_sha1": [
"f8e210d72b4c4853e0c9b85d223ba20e3d1b36ee"
]
],
},
"A500+": {"name": _("Amiga 500+"), "bios_sha1": ["c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"]},
"A600": {"name": _("Amiga 600"), "bios_sha1": ["02843c4253bbd29aba535b0aa3bd9a85034ecde4"]},
"A1200": {"name": _("Amiga 1200"), "bios_sha1": ["e21545723fe8374e91342617604f1b3d703094f1"]},
"A3000": {"name": _("Amiga 3000"), "bios_sha1": ["f8e210d72b4c4853e0c9b85d223ba20e3d1b36ee"]},
"A4000": {
"name": _("Amiga 4000"),
"bios_sha1": [
"5fe04842d04a489720f0f4bb0e46948199406f49",
"c3c481160866e60d085e436a24db3617ff60b5f9"
]
},
"A1000": {
"name": _("Amiga 1000"),
"bios_sha1": [
"11f9e62cf299f72184835b7b2a70a16333fc0d88"
]
"bios_sha1": ["5fe04842d04a489720f0f4bb0e46948199406f49", "c3c481160866e60d085e436a24db3617ff60b5f9"],
},
"A1000": {"name": _("Amiga 1000"), "bios_sha1": ["11f9e62cf299f72184835b7b2a70a16333fc0d88"]},
"CD32": {
"name": _("Amiga CD32"),
"bios_sha1": [
"3525be8887f79b5929e017b42380a79edfee542d"
],
"bios_ext_sha1": [
"5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"
]
"bios_sha1": ["3525be8887f79b5929e017b42380a79edfee542d"],
"bios_ext_sha1": ["5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"],
},
"CDTV": {
"name": _("Commodore CDTV"),
@ -69,10 +37,8 @@ AMIGAS = {
"c39bd9094d4e5f4e28c1411f3086950406062e87",
"90933936cce43ca9bc6bf375662c076b27e3c458",
],
"bios_ext_sha1": [
"7ba40ffa17e500ed9fed041f3424bd81d9c907be"
]
}
"bios_ext_sha1": ["7ba40ffa17e500ed9fed041f3424bd81d9c907be"],
},
}
@ -239,13 +205,14 @@ class fsuae(Runner):
"label": _("Additionnal floppies"),
"default_path": "game_path",
"help": _("The additional floppy disk image(s)."),
}, {
},
{
"option": "cdrom_image",
"section": _("Media"),
"label": _("CD-ROM image"),
"type": "file",
"help": _("CD-ROM image to use on non CD32/CDTV models")
}
"help": _("CD-ROM image to use on non CD32/CDTV models"),
},
]
runner_options = [
@ -291,8 +258,7 @@ class fsuae(Runner):
"label": _("Scanlines display style"),
"type": "bool",
"default": False,
"help": _("Activates a display filter adding scanlines to imitate "
"the displays of yesteryear."),
"help": _("Activates a display filter adding scanlines to imitate " "the displays of yesteryear."),
},
{
"option": "grafixcard",
@ -305,7 +271,7 @@ class fsuae(Runner):
"help": _(
"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."
)
),
},
{
"option": "grafixmemory",
@ -318,7 +284,7 @@ class fsuae(Runner):
"help": _(
"Override the amount of graphics memory on the graphics card. The 0 MB option is "
"not really valid, but exists for user interface reasons."
)
),
},
{
"option": "cpumodel",
@ -327,9 +293,11 @@ class fsuae(Runner):
"choices": cpumodel_choices,
"default": "auto",
"advanced": True,
"help": _("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 "
"want to use another CPU."),
"help": _(
"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 "
"want to use another CPU."
),
},
{
"option": "fmemory",
@ -347,9 +315,11 @@ class fsuae(Runner):
"choices": zorroiii_choices,
"default": "0",
"advanced": True,
"help": _("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 "
"processor with 32-bit address bus, (use for example the A1200/020 model)."),
"help": _(
"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 "
"processor with 32-bit address bus, (use for example the A1200/020 model)."
),
},
{
"option": "fdvolume",
@ -359,8 +329,7 @@ class fsuae(Runner):
"choices": flsound_choices,
"default": "0",
"advanced": True,
"help": _("Set volume to 0 to disable floppy drive clicks "
"when the drive is empty. Max volume is 100.")
"help": _("Set volume to 0 to disable floppy drive clicks " "when the drive is empty. Max volume is 100."),
},
{
"option": "fdspeed",
@ -375,7 +344,7 @@ class fsuae(Runner):
"For example, you can specify 800 to get an 8x increase in "
"speed. Use 0 to specify turbo mode. Turbo mode means that "
"all floppy operations complete immediately. The default is 100 for most models."
)
),
},
{
"option": "jitcompiler",
@ -390,8 +359,7 @@ class fsuae(Runner):
"type": "bool",
"default": False,
"advanced": True,
"help": _("Automatically uses Feral GameMode daemon if available. "
"Set to true to disable the feature.")
"help": _("Automatically uses Feral GameMode daemon if available. " "Set to true to disable the feature."),
},
{
"option": "govwarning",
@ -399,9 +367,9 @@ class fsuae(Runner):
"type": "bool",
"default": False,
"advanced": True,
"help":
_("Warn if running with a CPU governor other than performance. "
"Set to true to disable the warning.")
"help": _(
"Warn if running with a CPU governor other than performance. " "Set to true to disable the warning."
),
},
{
"option": "bsdsocket",

View File

@ -119,14 +119,14 @@ class hatari(Runner):
]
def install(self, install_ui_delegate, version=None, callback=None):
def on_runner_installed(*args):
bios_path = system.create_folder("~/.hatari/bios")
bios_filename = install_ui_delegate.show_install_file_inquiry(
question=_("Do you want to select an Atari ST BIOS file?"),
title=_("Use BIOS file?"),
message=_("Select a BIOS file"))
message=_("Select a BIOS file"),
)
if bios_filename:
shutil.copy(bios_filename, bios_path)

View File

@ -20,7 +20,7 @@ class JsonRunner(Runner):
super().__init__(config)
if not self.json_path:
raise RuntimeError("Create subclasses of JsonRunner with the json_path attribute set")
with open(self.json_path, encoding='utf-8') as json_file:
with open(self.json_path, encoding="utf-8") as json_file:
self._json_data = json.load(json_file)
self.game_options = self._json_data["game_options"]
@ -69,10 +69,6 @@ def load_json_runners():
if not json_path.endswith(".json"):
continue
runner_name = json_path[:-5]
runner_class = type(
runner_name,
(JsonRunner, ),
{'json_path': os.path.join(json_dir, json_path)}
)
runner_class = type(runner_name, (JsonRunner,), {"json_path": os.path.join(json_dir, json_path)})
json_runners[runner_name] = runner_class
return json_runners

View File

@ -3,6 +3,7 @@ import os
from gettext import gettext as _
from lutris.exceptions import MissingBiosError, MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system
@ -38,12 +39,7 @@ class jzintv(Runner):
"necessary to the emulation."
),
},
{
"option": "fullscreen",
"type": "bool",
"section": _("Graphics"),
"label": _("Fullscreen")
},
{"option": "fullscreen", "type": "bool", "section": _("Graphics"), "label": _("Fullscreen")},
{
"option": "resolution",
"type": "choice",
@ -58,7 +54,7 @@ class jzintv(Runner):
("1680 x 1050", "4"),
("1600 x 1200", "6"),
),
"default": "0"
"default": "0",
},
]

View File

@ -27,15 +27,11 @@ def get_libretro_cores():
# 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,
timeout=5
)
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:
with open(get_default_config_path("info.zip"), "wb") as info_zip:
info_zip.write(req.content)
with ZipFile(get_default_config_path('info.zip'), 'r') as info_zip:
with ZipFile(get_default_config_path("info.zip"), "r") as info_zip:
info_zip.extractall(info_path)
else:
logger.error("Error retrieving libretro info archive from server: %s - %s", req.status_code, req.reason)
@ -74,11 +70,7 @@ class libretro(Runner):
has_runner_versions = True
game_options = [
{
"option": "main_file",
"type": "file",
"label": _("ROM file")
},
{"option": "main_file", "type": "file", "label": _("ROM file")},
{
"option": "core",
"type": "choice",
@ -196,7 +188,7 @@ class libretro(Runner):
# TODO: review later
# Create retroarch.cfg if it doesn't exist.
if not system.path_exists(config_file):
with open(config_file, "w", encoding='utf-8') as f:
with open(config_file, "w", encoding="utf-8") as f:
f.write("# Lutris RetroArch Configuration")
f.close()

View File

@ -137,7 +137,7 @@ class linux(Runner):
working_dir = os.path.expanduser(launch_config.get("working_dir") or self.working_dir)
if "exe" in launch_config:
config_exe = os.path.expanduser(launch_config["exe"] or '')
config_exe = os.path.expanduser(launch_config["exe"] or "")
command.append(self.get_relative_exe(config_exe, working_dir))
elif len(command) == 0:
raise GameConfigError(_("The runner could not find a command or exe to use for this configuration."))

View File

@ -84,7 +84,7 @@ class mame(Runner): # pylint: disable=invalid-name
"type": "choice_with_search",
"label": _("Machine"),
"choices": get_system_choices,
"help": _("The emulated machine.")
"help": _("The emulated machine."),
},
{
"option": "device",
@ -137,8 +137,9 @@ class mame(Runner): # pylint: disable=invalid-name
"type": "string",
"section": _("Autoboot"),
"label": _("Autoboot command"),
"help": _("Autotype this command when the system has started, "
"an enter keypress is automatically added."),
"help": _(
"Autotype this command when the system has started, " "an enter keypress is automatically added."
),
},
{
"option": "autoboot_delay",
@ -147,7 +148,7 @@ class mame(Runner): # pylint: disable=invalid-name
"label": _("Delay before entering autoboot command"),
"min": 0,
"max": 120,
}
},
]
runner_options = [
@ -173,8 +174,7 @@ class mame(Runner): # pylint: disable=invalid-name
"type": "bool",
"section": _("Graphics"),
"label": _("CRT effect ()"),
"help": _("Applies a CRT effect to the screen."
"Requires OpenGL renderer."),
"help": _("Applies a CRT effect to the screen." "Requires OpenGL renderer."),
"default": False,
},
{
@ -196,9 +196,9 @@ class mame(Runner): # pylint: disable=invalid-name
"type": "bool",
"section": _("Graphics"),
"label": _("Wait for VSync"),
"help":
_("Enable waiting for the start of vblank before "
"flipping screens; reduces tearing effects."),
"help": _(
"Enable waiting for the start of vblank before " "flipping screens; reduces tearing effects."
),
"advanced": True,
"default": False,
},
@ -220,8 +220,7 @@ class mame(Runner): # pylint: disable=invalid-name
],
"default": "SCRLOCK",
"advanced": True,
"help": _("Key to switch between Full Keyboard Mode and "
"Partial Keyboard Mode (default: Scroll Lock)"),
"help": _("Key to switch between Full Keyboard Mode and " "Partial Keyboard Mode (default: Scroll Lock)"),
},
]
@ -238,7 +237,6 @@ class mame(Runner): # pylint: disable=invalid-name
return self._platforms
def install(self, install_ui_delegate, version=None, callback=None):
def on_runner_installed(*args):
AsyncCall(write_mame_xml, notify_mame_xml)
@ -259,7 +257,7 @@ class mame(Runner): # pylint: disable=invalid-name
os.makedirs(self.cache_dir, exist_ok=True)
output, error_output = system.execute_with_error(listxml_command, env=env)
if output:
with open(self.xml_path, "w", encoding='utf-8') as xml_file:
with open(self.xml_path, "w", encoding="utf-8") as xml_file:
xml_file.write(output)
logger.info("MAME XML list written to %s", self.xml_path)
else:
@ -288,7 +286,7 @@ class mame(Runner): # pylint: disable=invalid-name
system.execute(
self.get_command() + ["-createconfig", "-inipath", self.config_dir],
env=runtime.get_env(),
cwd=self.working_dir
cwd=self.working_dir,
)
def get_shader_params(self, shader_dir, shaders):
@ -296,11 +294,7 @@ class mame(Runner): # pylint: disable=invalid-name
params = []
shader_path = os.path.join(self.working_dir, "shaders", shader_dir)
for index, shader in enumerate(shaders):
params += [
"-gl_glsl",
"-glsl_shader_mame%s" % index,
os.path.join(shader_path, shader)
]
params += ["-gl_glsl", "-glsl_shader_mame%s" % index, os.path.join(shader_path, shader)]
return params
def play(self):
@ -337,7 +331,7 @@ class mame(Runner): # pylint: disable=invalid-name
rompath = self.runner_config.get("rompath")
rom = os.path.basename(self.game_config.get("main_file"))
if not rompath:
raise GameConfigError(_("The path '%s' is not set. please set it in the options.") % 'rompath')
raise GameConfigError(_("The path '%s' is not set. please set it in the options.") % "rompath")
command += ["-rompath", rompath, rom]
if self.game_config.get("autoboot_command"):

View File

@ -3,6 +3,7 @@ import subprocess
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system
@ -56,9 +57,9 @@ class mednafen(Runner):
"option": "main_file",
"type": "file",
"label": _("ROM file"),
"help":
_("The game data, commonly called a ROM image. \n"
"Mednafen supports GZIP and ZIP compressed ROMs."),
"help": _(
"The game data, commonly called a ROM image. \n" "Mednafen supports GZIP and ZIP compressed ROMs."
),
},
{
"option": "machine",
@ -69,13 +70,7 @@ class mednafen(Runner):
},
]
runner_options = [
{
"option": "fs",
"type": "bool",
"section": _("Graphics"),
"label": _("Fullscreen"),
"default": False
},
{"option": "fs", "type": "bool", "section": _("Graphics"), "label": _("Fullscreen"), "default": False},
{
"option": "stretch",
"type": "choice",
@ -126,7 +121,7 @@ class mednafen(Runner):
("hw:1", "hw:1,0"),
("hw:2", "hw:2,0"),
),
"default": "sexyal-literal-default"
"default": "sexyal-literal-default",
},
{
"option": "dont_map_controllers",
@ -145,7 +140,7 @@ class mednafen(Runner):
return ""
def find_joysticks(self):
""" Detect connected joysticks and return their ids """
"""Detect connected joysticks and return their ids"""
joy_ids = []
if not self.is_installed:
return []
@ -167,14 +162,14 @@ class mednafen(Runner):
for joy in joy_list:
index = joy.find("Unique ID:")
joy_id = joy[index + 11:]
joy_id = joy[index + 11 :]
logger.debug("Joystick found id %s ", joy_id)
joy_ids.append(joy_id)
return joy_ids
@staticmethod
def set_joystick_controls(joy_ids, machine):
""" Setup joystick mappings per machine """
"""Setup joystick mappings per machine"""
# Get the controller mappings
controller_mappings = get_controller_mappings()

View File

@ -29,12 +29,7 @@ class mupen64plus(Runner):
"label": _("Fullscreen"),
"default": True,
},
{
"option": "hideosd",
"type": "bool",
"label": _("Hide OSD"),
"default": True
},
{"option": "hideosd", "type": "bool", "label": _("Hide OSD"), "default": True},
]
@property

View File

@ -3,6 +3,7 @@ import os
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system
@ -85,8 +86,7 @@ class o2em(Runner):
"section": _("Graphics"),
"label": _("Scanlines display style"),
"default": False,
"help": _("Activates a display filter adding scanlines to imitate "
"the displays of yesteryear."),
"help": _("Activates a display filter adding scanlines to imitate " "the displays of yesteryear."),
},
]
@ -99,7 +99,6 @@ class o2em(Runner):
return ""
def install(self, install_ui_delegate, version=None, callback=None):
def on_runner_installed(*args):
if not system.path_exists(self.bios_path):
os.makedirs(self.bios_path)

View File

@ -2,6 +2,7 @@
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system

View File

@ -2,6 +2,7 @@
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system
@ -14,14 +15,10 @@ class osmose(Runner):
runner_executable = "osmose/osmose"
game_options = [
{
"option":
"main_file",
"type":
"file",
"label":
_("ROM file"),
"default_path":
"game_path",
"option": "main_file",
"type": "file",
"label": _("ROM file"),
"default_path": "game_path",
"help": _(
"The game data, commonly called a ROM image.\n"
"Supported formats: SMS and GG files. ZIP compressed "
@ -29,12 +26,14 @@ class osmose(Runner):
),
}
]
runner_options = [{
"option": "fullscreen",
"type": "bool",
"label": _("Fullscreen"),
"default": False,
}]
runner_options = [
{
"option": "fullscreen",
"type": "bool",
"label": _("Fullscreen"),
"default": False,
}
]
def play(self):
"""Run Sega Master System game"""

View File

@ -2,6 +2,7 @@
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system
@ -14,12 +15,14 @@ class pcsx2(Runner):
runnable_alone = True
runner_executable = "pcsx2/PCSX2"
flatpak_id = "net.pcsx2.PCSX2"
game_options = [{
"option": "main_file",
"type": "file",
"label": _("ISO file"),
"default_path": "game_path",
}]
game_options = [
{
"option": "main_file",
"type": "file",
"label": _("ISO file"),
"default_path": "game_path",
}
]
runner_options = [
{
@ -28,18 +31,8 @@ class pcsx2(Runner):
"label": _("Fullscreen"),
"default": False,
},
{
"option": "full_boot",
"type": "bool",
"label": _("Fullboot"),
"default": False
},
{
"option": "nogui",
"type": "bool",
"label": _("No GUI"),
"default": False
}
{"option": "full_boot", "type": "bool", "label": _("Fullboot"), "default": False},
{"option": "nogui", "type": "bool", "label": _("No GUI"), "default": False},
]
# PCSX2 currently uses an AppImage, no need for the runtime.

View File

@ -132,8 +132,7 @@ class pico8(Runner):
return {"command": self.launch_args, "env": self.get_env(os_env=False)}
def is_installed(self, flatpak_allowed: bool = True) -> bool:
"""Checks if pico8 runner is installed and if the pico8 executable available.
"""
"""Checks if pico8 runner is installed and if the pico8 executable available."""
if self.is_native and system.path_exists(self.runner_config.get("runner_executable")):
return True
return system.path_exists(os.path.join(settings.RUNNER_DIR, "pico8/web/player.html"))

View File

@ -21,12 +21,7 @@ class redream(Runner):
}
]
runner_options = [
{
"option": "fs",
"type": "bool",
"section": _("Graphics"),
"label": _("Fullscreen"),
"default": False},
{"option": "fs", "type": "bool", "section": _("Graphics"), "label": _("Fullscreen"), "default": False},
{
"option": "ar",
"type": "choice",
@ -106,16 +101,13 @@ class redream(Runner):
license_filename = install_ui_delegate.show_install_file_inquiry(
question=_("Do you want to select a premium license file?"),
title=_("Use premium version?"),
message=_("Use premium version?"))
message=_("Use premium version?"),
)
if license_filename:
shutil.copy(
license_filename, os.path.join(settings.RUNNER_DIR, "redream")
)
shutil.copy(license_filename, os.path.join(settings.RUNNER_DIR, "redream"))
super().install(
install_ui_delegate, version=version, callback=on_runner_installed
)
super().install(install_ui_delegate, version=version, callback=on_runner_installed)
def play(self):
command = self.get_command()

View File

@ -26,8 +26,7 @@ class reicast(Runner):
"option": "iso",
"type": "file",
"label": _("Disc image file"),
"help": _("The game data.\n"
"Supported formats: ISO, CDI"),
"help": _("The game data.\n" "Supported formats: ISO, CDI"),
}
]
@ -82,6 +81,7 @@ class reicast(Runner):
for mapping_file in os.listdir(mapping_source):
shutil.copy(os.path.join(mapping_source, mapping_file), mapping_path)
system.create_folder("~/.reicast/data")
super().install(install_ui_delegate, version, on_runner_installed)
def get_joypads(self):
@ -92,7 +92,7 @@ class reicast(Runner):
joypad_devices = joypad.get_joypads()
name_counter = Counter([j[1] for j in joypad_devices])
name_indexes = {}
for (dev, joy_name) in joypad_devices:
for dev, joy_name in joypad_devices:
dev_id = re.findall(r"(\d+)", dev)[0]
if name_counter[joy_name] > 1:
if joy_name not in name_indexes:
@ -118,28 +118,24 @@ class reicast(Runner):
config_path = os.path.expanduser("~/.reicast/emu.cfg")
if system.path_exists(config_path):
with open(config_path, "r", encoding='utf-8') as config_file:
with open(config_path, "r", encoding="utf-8") as config_file:
parser.read_file(config_file)
for section in config:
if not parser.has_section(section):
parser.add_section(section)
for (key, value) in config[section].items():
for key, value in config[section].items():
parser.set(section, key, str(value))
with open(config_path, "w", encoding='utf-8') as config_file:
with open(config_path, "w", encoding="utf-8") as config_file:
parser.write(config_file)
def play(self):
fullscreen = "1" if self.runner_config.get("fullscreen") else "0"
reicast_config = {
"x11": {
"fullscreen": fullscreen
},
"x11": {"fullscreen": fullscreen},
"input": {},
"players": {
"nb": "1"
},
"players": {"nb": "1"},
}
players = 1
reicast_config["input"] = {}
@ -154,6 +150,4 @@ class reicast(Runner):
self.write_config(reicast_config)
iso = self.game_config.get("iso")
return {
"command": self.get_command() + ["-config", f"config:image={iso}"]
}
return {"command": self.get_command() + ["-config", f"config:image={iso}"]}

View File

@ -2,6 +2,7 @@
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
# Lutris Modules
from lutris.runners.runner import Runner
from lutris.util import system

View File

@ -178,7 +178,7 @@ class Runner: # pylint: disable=too-many-public-methods
"default": True,
"advanced": True,
"scope": ["runner"],
"help": _("Show this runner in the side panel if it is installed or available through Flatpak.")
"help": _("Show this runner in the side panel if it is installed or available through Flatpak."),
}
)
return runner_options
@ -226,7 +226,7 @@ class Runner: # pylint: disable=too-many-public-methods
if sdl_gamecontrollerconfig:
path = os.path.expanduser(sdl_gamecontrollerconfig)
if system.path_exists(path):
with open(path, "r", encoding='utf-8') as controllerdb_file:
with open(path, "r", encoding="utf-8") as controllerdb_file:
sdl_gamecontrollerconfig = controllerdb_file.read()
env["SDL_GAMECONTROLLERCONFIG"] = sdl_gamecontrollerconfig
@ -259,8 +259,7 @@ class Runner: # pylint: disable=too-many-public-methods
if runtime_ld_library_path:
ld_library_path = env.get("LD_LIBRARY_PATH")
env["LD_LIBRARY_PATH"] = os.pathsep.join(filter(None, [
runtime_ld_library_path, ld_library_path]))
env["LD_LIBRARY_PATH"] = os.pathsep.join(filter(None, [runtime_ld_library_path, ld_library_path]))
# Apply user overrides at the end
env.update(self.system_config.get("env") or {})
@ -423,8 +422,7 @@ class Runner: # pylint: disable=too-many-public-methods
"""
if ui_delegate.show_install_yesno_inquiry(
question=_("The required runner is not installed.\n"
"Do you wish to install it now?"),
question=_("The required runner is not installed.\n" "Do you wish to install it now?"),
title=_("Required runner unavailable"),
):
if hasattr(self, "get_version"):
@ -479,9 +477,7 @@ class Runner: # pylint: disable=too-many-public-methods
if "wine" in self.name:
opts["merge_single"] = True
opts["dest"] = os.path.join(
self.directory, format_runner_version(runner_version_info)
)
opts["dest"] = os.path.join(self.directory, format_runner_version(runner_version_info))
if self.name == "libretro" and version:
opts["merge_single"] = False
@ -513,6 +509,7 @@ class Runner: # pylint: disable=too-many-public-methods
if self.name == "wine":
logger.debug("Clearing wine version cache")
from lutris.util.wine.wine import get_installed_wine_versions
get_installed_wine_versions.cache_clear()
if self.runner_executable:
@ -538,7 +535,7 @@ class Runner: # pylint: disable=too-many-public-methods
def find_option(self, options_group, option_name):
"""Retrieve an option dict if it exists in the group"""
if options_group not in ['game_options', 'runner_options']:
if options_group not in ["game_options", "runner_options"]:
return None
output = None
for item in getattr(self, options_group):

View File

@ -32,22 +32,23 @@ class ryujinx(Runner):
"label": _("Encryption keys"),
"type": "file",
"help": _("File containing the encryption keys."),
}, {
},
{
"option": "title_keys",
"label": _("Title keys"),
"type": "file",
"help": _("File containing the title keys."),
}
},
]
@property
def ryujinx_data_dir(self):
"""Return dir where Ryujinx files lie."""
candidates = ("~/.local/share/ryujinx", )
candidates = ("~/.local/share/ryujinx",)
for candidate in candidates:
path = system.fix_path_case(os.path.join(os.path.expanduser(candidate), "nand"))
if system.path_exists(path):
return path[:-len("nand")]
return path[: -len("nand")]
def play(self):
"""Run the game."""
@ -59,7 +60,7 @@ class ryujinx(Runner):
return {"command": arguments}
def _update_key(self, key_type):
"""Update a keys file if set """
"""Update a keys file if set"""
ryujinx_data_dir = self.ryujinx_data_dir
if not ryujinx_data_dir:
logger.error("Ryujinx data dir not set")

View File

@ -39,7 +39,9 @@ def _get_scale_factor_warning(config, _option_key):
scale_factor = config["scale-factor"]
if scale_factor not in _supported_scale_factors[scaler]:
return _("<b>Warning</b> The '%s' scaler does not work with a scale factor of %s.") % (
scaler, scale_factor)
scaler,
scale_factor,
)
return None
@ -52,16 +54,8 @@ class scummvm(Runner):
runner_executable = "scummvm/bin/scummvm"
flatpak_id = "org.scummvm.ScummVM"
game_options = [
{
"option": "game_id",
"type": "string",
"label": _("Game identifier")
},
{
"option": "path",
"type": "directory_chooser",
"label": _("Game files location")
},
{"option": "game_id", "type": "string", "label": _("Game identifier")},
{"option": "path", "type": "directory_chooser", "label": _("Game files location")},
{
"option": "args",
"type": "string",
@ -105,9 +99,7 @@ class scummvm(Runner):
"debug-flags": "--debug-flags=%s",
}
option_empty_map = {
"fullscreen": "--no-fullscreen"
}
option_empty_map = {"fullscreen": "--no-fullscreen"}
runner_options = [
{
@ -157,9 +149,9 @@ class scummvm(Runner):
("tv2x", "tv2x"),
],
"warning": _get_opengl_warning,
"help":
_("The algorithm used to scale up the game's base "
"resolution, resulting in different visual styles. "),
"help": _(
"The algorithm used to scale up the game's base " "resolution, resulting in different visual styles. "
),
},
{
"option": "scale-factor",
@ -174,11 +166,12 @@ class scummvm(Runner):
("4", "4"),
("5", "5"),
],
"help":
_("Changes the resolution of the game. "
"For example, a 2x scale will take a 320x200 "
"resolution game and scale it up to 640x400. "),
"warning": _get_scale_factor_warning
"help": _(
"Changes the resolution of the game. "
"For example, a 2x scale will take a 320x200 "
"resolution game and scale it up to 640x400. "
),
"warning": _get_scale_factor_warning,
},
{
"option": "renderer",
@ -189,7 +182,7 @@ class scummvm(Runner):
(_("Auto"), ""),
(_("Software"), "software"),
(_("OpenGL"), "opengl"),
(_("OpenGL (with shaders)"), "opengl_shaders")
(_("OpenGL (with shaders)"), "opengl_shaders"),
],
"default": "",
"advanced": True,
@ -242,8 +235,10 @@ class scummvm(Runner):
"section": _("Graphics"),
"label": _("Filtering"),
"type": "bool",
"help": _("Uses bilinear interpolation instead of nearest neighbor "
"resampling for the aspect ratio correction and stretch mode."),
"help": _(
"Uses bilinear interpolation instead of nearest neighbor "
"resampling for the aspect ratio correction and stretch mode."
),
"default": False,
"advanced": True,
},
@ -258,8 +253,10 @@ class scummvm(Runner):
"option": "platform",
"type": "string",
"label": _("Platform"),
"help": _("Specifes platform of game. Allowed values: 2gs, 3do, acorn, amiga, atari, c64, "
"fmtowns, nes, mac, pc pc98, pce, segacd, wii, windows"),
"help": _(
"Specifes platform of game. Allowed values: 2gs, 3do, acorn, amiga, atari, c64, "
"fmtowns, nes, mac, pc pc98, pce, segacd, wii, windows"
),
"advanced": True,
},
{
@ -280,8 +277,10 @@ class scummvm(Runner):
"option": "engine-speed",
"type": "string",
"label": _("Engine speed"),
"help": _("Sets frames per second limit (0 - 100) for Grim Fandango "
"or Escape from Monkey Island (default: 60)."),
"help": _(
"Sets frames per second limit (0 - 100) for Grim Fandango "
"or Escape from Monkey Island (default: 60)."
),
"advanced": True,
},
{
@ -357,8 +356,10 @@ class scummvm(Runner):
("op3lpt", "op3lpt"),
("rwopl3", "rwopl3"),
],
"help": _("Chooses which emulator is used by ScummVM when the AdLib emulator "
"is chosen as the Preferred device."),
"help": _(
"Chooses which emulator is used by ScummVM when the AdLib emulator "
"is chosen as the Preferred device."
),
"advanced": True,
},
{
@ -416,8 +417,10 @@ class scummvm(Runner):
"label": _("True Roland MT-32"),
"type": "bool",
"default": False,
"help": _("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."),
"help": _(
"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."
),
"advanced": True,
},
{
@ -426,8 +429,10 @@ class scummvm(Runner):
"label": _("Enable Roland GS"),
"type": "bool",
"default": False,
"help": _("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."),
"help": _(
"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."
),
"advanced": True,
},
{
@ -550,10 +555,9 @@ class scummvm(Runner):
def get_game_list(self):
"""Return the entire list of games supported by ScummVM."""
with subprocess.Popen(self.get_command() + ["--list-games"],
stdout=subprocess.PIPE,
encoding="utf-8",
universal_newlines=True) as scummvm_process:
with subprocess.Popen(
self.get_command() + ["--list-games"], stdout=subprocess.PIPE, encoding="utf-8", universal_newlines=True
) as scummvm_process:
scumm_output = scummvm_process.communicate()[0]
game_list = str.split(scumm_output, "\n")
game_array = []
@ -566,7 +570,7 @@ class scummvm(Runner):
dir_limit = None
if dir_limit is not None:
game_dir = game[0:dir_limit]
game_name = game[dir_limit + 1:len(game)].strip()
game_name = game[dir_limit + 1 : len(game)].strip()
game_array.append([game_dir, game_name])
# The actual list is below a separator
if game.startswith("-----"):

Some files were not shown because too many files have changed in this diff Show More