diff --git a/lutris/gui/application.py b/lutris/gui/application.py index 37cec1b40..b1e593853 100644 --- a/lutris/gui/application.py +++ b/lutris/gui/application.py @@ -96,7 +96,7 @@ class LutrisApplication(Gtk.Application): self.tray = None self.quit_on_game_exit = False - self.style_manager = None + self.style_manager = StyleManager() if os.geteuid() == 0: NoticeDialog(_("Do not run Lutris as root.")) @@ -340,8 +340,6 @@ class LutrisApplication(Gtk.Application): self.add_action(action) self.add_accelerator("q", "app.quit") - self.style_manager = StyleManager() - def do_activate(self): # pylint: disable=arguments-differ if not self.window: self.window = LutrisWindow(application=self) diff --git a/lutris/gui/config/preferences_box.py b/lutris/gui/config/preferences_box.py index 92f2991a3..5cbe2ff5a 100644 --- a/lutris/gui/config/preferences_box.py +++ b/lutris/gui/config/preferences_box.py @@ -1,7 +1,7 @@ from gettext import gettext as _ from typing import Any, Dict, Optional -from gi.repository import Gio, Gtk +from gi.repository import Gio, Gtk # type: ignore from lutris import settings from lutris.gui.config.base_config_box import BaseConfigBox diff --git a/lutris/gui/config/widget_generator.py b/lutris/gui/config/widget_generator.py index c0ca2a96b..64a146f5d 100644 --- a/lutris/gui/config/widget_generator.py +++ b/lutris/gui/config/widget_generator.py @@ -4,9 +4,9 @@ import os from abc import ABC, abstractmethod from gettext import gettext as _ from inspect import Parameter, signature -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable, Dict, List, Optional -from gi.repository import Gdk, Gtk +from gi.repository import Gdk, Gtk # type: ignore from lutris.config import LutrisConfig from lutris.gui.widgets import NotificationSource @@ -50,7 +50,7 @@ class WidgetGenerator(ABC): # These are outputs set by generate_widget() or generate_container() # and they are reset on each call. - self.wrapper: Optional[Gtk.Widget] = None + self.wrapper: Optional[Gtk.Box] = None self.default_value = None self.tooltip_default: Optional[str] = None self.options: Dict[str, Dict[str, Any]] = {} @@ -59,9 +59,9 @@ class WidgetGenerator(ABC): self.warning_messages: List[Gtk.Widget] = [] # These accumulate results across all widgets - self.wrappers: Dict[str, Gtk.Widget] = {} + self.wrappers: Dict[str, Gtk.Container] = {} self.section_frames: List[SectionFrame] = [] - self.option_containers: Dict[str, Gtk.Widget] = {} + self.option_containers: Dict[str, Gtk.Container] = {} self._generators: Dict[str, WidgetGenerator.GeneratorFunction] = { "label": self._generate_label, @@ -98,7 +98,7 @@ class WidgetGenerator(ABC): # Widget Construction - def add_container(self, option: Dict[str, Any], wrapper: Gtk.Box = None) -> Optional[Gtk.Widget]: + def add_container(self, option: Dict[str, Any], wrapper: Optional[Gtk.Box] = None) -> Optional[Gtk.Widget]: """Generates the option's widget, wrapper and container, and adds the container to the parent; if the option uses 'section', then the container is actually placed inside a SectionFrame, or in the previous frame if it is for the same section.""" @@ -122,7 +122,7 @@ class WidgetGenerator(ABC): self._current_parent.pack_start(option_container, False, False, 0) return option_container - def generate_container(self, option: Dict[str, Any], wrapper: Gtk.Box = None) -> Optional[Gtk.Widget]: + def generate_container(self, option: Dict[str, Any], wrapper: Optional[Gtk.Box] = None) -> Optional[Gtk.Widget]: """Creates the widget, wrapper, and container; this returns the container (or the wrapper if there's no container).""" option_widget = self.generate_widget(option, wrapper) @@ -145,7 +145,7 @@ class WidgetGenerator(ABC): else: return None - def generate_widget(self, option: Dict[str, Any], wrapper: Gtk.Box = None) -> Optional[Gtk.Widget]: + def generate_widget(self, option: Dict[str, Any], wrapper: Optional[Gtk.Box] = None) -> Optional[Gtk.Widget]: """This creates a wrapper box and a label and widget within it according to the options dict given. The option widget itself, is returned, but this method also sets attributes on the generator. You get 'wrapper', 'default_value', 'tooltip_default' and 'option_widget' which restates @@ -232,7 +232,7 @@ class WidgetGenerator(ABC): return Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12, margin_bottom=6, visible=True) - def create_option_container(self, option: Dict[str, Any], wrapper: Gtk.Widget) -> Gtk.Widget: + def create_option_container(self, option: Dict[str, Any], wrapper: Gtk.Container) -> Gtk.Container: """This creates a wrapper box around the widget wrapper, to support additional controls. The base implementation wraps 'wrapper' in a Box with the error and warning widgets; if there are none it just returns 'wrapper'.""" @@ -292,15 +292,15 @@ class WidgetGenerator(ABC): frame.set_visible(visible) frame.set_no_show_all(not visible) - def update_option_container(self, option, container: Gtk.Box, wrapper: Gtk.Box): + def update_option_container(self, option, container: Gtk.Container, wrapper: Gtk.Container): """This method updates an option container and its wrapper; this re-evaluates the relevant options in case they contain callables and those callables return different results.""" # Update messages in message boxes that support it - for ch in container.get_children(): - if hasattr(ch, "update_message"): - ch.update_message(option, self) + for child in container.get_children(): + if hasattr(child, "update_message"): + child.update_message(option, self) # Hide entire container if the option is not visible visible = self.get_visibility(option) @@ -308,7 +308,7 @@ class WidgetGenerator(ABC): container.set_no_show_all(not visible) # Grey out option if condition unmet, or if a second setting is False - condition = self.get_condition(option) + condition: bool = self.get_condition(option) wrapper.set_sensitive(condition) # Widget factories @@ -600,7 +600,7 @@ class WidgetGenerator(ABC): vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) label = Label(label + ":") label.set_halign(Gtk.Align.START) - button = Gtk.Button(_("Add Files")) + button = Gtk.Button(label=_("Add Files")) button.connect("clicked", on_add_files_clicked) button.set_margin_left(10) vbox.pack_start(label, False, False, 5) @@ -619,7 +619,7 @@ class WidgetGenerator(ABC): for filename in files: files_list_store.append([filename]) cell_renderer = Gtk.CellRendererText() - files_treeview = Gtk.TreeView(files_list_store) + files_treeview = Gtk.TreeView(model=files_list_store) files_column = Gtk.TreeViewColumn(_("Files"), cell_renderer, text=0) files_treeview.append_column(files_column) files_treeview.connect("key-press-event", on_files_treeview_keypress) @@ -709,7 +709,7 @@ class WidgetGenerator(ABC): True, and if it is callable this calls it. Subclasses can add further conditions.""" return self._evaluate_flag_option("visible", option) - def get_condition(self, option: Dict[str, Any]) -> Union[None, bool, Callable]: + def get_condition(self, option: Dict[str, Any]) -> bool: """Extracts the 'condition' option; but also the 'conditional_on' option, and if both are present, then if either indicates the control should be disabled this will be false..""" condition = self._evaluate_flag_option("condition", option) @@ -722,8 +722,8 @@ class WidgetGenerator(ABC): container = self.option_containers[option["option"]] - for ch in container.get_children(): - if hasattr(ch, "blocks_sensitivity") and ch.blocks_sensitivity: + for child in container.get_children(): + if hasattr(child, "blocks_sensitivity") and child.blocks_sensitivity: return False return condition diff --git a/lutris/gui/dialogs/delegates.py b/lutris/gui/dialogs/delegates.py index 49c960058..5d3418afb 100644 --- a/lutris/gui/dialogs/delegates.py +++ b/lutris/gui/dialogs/delegates.py @@ -1,7 +1,7 @@ from gettext import gettext as _ from typing import Optional -from gi.repository import Gdk, Gtk +from gi.repository import Gdk, Gtk # type: ignore from lutris.exceptions import UnavailableRunnerError from lutris.game import Game @@ -79,7 +79,7 @@ class InstallUIDelegate(Delegate): """ return None - def download_install_file(self, url: str, destination: str) -> Downloader: + def download_install_file(self, url: str, destination: str) -> bool: """Downloads a file from a URL to a destination, overwriting any file at that path. @@ -137,7 +137,7 @@ class DialogInstallUIDelegate(InstallUIDelegate): dlg = dialogs.FileDialog(message) return dlg.filename - def download_install_file(self, url, destination): + def download_install_file(self, url: str, destination: str) -> bool: dialog = DownloadDialog(url, destination, parent=self) dialog.run() return dialog.downloader.state == Downloader.COMPLETED diff --git a/lutris/gui/download_queue.py b/lutris/gui/download_queue.py index da855c434..29981d025 100644 --- a/lutris/gui/download_queue.py +++ b/lutris/gui/download_queue.py @@ -1,8 +1,8 @@ # pylint: disable=no-member import os -from typing import Any, Callable, Dict, Iterable, List, Set +from typing import Any, Callable, Dict, Iterable, List, Optional, Set -from gi.repository import GObject, Gtk +from gi.repository import GObject, Gtk # type: ignore from lutris.gui.widgets.gi_composites import GtkTemplate from lutris.gui.widgets.progress_box import ProgressBox @@ -22,7 +22,7 @@ class DownloadQueue(Gtk.ScrolledWindow): __gtype_name__ = "DownloadQueue" - download_box: Gtk.Box = GtkTemplate.Child() + download_box: Gtk.Box = GtkTemplate.Child() # type: ignore CompletionFunction = Callable[[Any], None] ErrorFunction = Callable[[Exception], None] @@ -99,9 +99,9 @@ class DownloadQueue(Gtk.ScrolledWindow): self, operation: Callable[[], Any], progress_function: ProgressBox.ProgressFunction, - completion_function: CompletionFunction = None, - error_function: ErrorFunction = None, - operation_name: str = None, + completion_function: Optional[CompletionFunction] = None, + error_function: Optional[ErrorFunction] = None, + operation_name: Optional[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. @@ -129,9 +129,9 @@ class DownloadQueue(Gtk.ScrolledWindow): self, operation: Callable[[], Any], progress_functions: Iterable[ProgressBox.ProgressFunction], - completion_function: CompletionFunction = None, - error_function: ErrorFunction = None, - operation_names: List[str] = None, + completion_function: Optional[CompletionFunction] = None, + error_function: Optional[ErrorFunction] = None, + operation_names: Optional[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 @@ -152,6 +152,8 @@ class DownloadQueue(Gtk.ScrolledWindow): if not self.running_operation_names.isdisjoint(operation_names): return False self.running_operation_names.update(operation_names) + else: + operation_names = [] # Must capture the functions, since in earlier (<3.8) Pythons functions do not provide # value equality, so we need to make sure we're always using what we started with. diff --git a/lutris/gui/lutriswindow.py b/lutris/gui/lutriswindow.py index 7ee40ea2e..e190e169b 100644 --- a/lutris/gui/lutriswindow.py +++ b/lutris/gui/lutriswindow.py @@ -96,7 +96,7 @@ class LutrisWindow(Gtk.ApplicationWindow, DialogLaunchUIDelegate, DialogInstallU version_notification_label: Gtk.Revealer = GtkTemplate.Child() show_hidden_games_button: Gtk.ModelButton = GtkTemplate.Child() - def __init__(self, application, **kwargs) -> None: + def __init__(self, application=None, **kwargs) -> None: width = int(settings.read_setting("width") or self.default_width) height = int(settings.read_setting("height") or self.default_height) super().__init__( @@ -150,7 +150,7 @@ class LutrisWindow(Gtk.ApplicationWindow, DialogLaunchUIDelegate, DialogInstallU # Since system-search-symbolic is already *right there* we'll try to pick some # other icon for the button that shows the search popover. fallback_filter_icons_names = ["filter-symbolic", "edit-find-replace-symbolic", "system-search-symbolic"] - filter_button_image = self.search_filters_button.get_child() + filter_button_image: Gtk.Image = self.search_filters_button.get_child() for n in fallback_filter_icons_names: if has_stock_icon(n): filter_button_image.set_from_icon_name(n, Gtk.IconSize.BUTTON) diff --git a/lutris/gui/widgets/gi_composites.py b/lutris/gui/widgets/gi_composites.py index b92f9acbc..ff93bb78f 100644 --- a/lutris/gui/widgets/gi_composites.py +++ b/lutris/gui/widgets/gi_composites.py @@ -27,15 +27,12 @@ See: https://gitlab.gnome.org/GNOME/pygobject/merge_requests/52 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 # USA -# Standard Library import inspect +import os import warnings -from os.path import abspath, join -# Third Party Libraries -from gi.repository import Gio, GLib, GObject, Gtk +from gi.repository import Gio, GLib, GObject, Gtk # type: ignore -# Lutris Modules from lutris.gui.dialogs import ErrorDialog __all__ = ["GtkTemplate"] @@ -58,12 +55,13 @@ def _connect_func(builder, obj, signal_name, handler_name, connect_object, flags template_inst = builder.get_object(cls.__gtype_name__) if template_inst is None: # This should never happen - errmsg = ( + warnings.warn( "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) + % (obj, signal_name, handler_name, connect_object, cls), + GtkTemplateWarning, + stacklevel=2, ) - warnings.warn(errmsg, GtkTemplateWarning) return handler = getattr(template_inst, handler_name) @@ -145,8 +143,11 @@ def _init_template(self, cls, base_init_template): ) for name in self.__gtemplate_methods__.difference(connected_signals): - errmsg = ("Signal '%s' was declared with @GtkTemplate.Callback " + "but was not present in template") % name - warnings.warn(errmsg, GtkTemplateWarning) + warnings.warn( + "Signal '%s' was declared with @GtkTemplate.Callback but was not present in template" % name, + GtkTemplateWarning, + stacklevel=2, + ) # TODO: Make it easier for IDE to introspect this @@ -242,7 +243,7 @@ class _GtkTemplate: 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 + _GtkTemplate.__ui_path__ = os.path.abspath(os.path.join(*path)) def __init__(self, ui): self.ui = ui @@ -260,13 +261,13 @@ class _GtkTemplate: try: template_bytes = Gio.resources_lookup_data(self.ui, Gio.ResourceLookupFlags.NONE) - except GLib.GError: + except GLib.GError: # type: ignore ui = self.ui if isinstance(ui, (list, tuple)): - ui = join(ui) + ui = " ".join(ui) if _GtkTemplate.__ui_path__ is not None: - ui = join(_GtkTemplate.__ui_path__, ui) + ui = os.path.join(_GtkTemplate.__ui_path__, ui) with open(ui, "rb") as fp: template_bytes = GLib.Bytes.new(fp.read()) diff --git a/lutris/runners/commands/wine.py b/lutris/runners/commands/wine.py index d613c2c39..18590c2ff 100644 --- a/lutris/runners/commands/wine.py +++ b/lutris/runners/commands/wine.py @@ -4,8 +4,10 @@ import os import shlex import time +from typing import Dict, Optional, Tuple from lutris import runtime, settings +from lutris.config import LutrisConfig from lutris.monitored_command import MonitoredCommand from lutris.runners import import_runner from lutris.util import linux, system @@ -63,7 +65,7 @@ def set_regedit_file(filename, wine_path=None, prefix=None, arch=WINE_DEFAULT_AR # a bug in Wine. see: https://github.com/lutris/lutris/issues/804 wine_path = wine_path + "64" - if proton.is_proton_path(wine_path): + if not wine_path or proton.is_proton_path(wine_path): proton_verb = "run" wineexec( @@ -80,7 +82,7 @@ def set_regedit_file(filename, wine_path=None, prefix=None, arch=WINE_DEFAULT_AR def delete_registry_key(key, wine_path=None, prefix=None, arch=WINE_DEFAULT_ARCH, proton_verb=None): """Deletes a registry key from a Wine prefix""" - if proton.is_proton_path(wine_path): + if not wine_path or proton.is_proton_path(wine_path): proton_verb = "run" wineexec( @@ -98,7 +100,6 @@ def create_prefix( 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 if overrides is None: overrides = {} if not prefix: @@ -175,17 +176,21 @@ def create_prefix( prefix_manager.setup_defaults() -def winekill(prefix, arch=WINE_DEFAULT_ARCH, wine_path=None, env=None, initial_pids=None, runner=None): +def winekill(prefix, arch=WINE_DEFAULT_ARCH, wine_path="", env=None, initial_pids=None, runner=None): """Kill processes in Wine prefix.""" initial_pids = initial_pids or [] if not env: - env = {"WINEARCH": arch, "WINEPREFIX": prefix} - if proton.is_proton_path(wine_path): + env = { + "WINEARCH": arch, + "WINEPREFIX": prefix, + "GAMEID": proton.DEFAULT_GAMEID, + "PROTON_VERB": "runinprefix", + } + if proton.is_umu_path(wine_path): + command = [wine_path, "wineboot", "-k"] + elif proton.is_proton_path(wine_path): command = [proton.get_umu_path(), "wineboot", "-k"] - env["GAMEID"] = proton.DEFAULT_GAMEID - env["WINEPREFIX"] = prefix - env["PROTON_VERB"] = "runinprefix" env["PROTONPATH"] = proton.get_proton_path_by_path(wine_path) else: if not wine_path: @@ -232,33 +237,32 @@ def use_lutris_runtime(wine_path, force_disable=False): return True -# pragma pylint: disable=too-many-locals def wineexec( - executable, - args="", - wine_path=None, - prefix=None, - arch=None, - working_dir=None, - winetricks_wine="", - blocking=False, - config=None, - include_processes=None, - exclude_processes=None, - disable_runtime=False, - env=None, + executable: str, + prefix: str, + args: str = "", + wine_path: Optional[str] = None, + arch: str = WINE_DEFAULT_ARCH, + working_dir: Optional[str] = None, + winetricks_wine: str = "", + blocking: bool = False, + config: Optional[LutrisConfig] = None, + include_processes: Optional[list] = None, + exclude_processes: Optional[list] = None, + disable_runtime: bool = False, + env: Optional[dict] = None, overrides=None, runner=None, - proton_verb=None, + proton_verb: Optional[str] = None, ): """ Execute a Wine command. Args: executable (str): wine program to run, pass None to run wine itself + prefix (str): path to the wine prefix to use args (str): program arguments wine_path (str): path to the wine version to use - prefix (str): path to the wine prefix to use arch (str): wine architecture of the prefix working_dir (str): path to the working dir for the process winetricks_wine (str): path to the wine version used by winetricks @@ -271,12 +275,9 @@ def wineexec( Process results if the process is running in blocking mode or MonitoredCommand instance otherwise. """ - if env is None: - env = {} - if exclude_processes is None: - exclude_processes = [] - if include_processes is None: - include_processes = [] + env = env or {} + exclude_processes = exclude_processes or [] + include_processes = include_processes or [] executable = str(executable) if executable else "" if isinstance(include_processes, str): include_processes = shlex.split(include_processes) @@ -300,7 +301,7 @@ def wineexec( wineenv = {"WINEARCH": arch} if winetricks_wine and winetricks_wine is not wine_path and not proton.is_proton_path(wine_path): wineenv["WINE"] = winetricks_wine - else: + elif wine_path: wineenv["WINE"] = wine_path if prefix: @@ -376,7 +377,9 @@ def wineexec( # pragma pylint: enable=too-many-locals -def find_winetricks(env=None, system_winetricks=False): +def find_winetricks( + env: Optional[dict[str, str]] = None, system_winetricks=False +) -> Tuple[str, Optional[str], Dict[str, str]]: """Find winetricks path.""" winetricks_path = os.path.join(settings.RUNTIME_DIR, "winetricks/winetricks") if system_winetricks or not system.path_exists(winetricks_path): @@ -388,9 +391,7 @@ def find_winetricks(env=None, system_winetricks=False): # working_dir, so it will find the data file. working_dir = os.path.join(settings.RUNTIME_DIR, "winetricks") - if not env: - env = {} - + env = env or {} path = env.get("PATH", os.environ["PATH"]) env["PATH"] = "%s:%s" % (working_dir, path) @@ -398,11 +399,11 @@ def find_winetricks(env=None, system_winetricks=False): def winetricks( - app, - prefix=None, - arch=None, - silent=True, - wine_path=None, + app: Optional[str], + prefix: str, + arch: str = WINE_DEFAULT_ARCH, + silent: bool = True, + wine_path: Optional[str] = None, config=None, env=None, disable_runtime=False, @@ -411,39 +412,39 @@ def winetricks( proton_verb=None, ): """Execute winetricks.""" - winetricks_path, working_dir, env = find_winetricks(env, system_winetricks) + if not app: + silent = False + app = "--gui" + args = app + if not wine_path or proton.is_umu_path(wine_path): + winetricks_wine = proton.get_umu_path() + proton_verb = "waitforexitandrun" - if wine_path: - winetricks_wine = wine_path - if proton.is_proton_path(wine_path): - protonfixes_path = os.path.join(proton.get_proton_path_by_path(wine_path), "protonfixes") - if os.path.exists(protonfixes_path): - winetricks_wine = os.path.join(protonfixes_path, "winetricks") - winetricks_path = wine_path - if not app: - silent = False - app = "--gui" - else: - logger.info("winetricks: Valve official Proton builds do not support winetricks.") - return + elif proton.is_proton_path(wine_path): + proton_verb = "waitforexitandrun" + protonfixes_path = os.path.join(proton.get_proton_path_by_path(wine_path), "protonfixes") + if os.path.exists(protonfixes_path): + winetricks_wine = os.path.join(protonfixes_path, "winetricks") + winetricks_path = wine_path + if not app: + silent = False + app = "--gui" + else: + logger.error("winetricks: Valve official Proton builds do not support winetricks.") + return else: + winetricks_path, working_dir, env = find_winetricks(env, system_winetricks) if not runner: runner = import_runner("wine")() winetricks_wine = runner.get_executable() - - if arch not in ("win32", "win64"): - arch = detect_arch(prefix, winetricks_wine) - args = app - - if str(silent).lower() in ("yes", "on", "true") and not proton.is_proton_path(wine_path): - args = "-q " + args - else: - if proton.is_proton_path(wine_path): - proton_verb = "waitforexitandrun" + if arch not in ("win32", "win64"): + arch = detect_arch(prefix, winetricks_wine) + if str(silent).lower() in ("yes", "on", "true"): + args = "-q " + args # Execute wineexec return wineexec( - None, + "", prefix=prefix, winetricks_wine=winetricks_wine, wine_path=winetricks_path, @@ -483,7 +484,7 @@ def winecfg(wine_path=None, prefix=None, arch=WINE_DEFAULT_ARCH, config=None, en ) -def eject_disc(wine_path, prefix, proton_verb=None): +def eject_disc(wine_path: str, prefix: str, proton_verb=None): """Use Wine to eject a drive""" if proton.is_proton_path(wine_path): @@ -491,7 +492,7 @@ def eject_disc(wine_path, prefix, proton_verb=None): wineexec("eject", prefix=prefix, wine_path=wine_path, args="-a", proton_verb=proton_verb) -def install_cab_component(cabfile, component, wine_path=None, prefix=None, arch=None, proton_verb=None): +def install_cab_component(cabfile, component, wine_path: str, prefix=None, arch=None, proton_verb=None): """Install a component from a cabfile in a prefix""" if proton.is_proton_path(wine_path): @@ -504,7 +505,9 @@ def install_cab_component(cabfile, component, wine_path=None, prefix=None, arch= cab_installer.cleanup() -def open_wine_terminal(terminal, wine_path, prefix, env, system_winetricks): +def open_wine_terminal( + terminal: Optional[str], wine_path: str, prefix: str, env: Optional[Dict[str, str]], system_winetricks: bool +): winetricks_path, _working_dir, env = find_winetricks(env, system_winetricks) path_paths = [os.path.dirname(wine_path)] if proton.is_proton_path(wine_path): diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py index b9b032715..45d341dd0 100644 --- a/lutris/runners/wine.py +++ b/lutris/runners/wine.py @@ -913,7 +913,7 @@ class wine(Runner): def run_wine_terminal(self, *args): terminal = self.system_config.get("terminal_app") - system_winetricks = self.runner_config.get("system_winetricks") + system_winetricks: bool = self.runner_config.get("system_winetricks", False) open_wine_terminal( terminal=terminal, wine_path=self.get_executable(), @@ -1213,10 +1213,10 @@ class wine(Runner): except Exception as ex: logger.exception("Failed to setup desktop integration, the prefix may not be valid: %s", ex) - def play(self): # pylint: disable=too-many-return-statements # noqa: C901 + def play(self): # pylint: disable=too-many-return-statements game_exe = self.game_exe - arguments = self.game_config.get("args", "") - launch_info = {"env": self.get_env(os_env=False)} + arguments: str = self.game_config.get("args", "") + launch_info: dict = {"env": self.get_env(os_env=False)} using_dxvk = self.runner_config.get("dxvk") and LINUX_SYSTEM.is_vulkan_supported if using_dxvk: diff --git a/lutris/util/steam/steamid.py b/lutris/util/steam/steamid.py index 61d831e65..f5d0d653c 100644 --- a/lutris/util/steam/steamid.py +++ b/lutris/util/steam/steamid.py @@ -160,7 +160,10 @@ class SteamID: match = COMMUNITY32_REGEX.match(url.path) if match: if match.group("path") not in TYPE_URL_PATH_MAP[LETTER_TYPE_MAP[match.group("type")]]: - warnings.warn("Community URL ({}) path doesn't match type character".format(url.path)) + warnings.warn( + "Community URL ({}) path doesn't match type character".format(url.path), + stacklevel=2, + ) steamid = int(match.group("steamid")) instance = steamid & 1 account_number = (steamid - instance) / 2 diff --git a/lutris/util/system.py b/lutris/util/system.py index 1135232f7..9ac346a04 100644 --- a/lutris/util/system.py +++ b/lutris/util/system.py @@ -13,7 +13,7 @@ from gettext import gettext as _ from pathlib import Path from typing import Dict, List, Optional, Tuple -from gi.repository import Gio, GLib +from gi.repository import Gio, GLib # type: ignore from lutris import settings from lutris.exceptions import MissingExecutableError @@ -41,11 +41,11 @@ def get_environment(): def execute( command: List[str], - env: Dict[str, str] = None, - cwd: str = None, + env: Optional[Dict[str, str]] = None, + cwd: Optional[str] = None, quiet: bool = False, shell: bool = False, - timeout: float = None, + timeout: Optional[float] = None, ) -> str: """ Execute a system command and return its standard output; standard error is discarded. @@ -66,11 +66,11 @@ def execute( def execute_with_error( command: List[str], - env: Dict[str, str] = None, - cwd: str = None, + env: Optional[Dict[str, str]] = None, + cwd: Optional[str] = None, quiet: bool = False, shell: bool = False, - timeout: float = None, + timeout: Optional[float] = None, ) -> Tuple[str, str]: """ Execute a system command and return its standard output and; standard error in a tuple. @@ -90,12 +90,12 @@ def execute_with_error( def _execute( command: List[str], - env: Dict[str, str] = None, - cwd: str = None, + env: Optional[Dict[str, str]] = None, + cwd: Optional[str] = None, capture_stderr: bool = False, quiet: bool = False, shell: bool = False, - timeout: float = None, + timeout: Optional[float] = None, ) -> Tuple[str, str]: # Check if the executable exists if not command: @@ -292,11 +292,8 @@ def kill_pid(pid): logger.error("Could not kill process %s", pid) -def python_identifier(unsafe_string): +def python_identifier(unsafe_string: str): """Converts a string to something that can be used as a python variable""" - if not isinstance(unsafe_string, str): - logger.error("Cannot convert %s to a python identifier", type(unsafe_string)) - return def _dashrepl(matchobj): return matchobj.group(0).replace("-", "_") @@ -304,7 +301,7 @@ def python_identifier(unsafe_string): return re.sub(r"(\${)([\w-]*)(})", _dashrepl, unsafe_string) -def substitute(string_template, variables): +def substitute(string_template: str, variables): """Expand variables on a string template Args: @@ -355,8 +352,8 @@ def merge_folders(source, destination): def remove_folder( path: str, - completion_function: TrashPortal.CompletionFunction = None, - error_function: TrashPortal.ErrorFunction = None, + completion_function: Optional[TrashPortal.CompletionFunction] = None, + error_function: Optional[TrashPortal.ErrorFunction] = None, ) -> None: """Trashes a folder specified by path, asynchronously. The folder likely exists after this returns, since it's using DBus to ask diff --git a/lutris/util/wine/wine.py b/lutris/util/wine/wine.py index f76d34dfa..5376a678a 100644 --- a/lutris/util/wine/wine.py +++ b/lutris/util/wine/wine.py @@ -34,7 +34,7 @@ except Exception as ex: logger.exception("Unable to enumerate system Wine versions: %s", ex) -def detect_arch(prefix_path: str = "", wine_path: str = "") -> str: +def detect_arch(prefix_path: Optional[str] = None, wine_path: Optional[str] = None) -> str: """Given a Wine prefix path, return its architecture""" if wine_path: if proton.is_proton_path(wine_path) or system.path_exists(wine_path + "64"): @@ -237,7 +237,7 @@ def get_system_wine_version(wine_path: str = "wine") -> str: return version -def get_real_executable(windows_executable: str, working_dir: str) -> Tuple[str, List[str], str]: +def get_real_executable(windows_executable: str, working_dir: Optional[str]) -> Tuple[str, List[str], Optional[str]]: """Given a Windows executable, return the real program capable of launching it along with necessary arguments.""" diff --git a/pyproject.toml b/pyproject.toml index 437d5c4a9..a5f54a4b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.mypy] -python_version = "3.8" +python_version = "3.12" packages = [ "lutris", "tests", @@ -18,7 +18,6 @@ disable_error_code = [ allow_redefinition = true follow_imports = "silent" ignore_missing_imports = true -implicit_optional = true [tool.mypy-baseline] # --baseline-path: the file where the baseline should be stored diff --git a/ruff.toml b/ruff.toml index 8bb882a7c..eb310ee0b 100644 --- a/ruff.toml +++ b/ruff.toml @@ -5,7 +5,6 @@ select = ["A", "ARG", "B", "E", "F", "I", "W", "PERF", "RUF"] ignore = [ # Ignores that is not worth/too hard to fix "RUF001", # String contains ambiguous `!` (FULLWIDTH EXCLAMATION MARK). Did you mean `!` (EXCLAMATION MARK)? - used ,mostly on tests - "B028", # No explicit `stacklevel` keyword argument found - not sure why this is needed "E402", # Module level import not at top of file - gtk stuff # Ignores that should be fixed and removed