diff --git a/requirements.txt b/requirements.txt index fa8ac4b..107b1e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ gevent-eventemitter ~= 2.1 protobuf ~= 5.29.3 wsproto ~= 1.2.0 pillow == 12.1.1 +PyGObject == 3.56.2 diff --git a/steamiconfixer/compat/linux.py b/steamiconfixer/compat/linux.py index 4d25649..1158817 100644 --- a/steamiconfixer/compat/linux.py +++ b/steamiconfixer/compat/linux.py @@ -23,15 +23,21 @@ from steam.enums.emsg import EMsg from PIL import Image from pathlib import Path +import subprocess from ..types import Icon +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + usage = """--------------------------------------------- Usage: sif [path to icons] Examples: +sif ~/.local/share/applications sif ~/Desktop sif ~/Desktop/Steam Games sif ~/.local/share/applications $HOME/.icons @@ -43,15 +49,25 @@ Incompatible operating system (exit code 100) """ +gtk_sizes = [16, 32, 48, 64, 128, 256, 512, 1024, 2048] +gtk_user_path = os.path.expandvars('$HOME/.local/share/icons/hicolor') +gtk_icon_theme = Gtk.IconTheme.get_default() + steamapi = SteamClient() steamapi.anonymous_login() +def refreshiconcache(): + print("Refreshing icon cache") + Path(gtk_user_path).touch() + subprocess.run(["gtk-update-icon-cache"]) + def isshortcut(filename): return filename.name.endswith(".desktop") def setupiconpath(filename): if len(filename) == 0: - filename = os.path.expandvars("$HOME/.icons") + # Empty path signifies use Gtk icon paths + return '' if not os.path.exists(filename): os.makedirs(filename) return filename @@ -71,14 +87,24 @@ def readshortcut(filename): iconpathmatch = re.search(r"Icon=([^\n]*)\n", contents) if steamidmatch == None or iconpathmatch == None: - print(colored(filename.name + ": Shortcut doesn't appear to be a Steam shortcut. Skipping.", "yellow")) + print(colored(filename.name + ": Shortcut doesn't appear to be a Steam shortcut. Skipping.", "blue")) return None steamid = steamidmatch.group(1) iconpath = iconpathmatch.group(1) # Check if the icon exists - if iconpath != "steam" and os.path.exists(iconpath): + if iconpath == "steam": + pass + elif not Path(iconpath).is_absolute(): + # Ex: "steam_icon_440" can be valid as long as "steam_icon_440.png" exists within a valid icon search directory + gtk_icon_exists = gtk_icon_theme.has_icon(iconpath) + gtk_icon = gtk_icon_theme.lookup_icon(iconpath, 256, 0) + if gtk_icon_theme.has_icon(iconpath): + print(colored(filename.name + ": Icon file is present, nothing needs to be done. Skipping.", "green")) + return None + elif os.path.exists(iconpath): + # Absolute icon paths to anywhere are also valid, however certain directories are recommended, such as $HOME/.icons if not os.path.isfile(iconpath): print(colored(filename.name + ": Icon path is a directory. This error must be fixed manually. Skipping.", "red")) return None @@ -100,10 +126,25 @@ def readshortcut(filename): return Icon(steamid, iconpath, iconname, filename) def writeicon(icon, response, iconpath): + # Empty iconpath means use Gtk icons img = Image.open(io.BytesIO(response.content)) - savepath = Path(os.path.join(iconpath, "steamicon_" + Path(icon.name).stem + ".png")).resolve() - img.save(savepath, "PNG") - return str(savepath) + if len(iconpath) == 0: + name = "steam_icon_" + icon.steamid + width, height = img.size + for x in gtk_sizes: + if width < x and height < x: + break + full_dir = os.path.join(gtk_user_path, str(x) + "x" + str(x) + "/apps") + if not os.path.exists(full_dir): + os.makedirs(full_dir) + thumb = img.copy() + thumb.thumbnail((x, x), Image.LANCZOS) + thumb.save(os.path.join(full_dir, name + ".png"), "PNG") + return name + else: + savepath = Path(os.path.join(iconpath, "steam_icon_" + icon.steamid + ".png")).resolve() + img.save(savepath, "PNG") + return str(savepath) def updateshortcuts(icon, searchpath, iconpath): with open(icon.shortcutfilename, "r") as file: diff --git a/steamiconfixer/compat/windows.py b/steamiconfixer/compat/windows.py index 3bcbe47..75d3c3f 100644 --- a/steamiconfixer/compat/windows.py +++ b/steamiconfixer/compat/windows.py @@ -37,6 +37,10 @@ Incompatible operating system (exit code 100) """ +def refreshiconcache(): + # Unneeded on Windows + pass + def isshortcut(filename): return filename.name.endswith(".url") @@ -59,7 +63,7 @@ def readshortcut(filename): iconnamematch = re.search(r"\\([^\n\\]*)\n", contents) if steamidmatch == None or iconpathmatch == None or iconnamematch == None: - print(colored(filename.name + ": Shortcut doesn't appear to be a Steam shortcut. Skipping.", "yellow")) + print(colored(filename.name + ": Shortcut doesn't appear to be a Steam shortcut. Skipping.", "blue")) return None steamid = steamidmatch.group(1) diff --git a/steamiconfixer/main.py b/steamiconfixer/main.py index 25a049e..fae7cfc 100644 --- a/steamiconfixer/main.py +++ b/steamiconfixer/main.py @@ -25,8 +25,10 @@ from steamiconfixer.compat import compat icons = {} -baseurl = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/" -# baseurl = "https://shared.fastly.steamstatic.com/community_assets/images/apps/" # Alternative URL +baseurls = [ + "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/", + "https://shared.fastly.steamstatic.com/community_assets/images/apps/", + ] license = """\nSteam Icon Fixer, Version 1.1 Copyright (C) 2026 Liam "AyesC" Hogan @@ -75,6 +77,8 @@ # Create list of icons print("Searching for valid Steam shortcuts in " + searchpath + "...") +compat.refreshiconcache() + for filename in os.scandir(searchpath): # Igore directories or any files that are not .url or .desktop files if not filename.is_file(): @@ -118,14 +122,29 @@ errors = 0 +current_baseurl_index = 0 +current_baseurl = baseurls[current_baseurl_index] + for steamid, icon in icons.items(): # Create the URL and make a request - url = baseurl + steamid + "/" + icon.name + url = current_baseurl + steamid + "/" + icon.name response = requests.get(url) + while not response.ok: + print(colored("Got code " + str(response.status_code) + " at CDN " + current_baseurl, "red")) + current_baseurl_index = current_baseurl_index + 1 + if current_baseurl_index < len(baseurls): + current_baseurl = baseurls[current_baseurl_index] + print(colored("Retrying with CDN " + current_baseurl, "red")) + url = current_baseurl + steamid + "/" + icon.name + response = requests.get(url) + else: + print(colored("All CDNs failed to respond", "red")) + sys.exit(0) + # Check if response was ok if not response.ok: - print(colored(steamid + ": Failed to download icon. Response code was " + response.status_code + ".", "red")) + print(colored(steamid + ": Failed to download icon. Response code was " + str(response.status_code) + ".", "red")) errors = errors + 1 continue @@ -149,3 +168,5 @@ print(colored(steamid + ": Downloaded and saved successfully.", "green")) print("\nDownloading completed with " + str(errors) + " errors. Refer to the above log for details.") + +compat.refreshiconcache()