1 /** 2 * Getting paths where icon themes and icons are stored. 3 * 4 * Authors: 5 * $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov) 6 * Copyright: 7 * Roman Chistokhodov, 2015-2017 8 * License: 9 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 10 * See_Also: 11 * $(LINK2 http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html, Icon Theme Specification) 12 */ 13 14 module icontheme.paths; 15 16 private { 17 import std.algorithm; 18 import std.array; 19 import std.exception; 20 import std.path; 21 import std.range; 22 import std.traits; 23 import std.process : environment; 24 import isfreedesktop; 25 } 26 27 version(unittest) { 28 package struct EnvGuard 29 { 30 this(string env) { 31 envVar = env; 32 envValue = environment.get(env); 33 } 34 35 ~this() { 36 if (envValue is null) { 37 environment.remove(envVar); 38 } else { 39 environment[envVar] = envValue; 40 } 41 } 42 43 string envVar; 44 string envValue; 45 } 46 } 47 48 49 static if (isFreedesktop) { 50 import xdgpaths; 51 52 /** 53 * The list of base directories where icon thems should be looked for as described in $(LINK2 http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#directory_layout, Icon Theme Specification). 54 * 55 * $(BLUE This function is Freedesktop only). 56 * Note: This function does not provide any caching of its results. This function does not check if directories exist. 57 */ 58 @safe string[] baseIconDirs() nothrow 59 { 60 string[] toReturn; 61 string homePath; 62 collectException(environment.get("HOME"), homePath); 63 if (homePath.length) { 64 toReturn ~= buildPath(homePath, ".icons"); 65 } 66 toReturn ~= xdgAllDataDirs("icons"); 67 toReturn ~= "/usr/share/pixmaps"; 68 return toReturn; 69 } 70 71 /// 72 unittest 73 { 74 auto homeGuard = EnvGuard("HOME"); 75 auto dataHomeGuard = EnvGuard("XDG_DATA_HOME"); 76 auto dataDirsGuard = EnvGuard("XDG_DATA_DIRS"); 77 78 environment["HOME"] = "/home/user"; 79 environment["XDG_DATA_HOME"] = "/home/user/data"; 80 environment["XDG_DATA_DIRS"] = "/usr/local/data:/usr/data"; 81 82 assert(baseIconDirs() == ["/home/user/.icons", "/home/user/data/icons", "/usr/local/data/icons", "/usr/data/icons", "/usr/share/pixmaps"]); 83 } 84 85 /** 86 * Writable base icon path. Depends on XDG_DATA_HOME, so this is $HOME/.local/share/icons rather than $HOME/.icons 87 * 88 * $(BLUE This function is Freedesktop only). 89 * Note: it does not check if returned path exists and appears to be directory. 90 */ 91 @safe string writableIconsPath() nothrow { 92 return xdgDataHome("icons"); 93 } 94 95 /// 96 unittest 97 { 98 auto dataHomeGuard = EnvGuard("XDG_DATA_HOME"); 99 environment["XDG_DATA_HOME"] = "/home/user/data"; 100 assert(writableIconsPath() == "/home/user/data/icons"); 101 } 102 103 /// 104 enum IconThemeNameDetector 105 { 106 none = 0, 107 fallback = 1, /// Use hardcoded fallback to detect icon theme name depending on the current desktop environment. Has lower priority than other methods. 108 gtk2 = 2, /// Use gtk2 settings to detect icon theme name. Has lower priority than gtk3. 109 gtk3 = 4, /// Use gtk3 settings to detect icon theme name. 110 automatic = fallback | gtk2 | gtk3 /// Use all known means to detect icon theme name. 111 } 112 /** 113 * Try to detect the current icon name configured by user. 114 * 115 * $(BLUE This function is Freedesktop only). 116 * Note: There's no any specification on that so some heuristics are applied. 117 * Another note: It does not check if the icon theme with the detected name really exists on the file system. 118 */ 119 @safe string currentIconThemeName(IconThemeNameDetector detector = IconThemeNameDetector.automatic) nothrow 120 { 121 @trusted static string fallbackIconThemeName() 122 { 123 string xdgCurrentDesktop = environment.get("XDG_CURRENT_DESKTOP"); 124 switch(xdgCurrentDesktop) { 125 case "GNOME": 126 case "X-Cinnamon": 127 case "MATE": 128 return "gnome"; 129 case "LXDE": 130 return "Adwaita"; 131 case "XFCE": 132 return "Tango"; 133 case "KDE": 134 return "oxygen"; //TODO: detect KDE version and set breeze if it's KDE5 135 default: 136 return "Tango"; 137 } 138 } 139 @trusted static string gtk2IconThemeName() nothrow 140 { 141 import std.stdio : File; 142 try { 143 auto home = environment.get("HOME"); 144 if (!home.length) { 145 return null; 146 } 147 string themeName; 148 auto gtkConfig = buildPath(home, ".gtkrc-2.0"); 149 auto f = File(gtkConfig, "r"); 150 foreach(line; f.byLine()) { 151 auto splitted = line.findSplit("="); 152 if (splitted[0] == "gtk-icon-theme-name") { 153 if (splitted[2].length > 2 && splitted[2][0] == '"' && splitted[2][$-1] == '"') { 154 return splitted[2][1..$-1].idup; 155 } 156 break; 157 } 158 } 159 } catch(Exception e) { 160 161 } 162 return null; 163 } 164 @trusted static string gtk3IconThemeName() nothrow 165 { 166 import inilike.file; 167 try { 168 auto f = new IniLikeFile(xdgConfigHome("gtk-3.0/settings.ini"), IniLikeFile.ReadOptions(No.preserveComments)); 169 auto settings = f.group("Settings"); 170 if (settings) 171 return settings.readEntry("gtk-icon-theme-name"); 172 } catch(Exception e) { 173 174 } 175 return null; 176 } 177 178 try { 179 string themeName; 180 if (detector & IconThemeNameDetector.gtk3) { 181 themeName = gtk3IconThemeName(); 182 } 183 if (!themeName.length && (detector & IconThemeNameDetector.gtk2)) { 184 themeName = gtk2IconThemeName(); 185 } 186 if (!themeName.length && (detector & IconThemeNameDetector.fallback)) { 187 themeName = fallbackIconThemeName(); 188 } 189 return themeName; 190 } catch(Exception e) { 191 192 } 193 return null; 194 } 195 196 unittest 197 { 198 auto desktopGuard = EnvGuard("XDG_CURRENT_DESKTOP"); 199 environment["XDG_CURRENT_DESKTOP"] = ""; 200 assert(currentIconThemeName(IconThemeNameDetector.fallback).length); 201 assert(currentIconThemeName(IconThemeNameDetector.none).length == 0); 202 203 version(iconthemeFileTest) 204 { 205 auto homeGuard = EnvGuard("HOME"); 206 environment["HOME"] = "./test"; 207 208 auto configGuard = EnvGuard("XDG_CONFIG_HOME"); 209 environment["XDG_CONFIG_HOME"] = "./test"; 210 211 assert(currentIconThemeName() == "gnome"); 212 assert(currentIconThemeName(IconThemeNameDetector.gtk3) == "gnome"); 213 assert(currentIconThemeName(IconThemeNameDetector.gtk2) == "oxygen"); 214 } 215 } 216 } 217 218 /** 219 * The list of icon theme directories based on data paths. 220 * Returns: Array of paths with "icons" subdirectory appended to each data path. 221 * Note: This function does not check if directories exist. 222 */ 223 @trusted string[] baseIconDirs(Range)(Range dataPaths) if (isInputRange!Range && is(ElementType!Range : string)) 224 { 225 return dataPaths.map!(p => buildPath(p, "icons")).array; 226 } 227 228 /// 229 unittest 230 { 231 auto dataPaths = ["share", buildPath("local", "share")]; 232 assert(equal(baseIconDirs(dataPaths), [buildPath("share", "icons"), buildPath("local", "share", "icons")])); 233 }