Music production session manager https://www.laborejo.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

186 lines
6.1 KiB

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, 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 <http://www.gnu.org/licenses/>.
"""
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
"""
EXTENSIONS = [".png", ".xpm", ".svg"]
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.append(pathlib.Path("/usr/share/icons")) #for icons wrongly put directly there.
SEARCH_DIRECTORIES.append(pathlib.Path("/usr/local/share/icons")) #for icons wrongly put directly there.
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, EXTENSIONS)
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:
"""
Parameter executableName can be a direct icon name as well, from the .desktop icon path.
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
#Did we get an icon name directly? Remove the extension
#For example "ams_32.xpm" becomes "ams_32"
exeAsPath = pathlib.Path(executableName)
if exeAsPath.suffix in EXTENSIONS:
executableName = exeAsPath.stem
#for ext in EXTENSIONS: #all extensions
# if executableName.endswith(ext):
# executableName = executableName[:-4]
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)
if not result:
logger.warning(f"Did not find an icon for {executableName}")
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", "ams_32.xpm"):
r = findIconPath(exe)
if r:
print (f"{exe} Best resolution: {r[0]}")
else:
print (f"{exe}: No icon found ")