mirror of
https://github.com/lutris/lutris
synced 2024-07-01 06:34:26 +00:00
Ruff reformat
This commit is contained in:
parent
2c9d12337d
commit
38fbe9f05e
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"],))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"],
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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"]}
|
||||
)
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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."))
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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."))
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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}"]}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user