diff --git a/CHANGELOG.md b/CHANGELOG.md index 74cd714..8223b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed - Fix crash caused by the Steam shortcut configuration file containing extra data after the VDF section + - Fix differently sized custom app icons breaking the layout in the app selector ## [1.10.3] - 2023-05-06 ### Added diff --git a/setup.cfg b/setup.cfg index a6bb746..0f94fd8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ include_package_data = True install_requires = setuptools # Required for pkg_resources vdf>=3.2 + Pillow setup_requires = setuptools-scm python_requires = >=3.6 diff --git a/src/protontricks/gui.py b/src/protontricks/gui.py index 4148ee2..b30e7b6 100644 --- a/src/protontricks/gui.py +++ b/src/protontricks/gui.py @@ -10,9 +10,14 @@ from subprocess import PIPE, CalledProcessError, run import pkg_resources +from PIL import Image from .config import get_config from .flatpak import get_inaccessible_paths +from .util import get_cache_dir + +APP_ICON_SIZE = (32, 32) + __all__ = ( "LocaleError", "get_gui_provider", "select_steam_app_with_gui", @@ -60,19 +65,47 @@ def _get_appid2icon(steam_apps, steam_path): ) ) - icon_dir = steam_path / "appcache" / "librarycache" - existing_names = [path.name for path in icon_dir.glob("*")] + steam_icon_dir = steam_path / "appcache" / "librarycache" + existing_names = [path.name for path in steam_icon_dir.glob("*")] + + protontricks_icon_dir = get_cache_dir() / "app_icons" + protontricks_icon_dir.mkdir(exist_ok=True) appid2icon = {} for app in steam_apps: # Use library icon for Steam apps, fallback to placeholder icon # for non-Steam shortcuts and missing icons - appid2icon[app.appid] = ( - icon_dir / f"{app.appid}_icon.jpg" - if f"{app.appid}_icon.jpg" in existing_names - else placeholder_path - ) + icon_cache_path = protontricks_icon_dir / f"{app.appid}.jpg" + original_icon_path = steam_icon_dir / f"{app.appid}_icon.jpg" + + # What path to actually use for the app selector. Defaults to + # the original icon path + final_icon_path = placeholder_path + + icon_exists = f"{app.appid}_icon.jpg" in existing_names + resize_icon = False + + # Resize icons that have a non-standard size to ensure they can be + # displayed consistently in the app selector + if icon_exists: + final_icon_path = original_icon_path + + with Image.open(original_icon_path) as img: + resize_icon = img.size != APP_ICON_SIZE + + # Resize icons that have a non-standard size to ensure they can + # be displayed consistently in the app selector. + if resize_icon: + logger.info( + "App icon %s has unusual size, resizing", + original_icon_path + ) + resized_img = img.resize(APP_ICON_SIZE) + resized_img.save(icon_cache_path) + final_icon_path = icon_cache_path + + appid2icon[app.appid] = final_icon_path return appid2icon diff --git a/tests/test_gui.py b/tests/test_gui.py index 13d8c8f..e933933 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -2,6 +2,7 @@ import pytest from conftest import MockResult +from PIL import Image from protontricks.gui import (prompt_filesystem_access, select_steam_app_with_gui, @@ -100,8 +101,9 @@ def test_select_game_icons( ] # Create icons for game 1 and 3 - (steam_dir / "appcache" / "librarycache" / "10_icon.jpg").touch() - (steam_dir / "appcache" / "librarycache" / "30_icon.jpg").touch() + for appid in (10, 30): + Image.new("RGB", (32, 32)).save( + steam_dir / "appcache" / "librarycache" / f"{appid}_icon.jpg") gui_provider.mock_stdout = "Fake game 2: 20" select_steam_app_with_gui(steam_apps=steam_apps, steam_path=steam_dir) @@ -112,6 +114,41 @@ def test_select_game_icons( assert b"icon_placeholder.png\nFake game 2" in input_ assert b"librarycache/30_icon.jpg\nFake game 3" in input_ + def test_select_game_icons_ensure_resize( + self, gui_provider, steam_app_factory, steam_dir, home_dir): + """ + Select a game using the GUI. Ensure custom icons with sizes other than + 32x32 are resized. + """ + steam_apps = [ + steam_app_factory(name="Fake game 1", appid=10) + ] + + Image.new("RGB", (64, 64)).save( + steam_dir / "appcache" / "librarycache" / "10_icon.jpg" + ) + + gui_provider.mock_stdout = "Fake game 1: 10" + select_steam_app_with_gui(steam_apps=steam_apps, steam_path=steam_dir) + + # Resized icon should have been created with the correct size and used + resized_icon_path = \ + home_dir / ".cache" / "protontricks" / "app_icons" / "10.jpg" + assert resized_icon_path.is_file() + with Image.open(resized_icon_path) as img: + assert img.size == (32, 32) + + input_ = gui_provider.kwargs["input"] + + assert f"{resized_icon_path}\nFake game 1".encode("utf-8") in input_ + + # Any existing icon should be overwritten if it already exists + resized_icon_path.write_bytes(b"not valid") + select_steam_app_with_gui(steam_apps=steam_apps, steam_path=steam_dir) + + with Image.open(resized_icon_path) as img: + assert img.size == (32, 32) + def test_select_game_no_choice( self, gui_provider, steam_app_factory, steam_dir): """