/usr/share/pyshared/xdg/IconTheme.py is in python-xdg 0.19-3ubuntu2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | """
Complete implementation of the XDG Icon Spec Version 0.8
http://standards.freedesktop.org/icon-theme-spec/
"""
import os, sys, time
from xdg.IniFile import *
from xdg.BaseDirectory import *
from xdg.Exceptions import *
import xdg.Config
class IconTheme(IniFile):
"Class to parse and validate IconThemes"
def __init__(self):
IniFile.__init__(self)
def __repr__(self):
return self.name
def parse(self, file):
IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"])
self.dir = os.path.dirname(file)
(nil, self.name) = os.path.split(self.dir)
def getDir(self):
return self.dir
# Standard Keys
def getName(self):
return self.get('Name', locale=True)
def getComment(self):
return self.get('Comment', locale=True)
def getInherits(self):
return self.get('Inherits', list=True)
def getDirectories(self):
return self.get('Directories', list=True)
def getHidden(self):
return self.get('Hidden', type="boolean")
def getExample(self):
return self.get('Example')
# Per Directory Keys
def getSize(self, directory):
return self.get('Size', type="integer", group=directory)
def getContext(self, directory):
return self.get('Context', group=directory)
def getType(self, directory):
value = self.get('Type', group=directory)
if value:
return value
else:
return "Threshold"
def getMaxSize(self, directory):
value = self.get('MaxSize', type="integer", group=directory)
if value or value == 0:
return value
else:
return self.getSize(directory)
def getMinSize(self, directory):
value = self.get('MinSize', type="integer", group=directory)
if value or value == 0:
return value
else:
return self.getSize(directory)
def getThreshold(self, directory):
value = self.get('Threshold', type="integer", group=directory)
if value or value == 0:
return value
else:
return 2
# validation stuff
def checkExtras(self):
# header
if self.defaultGroup == "KDE Icon Theme":
self.warnings.append('[KDE Icon Theme]-Header is deprecated')
# file extension
if self.fileExtension == ".theme":
pass
elif self.fileExtension == ".desktop":
self.warnings.append('.desktop fileExtension is deprecated')
else:
self.warnings.append('Unknown File extension')
# Check required keys
# Name
try:
self.name = self.content[self.defaultGroup]["Name"]
except KeyError:
self.errors.append("Key 'Name' is missing")
# Comment
try:
self.comment = self.content[self.defaultGroup]["Comment"]
except KeyError:
self.errors.append("Key 'Comment' is missing")
# Directories
try:
self.directories = self.content[self.defaultGroup]["Directories"]
except KeyError:
self.errors.append("Key 'Directories' is missing")
def checkGroup(self, group):
# check if group header is valid
if group == self.defaultGroup:
pass
elif group in self.getDirectories():
try:
self.type = self.content[group]["Type"]
except KeyError:
self.type = "Threshold"
try:
self.name = self.content[group]["Name"]
except KeyError:
self.errors.append("Key 'Name' in Group '%s' is missing" % group)
elif not (re.match("^\[X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group):
self.errors.append("Invalid Group name: %s" % group)
def checkKey(self, key, value, group):
# standard keys
if group == self.defaultGroup:
if re.match("^Name"+xdg.Locale.regex+"$", key):
pass
elif re.match("^Comment"+xdg.Locale.regex+"$", key):
pass
elif key == "Inherits":
self.checkValue(key, value, list=True)
elif key == "Directories":
self.checkValue(key, value, list=True)
elif key == "Hidden":
self.checkValue(key, value, type="boolean")
elif key == "Example":
self.checkValue(key, value)
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
else:
self.errors.append("Invalid key: %s" % key)
elif group in self.getDirectories():
if key == "Size":
self.checkValue(key, value, type="integer")
elif key == "Context":
self.checkValue(key, value)
elif key == "Type":
self.checkValue(key, value)
if value not in ["Fixed", "Scalable", "Threshold"]:
self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value)
elif key == "MaxSize":
self.checkValue(key, value, type="integer")
if self.type != "Scalable":
self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type)
elif key == "MinSize":
self.checkValue(key, value, type="integer")
if self.type != "Scalable":
self.errors.append("Key 'MinSize' give, but Type is %s" % self.type)
elif key == "Threshold":
self.checkValue(key, value, type="integer")
if self.type != "Threshold":
self.errors.append("Key 'Threshold' give, but Type is %s" % self.type)
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
else:
self.errors.append("Invalid key: %s" % key)
class IconData(IniFile):
"Class to parse and validate IconData Files"
def __init__(self):
IniFile.__init__(self)
def __repr__(self):
return self.getDisplayName()
def parse(self, file):
IniFile.parse(self, file, ["Icon Data"])
# Standard Keys
def getDisplayName(self):
return self.get('DisplayName', locale=True)
def getEmbeddedTextRectangle(self):
return self.get('EmbeddedTextRectangle', list=True)
def getAttachPoints(self):
return self.get('AttachPoints', type="point", list=True)
# validation stuff
def checkExtras(self):
# file extension
if self.fileExtension != ".icon":
self.warnings.append('Unknown File extension')
def checkGroup(self, group):
# check if group header is valid
if not (group == self.defaultGroup \
or (re.match("^\[X-", group) and group.encode("ascii", 'ignore') == group)):
self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace"))
def checkKey(self, key, value, group):
# standard keys
if re.match("^DisplayName"+xdg.Locale.regex+"$", key):
pass
elif key == "EmbeddedTextRectangle":
self.checkValue(key, value, type="integer", list=True)
elif key == "AttachPoints":
self.checkValue(key, value, type="point", list=True)
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
else:
self.errors.append("Invalid key: %s" % key)
icondirs = []
for basedir in xdg_data_dirs:
icondirs.append(os.path.join(basedir, "icons"))
icondirs.append(os.path.join(basedir, "pixmaps"))
icondirs.append(os.path.expanduser("~/.icons"))
# just cache variables, they give a 10x speed improvement
themes = []
cache = dict()
dache = dict()
eache = dict()
def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", "xpm"]):
global themes
if size == None:
size = xdg.Config.icon_size
if theme == None:
theme = xdg.Config.icon_theme
# if we have an absolute path, just return it
if os.path.isabs(iconname):
return iconname
# check if it has an extension and strip it
if os.path.splitext(iconname)[1][1:] in extensions:
iconname = os.path.splitext(iconname)[0]
# parse theme files
try:
if themes[0].name != theme:
themes = []
__addTheme(theme)
except IndexError:
__addTheme(theme)
# more caching (icon looked up in the last 5 seconds?)
tmp = "".join([iconname, str(size), theme, "".join(extensions)])
if eache.has_key(tmp):
if int(time.time() - eache[tmp][0]) >= xdg.Config.cache_time:
del eache[tmp]
else:
return eache[tmp][1]
for thme in themes:
icon = LookupIcon(iconname, size, thme, extensions)
if icon:
eache[tmp] = [time.time(), icon]
return icon
# cache stuff again (directories lookuped up in the last 5 seconds?)
for directory in icondirs:
if (not dache.has_key(directory) \
or (int(time.time() - dache[directory][1]) >= xdg.Config.cache_time \
and dache[directory][2] < os.path.getmtime(directory))) \
and os.path.isdir(directory):
dache[directory] = [os.listdir(directory), time.time(), os.path.getmtime(directory)]
for dir, values in dache.items():
for extension in extensions:
try:
if iconname + "." + extension in values[0]:
icon = os.path.join(dir, iconname + "." + extension)
eache[tmp] = [time.time(), icon]
return icon
except UnicodeDecodeError, e:
if debug:
raise e
else:
pass
# we haven't found anything? "hicolor" is our fallback
if theme != "hicolor":
icon = getIconPath(iconname, size, "hicolor")
eache[tmp] = [time.time(), icon]
return icon
def getIconData(path):
if os.path.isfile(path):
dirname = os.path.dirname(path)
basename = os.path.basename(path)
if os.path.isfile(os.path.join(dirname, basename + ".icon")):
data = IconData()
data.parse(os.path.join(dirname, basename + ".icon"))
return data
def __addTheme(theme):
for dir in icondirs:
if os.path.isfile(os.path.join(dir, theme, "index.theme")):
__parseTheme(os.path.join(dir,theme, "index.theme"))
break
elif os.path.isfile(os.path.join(dir, theme, "index.desktop")):
__parseTheme(os.path.join(dir,theme, "index.desktop"))
break
else:
if debug:
raise NoThemeError(theme)
def __parseTheme(file):
theme = IconTheme()
theme.parse(file)
themes.append(theme)
for subtheme in theme.getInherits():
__addTheme(subtheme)
def LookupIcon(iconname, size, theme, extensions):
# look for the cache
if not cache.has_key(theme.name):
cache[theme.name] = []
cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup
cache[theme.name].append(0) # [1] mtime
cache[theme.name].append(dict()) # [2] dir: [subdir, [items]]
# cache stuff (directory lookuped up the in the last 5 seconds?)
if int(time.time() - cache[theme.name][0]) >= xdg.Config.cache_time:
cache[theme.name][0] = time.time()
for subdir in theme.getDirectories():
for directory in icondirs:
dir = os.path.join(directory,theme.name,subdir)
if (not cache[theme.name][2].has_key(dir) \
or cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \
and subdir != "" \
and os.path.isdir(dir):
cache[theme.name][2][dir] = [subdir, os.listdir(dir)]
cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name))
for dir, values in cache[theme.name][2].items():
if DirectoryMatchesSize(values[0], size, theme):
for extension in extensions:
if iconname + "." + extension in values[1]:
return os.path.join(dir, iconname + "." + extension)
minimal_size = sys.maxint
closest_filename = ""
for dir, values in cache[theme.name][2].items():
distance = DirectorySizeDistance(values[0], size, theme)
if distance < minimal_size:
for extension in extensions:
if iconname + "." + extension in values[1]:
closest_filename = os.path.join(dir, iconname + "." + extension)
minimal_size = distance
return closest_filename
def DirectoryMatchesSize(subdir, iconsize, theme):
Type = theme.getType(subdir)
Size = theme.getSize(subdir)
Threshold = theme.getThreshold(subdir)
MinSize = theme.getMinSize(subdir)
MaxSize = theme.getMaxSize(subdir)
if Type == "Fixed":
return Size == iconsize
elif Type == "Scaleable":
return MinSize <= iconsize <= MaxSize
elif Type == "Threshold":
return Size - Threshold <= iconsize <= Size + Threshold
def DirectorySizeDistance(subdir, iconsize, theme):
Type = theme.getType(subdir)
Size = theme.getSize(subdir)
Threshold = theme.getThreshold(subdir)
MinSize = theme.getMinSize(subdir)
MaxSize = theme.getMaxSize(subdir)
if Type == "Fixed":
return abs(Size - iconsize)
elif Type == "Scalable":
if iconsize < MinSize:
return MinSize - iconsize
elif iconsize > MaxSize:
return MaxSize - iconsize
return 0
elif Type == "Threshold":
if iconsize < Size - Threshold:
return MinSize - iconsize
elif iconsize > Size + Threshold:
return iconsize - MaxSize
return 0
|