desktop.d (3530B)
1 module xdg.desktop; 2 import std.stdio; 3 import std.string; 4 import std.path; 5 import std.process; 6 import std.algorithm:joiner,splitter,startsWith,skipOver,findSplit,map; 7 import std.range; 8 import std.file; 9 import std.format; 10 import std.array; 11 import xdg.mime; 12 13 14 auto file_url_to_path(inout(char)[] uri){ 15 uri.skipOver("file://localhost"); 16 uri.skipOver("file://"); 17 return uri; 18 } 19 20 auto xdg_user_dir(){ 21 auto home = environment.get("XDG_DATA_HOME", buildPath(environment.get("HOME"), ".local/share")); 22 auto sys = environment.get("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/"); 23 return chain(home.splitter(":"),sys.splitter(":")).map!(a=>buildPath(a,"applications")); 24 } 25 26 struct Database{ 27 struct Entry{ 28 string path; 29 string baseName; 30 string name; 31 string icon; 32 string exec; 33 string[] mimeTypes; 34 35 auto format(in char[] path){ 36 Appender!string app; 37 auto fmt = exec; 38 while(fmt.length>0){ 39 auto s = fmt.findSplit("%"); 40 app ~= s[0]; 41 if(s[1].length==0){ 42 break; 43 } 44 switch(s[2][0]){ 45 case '%': 46 app ~= "%"; 47 break; 48 case 'i': 49 app ~= "--icon "~icon; 50 break; 51 case 'f': 52 case 'F': 53 case 'u': 54 case 'U': 55 app ~= '"'~path.replace("\"","\\\"")~'"'; 56 break; 57 default: 58 throw new Exception("Unknown format literal {}".format(s[2])); 59 break; 60 } 61 fmt = s[2][1..$]; 62 } 63 return app.data; 64 } 65 66 void fromFile(in char[] file){ 67 auto f = File(file, "r"); 68 scope(exit){ 69 f.close(); 70 } 71 path = file.idup; 72 baseName = .baseName(path); 73 string entry = ""; 74 foreach(l; f.byLine){ 75 if(l[0]=='['){ 76 entry = l[1..$-1].idup; 77 } 78 if(entry == "Desktop Entry"){ 79 auto s = l.findSplit("="); 80 switch(s[0]){ 81 case "Name": 82 name = s[2].idup; 83 break; 84 case "Exec": 85 exec = s[2].idup; 86 break; 87 case "Icon": 88 icon = s[2].idup; 89 break; 90 case "MimeType": 91 mimeTypes = s[2].idup.split(";"); 92 break; 93 default: 94 break; 95 } 96 } 97 } 98 } 99 } 100 101 Entry[][string] entries; 102 Entry[][string] entries_by_basename; 103 Entry[string] preferred; 104 void index(in char[] path){ 105 writeln("Indexing ", path); 106 auto de = dirEntries(cast(string)path, "*.desktop", SpanMode.breadth); 107 foreach(entry; de){ 108 Entry e; 109 try{ 110 e.fromFile(entry); 111 } 112 catch(Exception exc){ 113 114 writeln(exc); 115 } 116 foreach(mt; e.mimeTypes){ 117 entries[mt] ~= e; 118 } 119 entries_by_basename[e.baseName] ~= e; 120 } 121 } 122 123 void index(){ 124 auto paths = xdg_user_dir(); 125 foreach(path; paths){ 126 if(!exists(path)) 127 continue; 128 try{ 129 index(path); 130 } 131 catch(Exception e){ 132 writeln(e); 133 } 134 } 135 136 } 137 138 void loadPreferred(){ 139 auto config_home = environment.get("XDG_CONFIG_HOME",buildPath(environment.get("HOME"), ".config")); 140 auto config = buildPath(config_home, "mimeapps.list"); 141 if(exists(config)){ 142 auto f = File(config); 143 scope(exit){ 144 f.close(); 145 } 146 foreach(l; f.byLine()){ 147 auto s = l.findSplit("="); 148 if(s[1].length > 0){ 149 auto de = s[2].findSplit(";")[0]; 150 if(auto entries = de in entries_by_basename){ 151 preferred[s[0].idup] = (*entries)[0]; 152 } 153 else{ 154 stderr.writeln("No entry for %s found".format(de)); 155 } 156 } 157 } 158 } 159 } 160 161 void init(){ 162 index(); 163 loadPreferred(); 164 } 165 166 Entry get(in char[] mime){ 167 if(auto e = mime in preferred){ 168 return *e; 169 } 170 else if(auto e = mime in entries){ 171 return (*e)[0]; 172 } 173 else{ 174 throw new Exception("Mime type %s not found".format(mime)); 175 } 176 } 177 } 178