#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2020, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging; logger = logging.getLogger(__name__); logger.info("import") from os import getenv import os import pathlib import re """ https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#icon_lookup $HOME/.icons (for backwards compatibility) $XDG_DATA_DIRS/icons /usr/share/pixmaps """ SEARCH_DIRECTORIES = [pathlib.Path(pathlib.Path.home(), ".icons")] XDG_DATA_DIRS = getenv("XDG_DATA_DIRS") #colon : separated, most likely empty if XDG_DATA_DIRS: SEARCH_DIRECTORIES += [pathlib.Path(p, "icons/hicolor") for p in XDG_DATA_DIRS.split(":")] SEARCH_DIRECTORIES += [pathlib.Path(p, "icons/scalable") for p in XDG_DATA_DIRS.split(":")] else: #If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used. SEARCH_DIRECTORIES += [pathlib.Path(p, "icons/hicolor") for p in "/usr/local/share/:/usr/share/".split(":")] SEARCH_DIRECTORIES += [pathlib.Path(p, "icons/scalable") for p in "/usr/local/share/:/usr/share/".split(":")] SEARCH_DIRECTORIES.append(pathlib.Path("/usr/share/pixmaps")) SEARCH_DIRECTORIES = set(p.resolve() for p in SEARCH_DIRECTORIES) #resolve follows symlinks, set() makes it unique def run_fast_scandir(dir, ext): """ https://stackoverflow.com/questions/18394147/recursive-sub-folder-search-and-return-files-in-a-list-python """ subfolders, files = [], [] try: for f in os.scandir(dir): if f.is_dir(): subfolders.append(f.path) if f.is_file(): if os.path.splitext(f.name)[1].lower() in ext: files.append(f.path) for dir in list(subfolders): sf, f = run_fast_scandir(dir, ext) subfolders.extend(sf) files.extend(f) except PermissionError: pass except FileNotFoundError: pass return subfolders, files def _buildCache()->set: result = [] for basePath in SEARCH_DIRECTORIES: forget, files = run_fast_scandir(basePath, [".png", ".xpm", ".svg"]) result += files #Convert str to real paths result = [pathlib.Path(r) for r in result if pathlib.Path(r).is_file()] return set(result) global _cache _cache = None def updateCache(serializedCache:list=None): global _cache if serializedCache: #Convert str to real paths logger.info("Filling icon cache with previously serialized data") _cache = set([pathlib.Path(r) for r in serializedCache if pathlib.Path(r).is_file()]) else: #Already real paths as a set logger.info("Building icon cache from scratch") _cache = _buildCache() logger.info("Icon cache complete") def getSerializedCache()->list: #list of strings, not paths. This is for saving in global system config for faster startup global _cache return [str(p) for p in _cache] rePattern = re.compile("\d+x\d+") #we don't put .* around this because we are searching for the subpattern def findIconPath(executableName:str)->list: """Return order is: svg first, then highest resolution first, then the rest unordered. so you can use result[0] for the best variant. It is not guaranteed that [1], or even [0] exists. This is not a sorted list, these extra variants are just added to the front of the list again""" global _cache if not _cache: raise ValueError("You need to call updateCache() first") svg = None bestr = 0 #resolution best = None result = [] for f in _cache: if f.stem == executableName: if f.suffix == ".svg": svg = f else: match = re.search(rePattern, str(f)) #find resolution dir like /48x48/ if match: resolutionAsNumber = int(match.group().split("x")[0]) if resolutionAsNumber > bestr: #new best one bestr = resolutionAsNumber best = f result.append(f) if best: result.insert(0, best) if svg: result.insert(0, svg) return result if __name__ == "__main__": """Example that tries to find a few icons""" updateCache() print("Search paths:") print(SEARCH_DIRECTORIES) print() for exe in ("zynaddsubfx", "patroneo", "jack_mixer", "carla", "ardour6", "synthv1"): r = findIconPath(exe) if r: print (f"{exe} Best resolution: {r[0]}") else: print (f"{exe}: No icon found ")