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.
The Vita3K emulator launches Vita titles using their Title ID and not a file path.
Therefore the Vita3K runner is setup to accept the title ID field.
However that title ID field was set as the 'main_file' option in the config yaml that gets generated when a Vita entry is added.
The missing installed title check in Lutris invokes the game.py [get_path_from_config](4620e1ba2f/lutris/game.py (L643)) which treats the `main_file` key as a file path.
As the Vita title ID is not a valid file path, the Missing Games check would flag all Vita installed titles as missing.
The fix here is to change the key from 'main_file' to 'title_id' so that the field is not treated as a file path.
This fixes an error about importing `GdkPixbuf` without specify a version.
Also added a `__init__.py` to the `tests` directory, so that it is seen as a package which allows the python `unittest` module to discover test within it.
This means that very module must be explicitly loaded by various tests that need the 'gi.requires_versions' part- they no longer sometimes get this by accident (sometimes).
In turn, _test_cloud_save_progress will no longer stub stuff in sys.modules, which is awful- it breaks later tests. That in turns makes it much chattier since it now actually logs stuff. But it seems to pass for all that.
Yikes!
Resolves#6570
Much research and drudgery done by Claude Code 🤖 before it clocked out.
With game IDs converted to str inside games.py query functions,
these manual str() wrappers at callsites are no longer needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously returned an empty dict, which masked missing-game errors and
was inconsistent with the Optional[DbGameDict] return type of
get_game_for_service. Callers that use the result as a fallback dict
(Game.__init__, Runner.__init__, web/pico8 runners) now use 'or {}'.
Callers that need a real game (install_updates, install_dlc,
on_game_duplicate, generate_script, uninstall sync) now guard against
None and bail out instead of crashing with a KeyError.
Also fixes redundant str() in add_or_update and wrong param type on
get_matching_game.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MagicMock returns a truthy object for unset attributes, so
game.skip_cloud_sync was truthy, causing _sync_location to
auto-resolve conflicts as uploads instead of showing the dialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrate heroic-gogdl as a runtime component to download GOG games
directly from GOG's depot/CDN system (the same backend GOG Galaxy uses),
offering faster downloads and incremental updates compared to offline
installers.
Changes:
- New `gogdl_setup` installer command for depot downloads
- Auth bridge between Lutris GOG tokens and gogdl's format
- Depot installer offered as primary, offline installer as fallback
- Post-install game detection from goggame-*.info (DOSBox, ScummVM,
Wine, native)
- Progress bar in the installer window during depot downloads
- Incremental depot updates for depot-installed games
- Handle expired GOG tokens gracefully (auto re-login via
AuthTokenExpiredError)
- Wine prefix set to $GAMEDIR/pfx, separate from game files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace CloudSyncProgressDialog with CloudSyncProgressAdapter that bridges
the sync worker thread's progress_callback to ProgressBox polling in the
existing sidebar DownloadQueue. The stop button provides skip-sync capability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace object.__new__() with CloudSyncProgressDialog.__new__() to
fix Python 3.13+ compatibility (object.__new__ rejects classes whose
__init__ takes extra parameters)
- Gzip-compress mock response data in download_file tests to match the
actual gzip.decompress() call in the implementation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thread a progress callback from the sync dialog through the GOG cloud
hooks into the sync engine so per-file progress is reported. The dialog
now shows a real progress bar (e.g. "3 / 10") with the current filename
(middle-ellipsized to prevent dialog resizing).
Replace blocking cloud sync calls during game launch and quit with a
non-blocking progress dialog (CloudSyncProgressDialog). The sync work
runs on a background thread while a pulsing progress bar keeps the GTK
main loop alive, preventing the window manager from flagging Lutris as
'not responding'.
Changes:
- New: lutris/gui/dialogs/cloud_sync_progress.py
Modal-style dialog with pulsing progress bar, status labels, and
a 'Skip Sync' button for users who want to launch immediately.
- Modified: lutris/game.py
Pre-launch sync (configure_game) and post-quit sync (on_game_quit)
now use the progress dialog for GOG games instead of blocking calls.
- New: tests/test_cloud_sync_progress.py
14 unit tests covering dialog construction, callbacks, cancel/skip
behaviour, pulse lifecycle, and sync completion handling.
Passes ruff and mypy static analysis.
Refactored the game_common class to split out the Game Info panel UI to it's own file.
The Game Info panel has been updated to support the "Advanced Options" switch.
When the Advanced Options switch is selected, several text entries become available with the path to the art images on the filesystem, as well as a Open Folder location button to jump to the location of the art image.
This is the part of implementing the browse UI option for issue 4213 to navigate to the UI image location.
My MyPy complains about the use of a Python 3.9 feature (why?) and then does not detect any type errors! I fix the test so MyPy will detect my actual type annotation error.
And then I fix that.
This will just leave the list alone if 'Installed games only' is off and the filter text is empty.
Also, it no longer strips the filter text over and over again, but just once at the start. It still has to strip each game name, but it's half the strippings now.
Also, fix some ruff issues.
Truncating the minutes part of a playtime was unstable; format(parse(x) didn't result in 'x'; rounding fixes that. But the unit tests were impacted, so let's update them.
Resolves#5191
This was the only option to use the 'scope' feature, and in 0.5.14 it was available in system and runner options, but not games.
This fix restores that behavior.
The trick is to pass the config_level through to the various option boxes, not just the config_section. The first is which dialog you are in (roughly) and the second is which tab.
add_url_tags() isn't correct- it does not handle escaping and can't be safely combined with gtk_safe(), as is done in two places.
But URLs can explicitly contain &, and that must be escaped. Text not inside URLs needs to be escaped, but escaping before or after the anchors breaks the URLs.
This function splits the text up into URLs and not-URLs and handles each case.
This code looks for a '.lutris_compat.json' file at the root of a
component's version's directory.
This contains, in JSON, the minimum Lutris version required. If Lutris
finds this to be too late, it will try earlier versions.
The code is messy and this could download a lot of versions, since
it tries them one at a time. But it's a start.