11623 Commits

Author SHA1 Message Date
Daniel Johnson
44acabb77e Make lutris-wrapper self-bootstrap in source-tree mode. Resolves #6730
8789d2bbb stopped publishing PYTHONPATH when launching lutris-wrapper,
on the assumption the wrapper's own sys.path bootstrap covered every
case it needed to. It didn't: anyone running `./bin/lutris` from a
git checkout without installing Lutris would now hit

    ModuleNotFoundError: No module named 'lutris'

at the wrapper's `from lutris.util.log import logger` line, before
the wrapper had a chance to do anything.

Two compounding causes:

1. The wrapper's bootstrap code lived inside `if __name__ == "__main__"`,
   meaning it ran AFTER the top-level `from lutris...` imports. The
   imports had to succeed first, and they were silently relying on the
   parent process having injected PYTHONPATH=":".join(sys.path) into
   the env before exec.
2. Even when it did run, the bootstrap's source-mode detection was
   checking `LAUNCH_PATH/../lutris` — i.e. `share/lutris/lutris/` —
   which has never existed. The check fell through to a second
   never-existent path. So the bootstrap was, in practice, dead code
   regardless of when it ran.

This commit fixes both:

* Move the source-tree detection above the lutris imports and look
  three directories up (`share/lutris/bin/` → repo root), where the
  `lutris/` package actually lives. Confirm via the package's
  `__init__.py` rather than just a directory match.
* Restore `env["PYTHONPATH"] = ":".join(sys.path)` in monitored_command
  as defense-in-depth. The wrapper now runs on the same Python as
  Lutris (sys.executable, via the existing 8789d2bbb change), so the
  inherited PYTHONPATH is ABI-compatible; and lutris-wrapper deletes
  PYTHONPATH from os.environ before spawning the game, so the leak
  doesn't reach Wine, umu-launcher, or any other runner subprocess.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 17:14:52 -04:00
Daniel Johnson
e1fb681c20 Exclude build/ from mypy
Stale build/ output from setup.py build created a duplicate lutris
package that caused mypy to bail before checking anything.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 18:43:48 -04:00
Daniel Johnson
18cd971c36 Merge pull request #6729 from SakuraJensen/epic-fab-filter-debug
Filter Epic/Fab editor resources from EGS library
2026-06-10 18:00:38 -04:00
Sakura
4e738410cc Format Epic resource filter 2026-06-09 20:25:55 -04:00
Sakura
398bb663f6 Filter Epic Fab resources from game library 2026-06-08 16:37:14 -04:00
Daniel Johnson
8789d2bbba Run lutris-wrapper with the current Python interpreter
MonitoredCommand previously launched lutris-wrapper via its
`#!/usr/bin/env python3` shebang and worked around the resulting
ambiguity by exporting PYTHONPATH=":".join(sys.path) on the subprocess
env. The original author flagged the PYTHONPATH line as suspect
("not clear why this needs to be added"); the actual reason was that
in scenarios where the shebang picks up a different Python than the
one running Lutris (an AppImage with a bundled interpreter, a venv
launched from outside, etc.), the wrapper couldn't import lutris's
modules without that hint.

Two problems with that workaround:

1. When the shebang resolves to a host Python with a different minor
   version, the inherited PYTHONPATH points at our stdlib, and host
   Python crashes during site initialization with a `_sre` MAGIC
   mismatch (a .pth file does `import importlib`, finds ours via
   PYTHONPATH, ours doesn't match the host interpreter's compiled
   constants).
2. PYTHONPATH propagates to every subprocess the wrapper then spawns,
   poisoning Wine, umu-launcher, RetroArch, every emulator runner —
   none of which need Lutris's sys.path and many of which break in
   subtle ways when handed an inappropriate one.

Invoke the wrapper as [sys.executable, WRAPPER_SCRIPT, ...] so the
interpreter is unambiguous, and drop the PYTHONPATH export. sys.path
is naturally inherited by the wrapper since it's running the same
Python, and host subprocesses get a clean environment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 10:36:30 -04:00
Daniel Johnson
3da2d31a74 Warn clearly when xrandr binary is missing
xrandr is the legacy fallback for `LegacyDisplayManager` when Mutter's
display config service isn't available (notably KDE/Wayland). Without
it the resolution dropdown collapses to a single dummy entry (users
have to hand-enter the value they want), and per-monitor info is
unavailable. The old "xrandr didn't return anything" log didn't tell
users what was wrong or how to fix it.

Wrap the lookup in a @cache_single helper so a clear "install
x11-xserver-utils / xrandr" warning fires exactly once per session,
then return the cached path (or None) on subsequent calls.

Refs #6707.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 09:26:24 -04:00
Daniel Johnson
094306f276 Merge pull request #6710 from MarongHappy/patch-1
Update ko.po
2026-05-25 07:49:00 -04:00
JungHee Lee
5336e5a7cc Update ko.po 2026-05-25 16:52:52 +09:00
Daniel Johnson
613306307c Defer Gtk import in log.py to TYPE_CHECKING
The lutris-wrapper subprocess imports lutris.util.log just for the logger,
but log.py was importing Gtk at module scope solely to annotate the
LOG_BUFFERS dict value type. That forced every wrapper invocation to load
the Gtk typelib (and transitively libgtk-4.so.1).

Under games configured with older bundled Wine runtimes, the wrapper's
LD_LIBRARY_PATH puts an older libgstvideo ahead of the system one, and
libgtk-4.so.1 then fails to resolve gst_video_info_dma_drm_to_video_info,
which surfaces in Python as an AssertionError in gi.overrides.Gdk
(g_type != TYPE_NONE) and crashes the wrapper before the game can start.

Move Gtk under TYPE_CHECKING and quote the annotation so log.py's runtime
import graph stays GTK-free. Process monitors have no UI need for Gtk.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 15:40:02 -04:00
Daniel Johnson
f47ef1131d Wait for component updates before launching games. Resolves #6702
Launching while runtime or wine component downloads are still in
progress can crash games or corrupt their Wine prefix. Game.launch()
now defers to LaunchUIDelegate.wait_for_component_updates(); the
GUI delegate shows a modal that auto-resolves when the download queue
empties, with Cancel and Launch Anyway as overrides.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 14:58:22 -04:00
Daniel Johnson
983f33f861 Merge pull request #6709 from mbarrio/master
Update Spanish translation
2026-05-23 10:33:04 -04:00
Miguel Barrio Orsikowsky
65c8f8bf18 Update Spanish translation 2026-05-23 14:25:37 +02:00
Daniel Johnson
a69bfe26c3 Defer runner option callables to avoid eager import-time failures
Two runner classes evaluated host-probing helpers in their class
bodies: atari800 called get_resolutions() (which hits xrandr) and
wine called is_fsync_supported() (which runs ctypes futex probes).
Both ran at module-import time during runner discovery, so any
exception from those probes crashed Lutris startup before the GUI
even loaded — the trigger behind #6707.

"choices" and "default" already support callables via
_evaluate_option; pass the functions themselves and let the widget
generator call them on demand. get_fsync_support is already
@cache_single, so the bool default doesn't repeat work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:59:07 -04:00
Daniel Johnson
fe74f21f8b Guard xrandr calls when the binary is missing (#6707)
LINUX_SYSTEM.get() can return None when the command isn't installed,
but its annotation falsely claimed str. That lie hid a crash at
startup on systems without xrandr (e.g. Wayland-only Fedora), where
turn_off_except() and change_resolution() would hand [None] to
subprocess and die in os.path.dirname(None).

Fix the return type to str | None and have both callers early-return
when xrandr isn't available. _get_vidmodes() was already guarded by
an earlier fix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:44:31 -04:00
Daniel Johnson
e269d439e4 Update installed DLCs alongside base game in GOG depot updates
get_update_installers now enumerates goggame-*.info files in the install
dir to find which DLCs are currently installed, and passes them to
gogdl as a comma-separated --dlcs list. This keeps installed DLCs in
sync with base-game patches without surprising the user by pulling in
every owned-but-not-installed DLC (which is what --with-dlcs alone
would do).

DLC enumeration uses goggame-<productId>.info files, which GOG writes
for every installed product regardless of installation method — Lutris
doesn't track DLCs as separate DB rows (extends-based installers skip
creating a game entry), so the install directory is the authoritative
source.

Factored the goggame info discovery into find_gog_config_dir on
lutris.util.gog, extending it to handle Linux depot installs (which
nest the info file under '<gameName>/game/') as well as the Windows
and Linux offline layouts already covered. The duplicate version in
commands.py is gone.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 10:31:27 -04:00
Daniel Johnson
5695d54756 Add depot-based DLC install for GOG games
DLCs for depot-installed games previously fell through to the legacy
offline-installer path, which assumes the base game's goginstaller files
and registry state exist — neither holds for depot installs, so the DLC
install either silently no-op'd or required selecting a .sh/.exe by hand.

get_dlc_installers now branches on is_depot_installed and emits installers
that delegate to gogdl with `--with-dlcs --dlcs <id>`. Both flags are
required: gogdl's V2 manager gates ownership lookup on --with-dlcs before
consulting --dlcs, so passing only --dlcs silently returns "Owned dlcs []"
and nothing downloads. A preserve_manifest flag on gogdl_setup keeps the
existing base-game manifest so gogdl diffs old→new and only fetches the
DLC files.

Along the way:
- is_depot_installed now takes install_dir + runner and detects both the
  V2 (Windows, shared cache) and Linux (in-tree .gogdl-linux-manifest)
  manifest layouts. The Linux marker is nested under gogdl's
  installDirectory subfolder, so we check one level down too.
- Factored manifest deletion into clear_stale_manifest so both platforms
  get the stale-install safety, not just Windows.
- _find_gog_config_path no longer uses recursive glob, which followed
  symlink cycles inside Wine prefixes and hung post-install.
- Log gogdl stdout (previously discarded) and tag stderr lines so
  progress parsing can see output from either stream.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 10:11:37 -04:00
Daniel Johnson
206a781e70 Merge pull request #6692 from Abzaek/fix/db-context-manager-transaction
fix: correct transactional behavior and resource leak in db_cursor
2026-05-17 07:34:45 -04:00
Abdulazez Zeinu Ali
df2537e417 style: fix line length in __exit__ signature 2026-05-16 23:36:59 +03:00
Abdulazez Zeinu Ali
9762bed05a fix: commit only on success and ensure close in db_cursor context manager
- Only commit when the with-block exits normally (exc_type is None)
- Roll back on exception to preserve transactional semantics
- Wrap in try/finally so close() is always called even if
  commit() or rollback() raises
2026-05-16 23:28:52 +03:00
Daniel Johnson
28cafbb2fd Treat /var/home/<user>/* as removable on Fedora Atomic
On Fedora Silverblue, Kinoite, Bazzite, etc., /home is a symlink to
/var/home, and accounts created after first-boot often have $HOME set
to /var/home/<user> directly. Game directories stored under that path
were misclassified as protected by is_removeable(), so the uninstall
dialog refused to delete them.

Extend the existing /var special case to fold /var/home/<user>/* into
the /home/<user>/* logic, preserving the same protections for the user
root and PROTECTED_HOME_FOLDERS entries. Fixes #6685.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 19:59:38 -04:00
Daniel Johnson
de576c6991 Recheck a single game's missing status on GAME_UPDATED / GAME_START
MissingGames.update_all_missing() is too expensive to run on every
event for large libraries (it stat()s every game), so it was wired only
to sidebar navigation. That left covers showing stale "missing" badges
between sidebar visits.

Add a synchronous update_one_missing(game_id, path=None) — one stat()
call, fires .updated only on transition — and register it against
GAME_UPDATED and GAME_START in MissingGames.__init__. The handler
reads the live path off the Game rather than the path cache, since
add_to_path_cache() is itself a GAME_UPDATED handler and the cache
isn't guaranteed to have been refreshed yet when ours fires.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-09 10:29:50 -04:00
Daniel Johnson
56ef443ca1 Accept either WebKit2 4.0 or 4.1 typelib in RPM Requires
The openSUSE branch hard-required typelib-1_0-WebKit2-4_0, but the
codebase already supports either 4.0 or 4.1 (matching the alternation
in debian/control). 4.0 is reaching end of life, so a hard pin to it
prevents installs on systems that have moved on to 4.1.

Use a rich `(A or B)` Requires so either typelib satisfies the dep.

Fixes #6669.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 06:27:43 -04:00
Daniel Johnson
9a29a956b0 Add glib-networking to .deb and .rpm dependencies
Without glib-networking, GIO has no TLS backend, so HTTPS in the
embedded WebKit login views fails silently. This breaks every web
auth flow (EA, GOG, Steam web login, etc.) on systems where it
isn't already pulled in transitively.

Reported in #6664 against the EA App login.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-29 16:41:16 -04:00
Daniel Johnson
601da62dc0 Rescan icon theme after runtime updates complete
Runtime icons are downloaded on startup, but GTK's IconTheme doesn't
pick up the new files until something triggers a rescan — previously
that only happened when something like a sidebar hover bumped the
theme. Icons picked via StockIconImage would sit on their fallback
until then.

Hook into install_runtime_component_updates' completion to call
Gtk.IconTheme.get_default().rescan_if_needed(), which emits the
"changed" signal StockIconImage listens to, so widgets swap over to
the real icons as soon as the download lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 16:57:02 -04:00
Daniel Johnson
4bfd36da50 Use StockIconImage for has_stock_icon-picked icons
The runner install dialog and saved-search edit box picked icons via
has_stock_icon, then handed the chosen name to Gtk.Image/Gtk.Button.
Switch both to StockIconImage so the selection re-runs on theme
changes.

In SearchFiltersBox._add_entry_box, drop the inner "no button if no
candidate resolves" branch — it was a bug; the button should still
appear (with StockIconImage's generic fallback) whenever the caller
asked for one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 16:49:11 -04:00
Daniel Johnson
313fee5f0e Add StockIconImage to refresh icon choice on theme changes
A Gtk.Image that picks the first available icon from a candidate list
and re-evaluates the selection when the icon theme changes, so an icon
gained or lost by a theme switch is picked up without a restart.

Used in LutrisSidebar.get_sidebar_icon, SidebarRow.create_button_box,
and the search filters button in LutrisWindow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 16:40:04 -04:00
Daniel Johnson
6ff0ed2fa1 Fall back to g_file_trash_async() when Trash portal refuses
xdg-desktop-portal 1.20.4's TrashFile rejects otherwise-trashable files
on some Btrfs layouts (nested subvolumes for /home vs /home/$USER),
returning "No suitable trash directory found". Users on affected
systems can't delete artwork, installers, or other files from Lutris.

Keep the portal as the primary path (still potentially required under Flatpak), but
fall back to Gio.File.trash_async() when the bus proxy can't be
acquired, the D-Bus call errors out, or the portal returns failure.
Downgrade the now-recoverable conditions from error to warning.

Resolves #6647

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 16:53:09 -04:00
Daniel Johnson
cfd6c811ac Merge pull request #6655 from upperpalaeolithic/fix/download-cache-state-protection-typo
fix(download_cache): correct CacheState typo in is_safe_to_delete
2026-04-20 18:01:55 -04:00
Tom
c13c9f55a8 fix(download_cache): correct CacheState typo in is_safe_to_delete
`is_safe_to_delete` listed `CacheState.DOWNLOADING` twice in the
protection tuple, omitting `CacheState.DOWNLOADED`. Files in the
DOWNLOADED state (download complete, installation pending) were
therefore not protected and could be deleted before installation
finished.

Regression introduced in #6525.
2026-04-20 11:05:16 -04:00
Mathieu Comandon
5aa61a238e Add "Kill all Wine processes" menu option
Adds a nuclear option to kill all Wine processes system-wide,
useful when games or launchers leave orphan processes behind.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:00:28 -07:00
Daniel Johnson
33fd2a286e Merge pull request #6644 from ItsAllAboutTheCode/unit-test-circular-import-fix
Fixed Lutris UnitTest when run on github
2026-04-11 10:52:11 -04:00
ItsAllAboutTheCode
80e6c46ee8 Fixed Lutris UnitTest when run on github
The primary causes of the issue is the manipulation of how the gog_cloud.py and gog_cloud_hooks.py was getting imported in UnitTest via the importlib mechanism. That seemed to have broken the resolution of paths to `lutris.services.*`.
As the GTK issues that it was trying to side step has been fixed at the `tests` root level, the workaround is no longer needed.

Additional changes have been added to help harden any potentially UnitTest environment and  circular import issues.
* Updated the github unit test run to use a virtual env to install the package dependencies and run the test
* Moved the imports of `lutris.services` subpackage to the method calls inside of the `__init__.py` script which will defer any potential circular imports until after the `lutris/services/__init__.py` script is fully imported.
2026-04-11 09:47:37 -05:00
Daniel Johnson
a2676385d8 Merge pull request #6642 from duthils/mypy-gui-dialogs-init
mypy: add typing annotations to gui/dialogs/__init__.py
2026-04-11 09:48:31 -04:00
Sebastien Duthil
91ebe9ebfa gui/dialogs/__init__: remove pylint ignores for unused argument 2026-04-09 21:06:58 -04:00
Sebastien Duthil
d6a43b181f mypy: remove breaking stubs
Why:

* The stubs would need to call super().run(), but this causes the same
  error that the stubs were trying to resolve, i.e. `Call to untyped
  function "run" in typed context`
2026-04-09 20:54:14 -04:00
Sebastien Duthil
a6105d2a7c mypy: add typing annotations to gui/dialogs/__init__.py 2026-04-09 09:25:53 -04:00
Daniel Johnson
f3c6e98d5e Merge pull request #6636 from barttran2k/contribai/fix/security/bare-except-clause-swallows-all-exceptio
Security: Bare except clause swallows all exceptions including SystemExit and KeyboardInterrupt
2026-04-07 19:27:34 -04:00
Daniel Johnson
daf63978e2 Tighten up the type checking in a few places 2026-04-07 19:20:59 -04:00
Daniel Johnson
29ba9475c5 Merge pull request #6631 from duthils/mypy-dialog-log-delegates
mypy: add typing annotations to gui/dialogs/log and delegates
2026-04-07 19:12:24 -04:00
Sebastien Duthil
0d0e71f01e mypy: add some ignores 2026-04-07 18:40:01 -04:00
Daniel Johnson
9aae7aa463 Remove dead code 2026-04-07 16:28:09 -04:00
Mathieu Comandon
64f7e7aa9e Remove clean_configs script 2026-04-07 12:25:47 -07:00
Daniel Johnson
68803eff25 Merge pull request #6638 from barttran2k/contribai/fix/security/typeerror-bug-membership-test-on-module-
Security: TypeError bug: membership test on module object instead of string
2026-04-07 06:38:39 -04:00
Trần Bách
ded86c0977 fix(security): typeerror bug: membership test on module object in
On line 32, `"-" in locale` performs a membership test on the `locale` *module* (imported at line 3), not on the `user_locale` string variable. In Python 3, this raises `TypeError: argument of type 'module' is not iterable` at runtime when `get_lang_and_country()` is called, crashing the function. This is both a bug and a reliability issue.

Affected files: i18n.py

Signed-off-by: Trần Bách <45133811+barttran2k@users.noreply.github.com>
2026-04-07 17:36:07 +07:00
Trần Bách
991ab33fe2 fix(security): bare except clause swallows all exceptions includi
The bare `except:` clause on line 18 catches all exceptions, including `SystemExit`, `KeyboardInterrupt`, and other critical exceptions. This can mask serious errors and prevent the process from being properly terminated. An attacker who can influence the settings file could cause unexpected behavior that goes undetected.

Affected files: migrate_hidden_ids.py

Signed-off-by: Trần Bách <45133811+barttran2k@users.noreply.github.com>
2026-04-07 16:01:55 +07:00
Sebastien Duthil
58df97be19 mypy: simplify typing annotations for gui/dialogs/delegates.py 2026-04-06 21:31:08 -04:00
Daniel Johnson
e062d7a0a1 Surface umu runtime setup as a download-queue progress box
Before, clicking Play on a Proton game while umu was fetching GE-Proton
or the steamrt3 runtime looked like nothing was happening — the game
stayed on "Launching" for minutes with no indication that a background
download was underway (issue #6632).

Add a Game.launch_status property that fires a new GAME_LAUNCH_STATUS
notification when assigned. Runners opt in via a new
Runner.attach_log_handlers hook; the wine runner installs a umu log
parser (lives in lutris.util.wine.proton) when the wine exe is umu,
which watches for "Downloading", "Extracting" and "Checking updates"
INFO lines and clears on "Using <Proton>". LutrisWindow mirrors the
status into the existing download queue as a pulsing ProgressBox, which
auto-removes itself once the status clears.

Also widens ProgressInfo.progress to float | None to match the
implementation, which already pulses when it's None.
2026-04-06 17:37:24 -04:00
Daniel Johnson
cc658b5340 Remove unused module. This was really not pulling its weight. 2026-04-06 16:45:37 -04:00
Mathieu Comandon
cf3dd08f78 Remove issuelocker script 2026-04-06 11:54:51 -07:00