1 //===========================================
2 // Lumina-DE source code
3 // Copyright (c) 2013-2015, Ken Moore
4 // Available under the 3-clause BSD license
5 // See the LICENSE file for full details
6 //===========================================
7 #include "LuminaXDG.h"
8 #include "LuminaOS.h"
9 #include "LUtils.h"
10 #include <QObject>
11 #include <QTimer>
12 #include <QMediaPlayer>
13 #include <QSvgRenderer>
14
15 static QStringList mimeglobs;
16 static qint64 mimechecktime;
17
18 //=============================
19 // XDGDesktop CLASS
20 //=============================
XDGDesktop(QString file,QObject * parent)21 XDGDesktop::XDGDesktop(QString file, QObject *parent) : QObject(parent){
22 isHidden=false;
23 useTerminal=false;
24 startupNotify=false;
25 useVGL = false;
26 type = XDGDesktop::BAD;
27 filePath = file;
28 exec = tryexec = ""; // just to make sure this is initialized
29 if(!filePath.isEmpty()){ sync(); } //if an input file is given - go ahead and sync now
30 }
31
sync()32 void XDGDesktop::sync(){
33 //Reset internal vars
34 isHidden=false;
35 useTerminal=false;
36 startupNotify=false;
37 type = XDGDesktop::BAD;
38 exec = tryexec = "";
39 //Read in the File
40 if(!filePath.endsWith(".desktop")){ return; }
41 lastRead = QDateTime::currentDateTime();
42 QStringList file = LUtils::readFile(filePath);
43 if(file.isEmpty()){ return; } //done with init right here - nothing to load
44 //Get the current localization code
45 type = XDGDesktop::APP; //assume this initially if we read the file properly
46 QString lang = QLocale::system().name(); //lang code
47 QString slang = lang.section("_",0,0); //short lang code
48 //Now start looping over the information
49 XDGDesktopAction CDA; //current desktop action
50 bool insection=false;
51 bool inaction=false;
52 for(int i=0; i<file.length(); i++){
53 QString line = file[i];
54 //if(filePath.contains("pcbsd")){ qDebug() << " - Check Line:" << line << inaction << insection; }
55 //Check if this is the end of a section
56 if(line.startsWith("[") && inaction){
57 insection=false; inaction=false;
58 //Add the current Action structure to the main desktop structure if appropriate
59 if(!CDA.ID.isEmpty()){ actions << CDA; CDA = XDGDesktopAction(); }
60 }else if(line.startsWith("[")){ insection=false; inaction = false; }
61 //Now check if this is the beginning of a section
62 if(line=="[Desktop Entry]"){ insection=true; continue; }
63 else if(line.startsWith("[Desktop Action ")){
64 //Grab the ID of the action out of the label
65 CDA.ID = line.section("]",0,0).section("Desktop Action",1,1).simplified();
66 inaction = true;
67 continue;
68 }else if( (!insection && !inaction) || line.startsWith("#")){ continue; }
69 //Now parse out the file
70 line = line.simplified();
71 QString var = line.section("=",0,0).simplified();
72 QString loc = var.section("[",1,1).section("]",0,0).simplified(); // localization
73 var = var.section("[",0,0).simplified(); //remove the localization
74 QString val = line.section("=",1,50).simplified();
75 if( val.count("\"")==2 && val.startsWith("\"") && val.endsWith("\"")){ val.chop(1); val = val.remove(0,1); } //remove the starting/ending quotes
76 //-------------------
77 if(var=="Name"){
78 if(insection){
79 if(loc==slang){ name = val;} //short locale code
80 else if(loc==lang){ name = val;}
81 else if(name.isEmpty() && loc.isEmpty()){ /*qDebug() << "Empty" << val;*/ name = val; }
82 }else if(inaction){
83 if(CDA.name.isEmpty() && loc.isEmpty()){ CDA.name = val; }
84 else if(CDA.name.isEmpty() && loc==slang){ CDA.name = val; } //short locale code
85 else if(loc == lang){ CDA.name = val; }
86 }
87 //hasName = true;
88 }else if(var=="GenericName" && insection){
89 if(genericName.isEmpty() && loc.isEmpty()){ genericName = val; }
90 else if(genericName.isEmpty() && loc==slang){ genericName = val; } //short locale code
91 else if(loc == lang){ genericName = val; }
92 }else if(var=="Comment" && insection){
93 if(comment.isEmpty() && loc.isEmpty()){ comment = val; }
94 else if(comment.isEmpty() && loc==slang){ comment = val; } //short locale code
95 else if(loc == lang){ comment = val; }
96 }else if(var=="Icon"){
97 //Quick fix for bad-registrations which add the icon suffix for theme icons
98 if(!val.startsWith("/") && val.endsWith(".png") ){ val = val.section(".",0,-2); }
99 if(insection){
100 if(icon.isEmpty() && loc.isEmpty()){ icon = val; }
101 else if(icon.isEmpty() && loc==slang){ icon = val; } //short locale code
102 else if(loc == lang){ icon = val; }
103 }else if(inaction){
104 if(CDA.icon.isEmpty() && loc.isEmpty()){ CDA.icon = val; }
105 else if(CDA.icon.isEmpty() && loc==slang){ CDA.icon = val; } //short locale code
106 else if(loc == lang){ CDA.icon = val; }
107 }
108 }
109 else if( (var=="TryExec") && (tryexec.isEmpty()) && insection) { tryexec = val; }
110 else if(var=="Exec"){
111 if(insection && exec.isEmpty() ){ exec = val; }
112 else if(inaction && CDA.exec.isEmpty() ){ CDA.exec = val; }
113 }
114 else if( (var=="Path") && (path.isEmpty() ) && insection){ path = val; }
115 else if(var=="NoDisplay" && !isHidden && insection){ isHidden = (val.toLower()=="true"); }
116 else if(var=="Hidden" && !isHidden && insection){ isHidden = (val.toLower()=="true"); }
117 else if(var=="Categories" && insection){ catList = val.split(";",QString::SkipEmptyParts); }
118 else if(var=="OnlyShowIn" && insection){ showInList = val.split(";",QString::SkipEmptyParts); }
119 else if(var=="NotShowIn" && insection){ notShowInList = val.split(";",QString::SkipEmptyParts); }
120 else if(var=="Terminal" && insection){ useTerminal= (val.toLower()=="true"); }
121 else if(var=="Actions" && insection){ actionList = val.split(";",QString::SkipEmptyParts); }
122 else if(var=="MimeType" && insection){ mimeList = val.split(";",QString::SkipEmptyParts); }
123 else if(var=="Keywords" && insection){
124 if(keyList.isEmpty() && loc.isEmpty()){ keyList = val.split(";",QString::SkipEmptyParts); }
125 else if(loc == lang){ keyList = val.split(";",QString::SkipEmptyParts); }
126 }
127 else if(var=="StartupNotify" && insection){ startupNotify = (val.toLower()=="true"); }
128 else if(var=="StartupWMClass" && insection){ startupWM = val; }
129 else if(var=="URL" && insection){ url = val;}
130 else if(var=="Type" && insection){
131 if(val.toLower()=="application"){ type = XDGDesktop::APP; }
132 else if(val.toLower()=="link"){ type = XDGDesktop::LINK; }
133 else if(val.toLower().startsWith("dir")){ type = XDGDesktop::DIR; } //older specs are "Dir", newer specs are "Directory"
134 else{ type = XDGDesktop::BAD; } //Unknown type
135 }
136 } //end reading file
137 if(!CDA.ID.isEmpty()){ actions << CDA; CDA = XDGDesktopAction(); } //if an action was still being read, add that to the list now
138
139 file.clear(); //done with contents of file
140 //If there are OnlyShowIn desktops listed, add them to the name
141 if( !showInList.isEmpty() && !showInList.contains("Lumina", Qt::CaseInsensitive) ){
142 name.append(" ("+showInList.join(", ")+")");
143 }
144 //Quick fix for showing "wine" applications (which quite often don't list a category, or have other differences)
145 if(catList.isEmpty() && filePath.contains("/wine/")){
146 catList << "Wine"; //Internal Lumina category only (not in XDG specs as of 11/14/14)
147 //Also add a fix for the location of Wine icons
148 if( !icon.isEmpty() ){
149 QStringList sizes; sizes << "256x256" << "128x128" << "64x64" << "48x48" << "32x32" << "16x16";
150 QString upath = QDir::homePath()+"/.local/share/icons/hicolor/%1/apps/%2.png";
151 //qDebug() << "Wine App: Check icon" << upath;
152 for(int i=0; i<sizes.length(); i++){
153 if( QFile::exists(upath.arg(sizes[i],icon)) ){
154 icon = upath.arg(sizes[i],icon);
155 //qDebug() << " - Found Icon:" << icon;
156 break;
157 }
158 }
159 }
160 }
161
162 }
163
164
isValid(bool showAll)165 bool XDGDesktop::isValid(bool showAll){
166 bool ok=true;
167 //bool DEBUG = false;
168 //if(DEBUG){ qDebug() << "[LXDG] Check File validity:" << dFile.name << dFile.filePath; }
169 switch (type){
170 case XDGDesktop::BAD:
171 ok=false;
172 //if(DEBUG){ qDebug() << " - Bad file type"; }
173 break;
174 case XDGDesktop::APP:
175 if(!tryexec.isEmpty() && !LXDG::checkExec(tryexec)){ ok=false; }//if(DEBUG){ qDebug() << " - tryexec does not exist";} }
176 else if(exec.isEmpty() || name.isEmpty()){ ok=false; }//if(DEBUG){ qDebug() << " - exec or name is empty";} }
177 else if(!LXDG::checkExec(exec.section(" ",0,0,QString::SectionSkipEmpty)) ){ ok=false; }//if(DEBUG){ qDebug() << " - first exec binary does not exist";} }
178 break;
179 case XDGDesktop::LINK:
180 ok = !url.isEmpty();
181 //if(DEBUG && !ok){ qDebug() << " - Link with missing URL"; }
182 break;
183 case XDGDesktop::DIR:
184 ok = !path.isEmpty() && QFile::exists(path);
185 //if(DEBUG && !ok){ qDebug() << " - Dir with missing path"; }
186 break;
187 default:
188 ok=false;
189 //if(DEBUG){ qDebug() << " - Unknown file type"; }
190 }
191 if(!showAll){
192 QString cdesk = getenv("XDG_CURRENT_DESKTOP");
193 if(cdesk.isEmpty()){ cdesk="Lumina"; }
194 if(!showInList.isEmpty()){ ok = showInList.contains(cdesk, Qt::CaseInsensitive); }
195 else if(!notShowInList.isEmpty()){ ok = !notShowInList.contains(cdesk,Qt::CaseInsensitive); }
196 else if(name.isEmpty()){ ok = false; }
197 }
198 return ok;
199 }
200
getDesktopExec(QString ActionID)201 QString XDGDesktop::getDesktopExec(QString ActionID){
202 //Generate the executable line for the application
203 QString out = exec;
204 if( !ActionID.isEmpty() ){
205 //Go through and grab the proper exec for the listed action
206 for(int i=0; i<actions.length(); i++){
207 if(actions[i].ID == ActionID){
208 out = actions[i].exec;
209 break;
210 }
211 }
212 }
213
214 if(out.isEmpty()){ return ""; }
215 else if(useTerminal){
216 //Get the currently default terminal
217 QString term = LXDG::findDefaultAppForMime("application/terminal");
218 if(!QFile::exists(term)){ term = "xterm -lc"; }
219 else if(term.endsWith(".desktop")){
220 XDGDesktop DF(term);
221 if(DF.isValid()){ term = DF.getDesktopExec(); }
222 else{ term = "xterm -lc"; }
223 //DF.deleteLater(); //done with this struct
224 }else if( !LUtils::isValidBinary(term)){ term = "xterm -lc"; }
225 out = term+" -e "+out; //-e is a nearly-universal flag for terminal emulators
226 }
227 //Now perform any of the XDG flag substitutions as appropriate (9/2014 standards)
228 if(out.contains("%i") && !icon.isEmpty() ){ out.replace("%i", "--icon \""+icon+"\""); }
229 if(out.contains("%c")){
230 if(!name.isEmpty()){ out.replace("%c", "\""+name+"\""); }
231 else if(!genericName.isEmpty()){ out.replace("%c", "\""+genericName+"\""); }
232 else{ out.replace("%c", "\""+filePath.section("/",-1).section(".desktop",0,0)+"\""); }
233 }
234 if(out.contains("%k")){ out.replace("%k", "\""+filePath+"\""); }
235 return out;
236 }
237
generateExec(QStringList inputfiles,QString ActionID)238 QString XDGDesktop::generateExec(QStringList inputfiles, QString ActionID){
239 QString exec = getDesktopExec(ActionID);
240 //Does the app need the input files in URL or File syntax?
241 bool URLsyntax = (exec.contains("%u") || exec.contains("%U"));
242 //Adjust the input file formats as needed
243 //qDebug() << "Got inputfiles:" << inputfiles << URLsyntax;
244 for(int i=0; i<inputfiles.length(); i++){
245 bool url = inputfiles[i].startsWith("www") || inputfiles[i].contains("://");
246 //Run it through the QUrl class to catch/fix any URL syntax issues
247 if(URLsyntax){
248 if(inputfiles[i].startsWith("mailto:") ){} //don't touch this syntax - already formatted
249 else if(url){ inputfiles[i] = QUrl(inputfiles[i]).url(); }
250 else{ inputfiles[i] = QUrl::fromLocalFile(inputfiles[i]).url(); }
251 }else{
252 //if(inputfiles[i].startsWith("mailto:") ){} //don't touch this syntax - already formatted
253 //qDebug() << "Need local format:" << inputfiles[i] << url;
254 if(url){ inputfiles[i] = QUrl(inputfiles[i]).toLocalFile(); }
255 else{ inputfiles[i] = inputfiles[i]; } //QUrl::fromLocalFile(inputfiles[i]).toLocalFile(); }
256 }
257 }
258 inputfiles.removeAll(""); //just in case any empty ones get through
259 //Now to the exec replacements as needed
260 //qDebug() << "Generate Exec:" << exec << inputfiles;
261 if(exec.contains("%f")){
262 if(inputfiles.isEmpty()){ exec.replace("%f",""); }
263 else{ exec.replace("%f", "\""+inputfiles.first()+"\""); } //Note: can only take one input
264 }else if(exec.contains("%F")){
265 if(inputfiles.isEmpty()){ exec.replace("%F",""); }
266 else{ exec.replace("%F", "\""+inputfiles.join("\" \"")+"\""); }
267 }
268 if(exec.contains("%u")){
269 if(inputfiles.isEmpty()){ exec.replace("%u",""); }
270 else{ exec.replace("%u", "\""+inputfiles.first()+"\""); } //Note: can only take one input
271 }else if(exec.contains("%U")){
272 if(inputfiles.isEmpty()){ exec.replace("%U",""); }
273 else{ exec.replace("%U", "\""+inputfiles.join("\" \"")+"\""); }
274 }
275 //Sanity check for known Local/URL syntax issues from some apps
276 if(!URLsyntax && exec.contains("%20")){ exec.replace("%20"," "); }
277 //Clean up any leftover "Exec" field codes (should have already been replaced earlier)
278 if(exec.contains("%")){ exec = exec.remove("%U").remove("%u").remove("%F").remove("%f").remove("%i").remove("%c").remove("%k"); }
279 return exec.simplified();
280 }
281
saveDesktopFile(bool merge)282 bool XDGDesktop::saveDesktopFile(bool merge){
283 qDebug() << "Save Desktop File:" << filePath << "Merge:" << merge;
284 bool autofile = filePath.contains("/autostart/"); //use the "Hidden" field instead of the "NoDisplay"
285 int insertloc = -1;
286 QStringList info;
287 if(QFile::exists(filePath) && merge){
288 //Load the existing file and merge in in any changes
289 info = LUtils::readFile(filePath);
290 //set a couple flags based on the contents before we start iterating through
291 // - determine if a translated field was changed (need to remove all the now-invalid translations)
292 bool clearName, clearComment, clearGName;
293 QString tmp = "";
294 if(!info.filter("Name=").isEmpty()){ tmp = info.filter("Name=").first().section("=",1,50); }
295 clearName=(tmp!=name);
296 tmp.clear();
297 if(!info.filter("Comment=").isEmpty()){ tmp = info.filter("Comment=").first().section("=",1,50); }
298 clearComment=(tmp!=comment);
299 tmp.clear();
300 if(!info.filter("GenericName=").isEmpty()){ tmp = info.filter("GenericName=").first().section("=",1,50); }
301 clearGName=(tmp!=genericName);
302 //Now start iterating through the file and changing fields as necessary
303 bool insection = false;
304 for(int i=0; i<info.length(); i++){
305 if(info[i]=="[Desktop Entry]"){
306 insection = true;
307 continue;
308 }else if(info[i].startsWith("[")){
309 if(insection){ insertloc = i; } //save this location for later insertions
310 insection = false;
311 continue;
312 }
313 if(!insection || info[i].isEmpty() || info[i].section("#",0,0).simplified().isEmpty()){ continue; }
314 QString var = info[i].section("=",0,0);
315 QString val = info[i].section("=",1,50).simplified();
316 //NOTE: Clear the dFile variable as it is found/set in the file (to keep track of what has been used already)
317 // For boolian values, set them to false
318 // --LOCALIZED VALUES --
319 if(var.startsWith("Name")){
320 if(var.contains("[") && clearName){ info.removeAt(i); i--; continue;}
321 else if(!var.contains("[")){ info[i] = var+"="+name; name.clear(); }
322 }else if(var.startsWith("GenericName")){
323 if(var.contains("[") && clearGName){ info.removeAt(i); i--; continue;}
324 else if(!var.contains("[")){ info[i] = var+"="+genericName; genericName.clear(); }
325 }else if(var.startsWith("Comment")){
326 if(var.contains("[") && clearComment){ info.removeAt(i); i--; continue;}
327 else if(!var.contains("[")){ info[i] = var+"="+comment; comment.clear(); }
328
329 // --STRING/LIST VALUES--
330 }else if(var=="Exec"){ info[i] = var+"="+exec; exec.clear(); }
331 else if(var=="TryExec"){ info[i] = var+"="+tryexec; tryexec.clear(); }
332 else if(var=="Path"){ info[i] = var+"="+path; path.clear(); }
333 else if(var=="Icon"){ info[i] = var+"="+icon; icon.clear(); }
334 else if(var=="StartupWMClass"){ info[i] = var+"="+startupWM; startupWM.clear(); }
335 else if(var=="MimeType"){ info[i] = var+"="+mimeList.join(";"); mimeList.clear(); }
336 else if(var=="Categories"){ info[i] = var+"="+catList.join(";"); catList.clear(); }
337 else if(var=="Keywords"){ info[i] = var+"="+keyList.join(";"); keyList.clear(); }
338 else if(var=="Actions"){ info[i] = var+"="+actionList.join(";"); actionList.clear(); }
339 else if(var=="OnlyShowIn"){ info[i] = var+"="+showInList.join(";"); showInList.clear(); }
340 else if(var=="NotShowIn"){ info[i] = var+"="+notShowInList.join(";"); notShowInList.clear(); }
341 else if(var=="URL"){ info[i] = var+"="+url; url.clear(); }
342
343 // --BOOLIAN VALUES--
344 else if(var=="Hidden"){
345 if(!autofile){ info.removeAt(i); i--; continue; }
346 else{ info[i] = var+"="+(isHidden ? "true": "false"); isHidden=false;}
347 }else if(var=="NoDisplay"){
348 if(autofile){ info.removeAt(i); i--; continue; }
349 else{ info[i] = var+"="+(isHidden ? "true": "false"); isHidden=false;}
350 }else if(var=="Terminal"){
351 info[i] = var+"="+(useTerminal ? "true": "false"); useTerminal=false;
352 }else if(var=="StartupNotify"){
353 info[i] = var+"="+(startupNotify ? "true": "false"); startupNotify=false;
354 }
355 // Remove any lines that have been un-set or removed from the file
356 if(info[i].section("=",1,50).simplified().isEmpty()){ info.removeAt(i); i--; }
357 }
358
359 }else{
360 //Just write a new file and overwrite any old one
361 // (pre-set some values here which are always required)
362 info << "[Desktop Entry]";
363 info << "Version=1.0";
364 if(type==XDGDesktop::APP){ info << "Type=Application"; }
365 else if(type==XDGDesktop::LINK){ info << "Type=Link"; }
366 else if(type==XDGDesktop::DIR){ info << "Type=Dir"; }
367 }
368
369 if(insertloc<0){ insertloc = info.size(); }//put it at the end
370 //Now add in any items that did not exist in the original file
371 if( !exec.isEmpty() ){ info.insert(insertloc,"Exec="+exec); }
372 if( !tryexec.isEmpty() ){ info.insert(insertloc,"TryExec="+tryexec); }
373 if( !path.isEmpty() ){ info.insert(insertloc,"Path="+path); }
374 if( !icon.isEmpty() ){ info.insert(insertloc,"Icon="+icon); }
375 if( !name.isEmpty() ){ info.insert(insertloc,"Name="+name); }
376 if( !genericName.isEmpty() ){ info.insert(insertloc,"GenericName="+genericName); }
377 if( !comment.isEmpty() ){ info.insert(insertloc,"Comment="+comment); }
378 if( !startupWM.isEmpty() ){ info.insert(insertloc,"StartupWMClass="+startupWM); }
379 if( !mimeList.isEmpty() ){ info.insert(insertloc,"MimeType="+mimeList.join(";")); }
380 if( !catList.isEmpty() ){ info.insert(insertloc,"Categories="+catList.join(";")); }
381 if( !keyList.isEmpty() ){ info.insert(insertloc,"Keywords="+keyList.join(";")); }
382 if( !actionList.isEmpty() ){ info.insert(insertloc,"Actions="+actionList.join(";")); }
383 if( !showInList.isEmpty() ){ info.insert(insertloc,"OnlyShowIn="+showInList.join(";")); }
384 else if( !notShowInList.isEmpty() ){ info.insert(insertloc,"NotShowIn="+notShowInList.join(";")); }
385 if( !url.isEmpty() ){ info.insert(insertloc,"URL="+url); }
386 if( isHidden && autofile ){ info.insert(insertloc,"Hidden=true"); }
387 else if(isHidden){ info.insert(insertloc,"NoDisplay=true"); }
388 if( useTerminal){ info.insert(insertloc,"Terminal=true"); }
389 if( startupNotify ){ info.insert(insertloc,"StartupNotify=true"); }
390
391 //Now save the file
392 return LUtils::writeFile(filePath, info, true);
393 }
394
setAutoStarted(bool autostart)395 bool XDGDesktop::setAutoStarted(bool autostart){
396 //First get the list of system directories to search (system first, user-provided files come later and overwrite sys files as needed)
397 QStringList paths = QString(getenv("XDG_CONFIG_DIRS")).split(":");
398 QString upath = QString(getenv("XDG_CONFIG_HOME")).section(":",0,0);
399 if(upath.isEmpty()){ upath = QDir::homePath()+"/.config/autostart/"; }
400 else{ upath.append("/autostart/"); }
401 //Verify that the autostart directory exists
402 if(!QFile::exists(upath)){
403 QDir dir;
404 dir.mkpath(upath);
405 }
406
407 //Quick check/finish for user-defined files which are getting disabled (just remove the file)
408 if(filePath.startsWith(upath) && !autostart){
409 return QFile::remove(filePath);
410 }
411 bool sysfile = false;
412 for(int i=0; i<paths.length(); i++){
413 if(filePath.startsWith(paths[i]+"/autostart/") ){
414 sysfile = true;
415 //Change it to the user-modifiable directory
416 filePath = filePath.replace(paths[i]+"/autostart/", upath);
417 }
418 }
419 //Make sure the user-autostart dir is specified, and clean the app structure as necessary
420 if( !filePath.startsWith(upath) && autostart){
421 //Some other non-override autostart file - set it up to open with lumina-open
422 if(!filePath.endsWith(".desktop")){
423 exec = "lumina-open \""+filePath+"\"";
424 tryexec = filePath; //make sure this file exists
425 if(name.isEmpty()){ name = filePath.section("/",-1); }
426 if(icon.isEmpty()){ icon = LXDG::findAppMimeForFile(filePath); icon.replace("/","-"); }
427 filePath = upath+filePath.section("/",-1)+".desktop";
428 type = XDGDesktop::APP;
429 }else{
430 //Some other *.desktop file on the system (keep almost all the existing settings/values)
431 // - setup a redirect to the other file
432 exec = "lumina-open \""+filePath+"\"";
433 tryexec = filePath; //make sure this file exists
434 // - Adjust the actual path where this file will get saved
435 filePath = upath+filePath.section("/",-1);
436 }
437 }
438 //Now save the "hidden" value into the file
439 isHidden = !autostart; //if hidden, it will not be autostarted
440 //Now save the file as necessary
441 bool saved = false;
442 //qDebug() << " - Saving AutoStart File:" << filePath << name << isHidden;
443 if(sysfile){
444 //Just an override file for the "hidden" field - nothing more
445 QStringList info;
446 info << "[Desktop Entry]" << "Type=Application" << QString("Hidden=")+ (isHidden ? QString("true"): QString("false"));
447 saved = LUtils::writeFile(filePath, info, true);
448 }else{
449 //Need to actually save the full file
450 saved = saveDesktopFile();
451 }
452 return saved;
453 }
454
addToMenu(QMenu * topmenu)455 void XDGDesktop::addToMenu(QMenu *topmenu){
456 if(!this->isValid()){ return; }
457 if(actions.isEmpty()){
458 //Just a single entry point - no extra actions
459 QAction *act = new QAction(this->name, topmenu);
460 act->setIcon(LXDG::findIcon(this->icon, ""));
461 act->setToolTip(this->comment);
462 act->setWhatsThis(this->filePath);
463 topmenu->addAction(act);
464 }else{
465 //This app has additional actions - make this a sub menu
466 // - first the main menu/action
467 QMenu *submenu = new QMenu(this->name, topmenu);
468 submenu->setIcon( LXDG::findIcon(this->icon,"") );
469 //This is the normal behavior - not a special sub-action (although it needs to be at the top of the new menu)
470 QAction *act = new QAction(this->name, submenu);
471 act->setIcon(LXDG::findIcon(this->icon, ""));
472 act->setToolTip(this->comment);
473 act->setWhatsThis(this->filePath);
474 submenu->addAction(act);
475 //Now add entries for every sub-action listed
476 for(int sa=0; sa<this->actions.length(); sa++){
477 QAction *sact = new QAction( this->actions[sa].name, this);
478 sact->setIcon(LXDG::findIcon( this->actions[sa].icon, this->icon));
479 sact->setToolTip(this->comment);
480 sact->setWhatsThis("-action \""+this->actions[sa].ID+"\" \""+this->filePath+"\"");
481 submenu->addAction(sact);
482 }
483 topmenu->addMenu(submenu);
484 }
485 }
486
487
488 //====XDGDesktopList Functions ====
XDGDesktopList(QObject * parent,bool watchdirs)489 XDGDesktopList::XDGDesktopList(QObject *parent, bool watchdirs) : QObject(parent){
490 synctimer = new QTimer(this); //interval set automatically based on changes/interactions
491 connect(synctimer, SIGNAL(timeout()), this, SLOT(updateList()) );
492 keepsynced = watchdirs;
493 if(watchdirs){
494 watcher = new QFileSystemWatcher(this);
495 connect(watcher, SIGNAL(fileChanged(const QString&)), this, SLOT(watcherChanged()) );
496 connect(watcher, SIGNAL(directoryChanged(const QString&)), this, SLOT(watcherChanged()) );
497 }else{
498 watcher = 0;
499 }
500 }
501
~XDGDesktopList()502 XDGDesktopList::~XDGDesktopList(){
503 //nothing special to do here
504 }
505
instance()506 XDGDesktopList* XDGDesktopList::instance(){
507 static XDGDesktopList *APPLIST = 0;
508 if(APPLIST==0){
509 APPLIST = new XDGDesktopList(0, true);
510 }
511 return APPLIST;
512 }
513
watcherChanged()514 void XDGDesktopList::watcherChanged(){
515 if(synctimer->isActive()){ synctimer->stop(); }
516 synctimer->setInterval(1000); //1 second delay before check kicks off
517 synctimer->start();
518 }
519
updateList()520 void XDGDesktopList::updateList(){
521 //run the check routine
522 if(synctimer->isActive()){ synctimer->stop(); }
523 hashmutex.lock();
524 QStringList appDirs = LXDG::systemApplicationDirs(); //get all system directories
525 QStringList found, newfiles; //for avoiding duplicate apps (might be files with same name in different priority directories)
526 QStringList oldkeys = files.keys();
527 bool appschanged = false;
528 bool firstrun = lastCheck.isNull() || oldkeys.isEmpty();
529 lastCheck = QDateTime::currentDateTime();
530 //Variables for internal loop use only (to prevent re-initializing variable on every iteration)
531 QString path; QDir dir; QStringList apps;
532 for(int i=0; i<appDirs.length(); i++){
533 if( !dir.cd(appDirs[i]) ){ continue; } //could not open dir for some reason
534 apps = dir.entryList(QStringList() << "*.desktop",QDir::Files, QDir::Name);
535 for(int a=0; a<apps.length(); a++){
536 path = dir.absoluteFilePath(apps[a]);
537 if(files.contains(path) && (files.value(path)->lastRead>QFileInfo(path).lastModified()) ){
538 //Re-use previous data for this file (nothing changed)
539 found << files[path]->name; //keep track of which files were already found
540 }else{
541 if(files.contains(path)){ appschanged = true; files.take(path)->deleteLater(); }
542 XDGDesktop *dFile = new XDGDesktop(path, this);
543 if(dFile->type!=XDGDesktop::BAD){
544 appschanged = true; //flag that something changed - needed to load a file
545 if(!oldkeys.contains(path)){ newfiles << path; } //brand new file (not an update to a previously-read file)
546 files.insert(path, dFile);
547 found << dFile->name;
548 }else{
549 dFile->deleteLater(); //bad file - discard it
550 }
551 }
552 oldkeys.removeAll(path); //make sure this key does not get cleaned up later
553 } //end loop over apps
554 } //end loop over appDirs
555 //Save the extra info to the internal lists
556 if(!firstrun){
557 removedApps = oldkeys;//files which were removed
558 newApps = newfiles; //files which were added
559 }
560 //Now go through and cleanup any old keys where the associated file does not exist anymore
561 for(int i=0; i<oldkeys.length(); i++){
562 //qDebug() << "Removing file from internal map:" << oldkeys[i];
563 if(i==0){ appschanged = true; }
564 //files.remove(oldkeys[i]);
565 files.take(oldkeys[i])->deleteLater();
566 }
567 //If this class is automatically managing the lists, update the watched files/dirs and send out notifications
568 if(watcher!=0){
569 if(appschanged){ qDebug() << "Auto App List Update:" << lastCheck << "Files Found:" << files.count(); }
570 watcher->removePaths(QStringList() << watcher->files() << watcher->directories());
571 watcher->addPaths(appDirs);
572 if(appschanged){ emit appsUpdated(); }
573 synctimer->setInterval(60000); //Update in 1 minute if nothing changes before then
574 synctimer->start();
575 }
576 hashmutex.unlock();
577 }
578
apps(bool showAll,bool showHidden)579 QList<XDGDesktop*> XDGDesktopList::apps(bool showAll, bool showHidden){
580 //showAll: include invalid files, showHidden: include NoShow/Hidden files
581 //hashmutex.lock();
582 QStringList keys = files.keys();
583 QList<XDGDesktop*> out;
584 for(int i=0; i<keys.length(); i++){
585 if( showHidden || !files[keys[i]]->isHidden ){ //this is faster than the "checkValidity()" function below - so always filter here first
586 if( files[keys[i]]->isValid(showAll) ){
587 out << files[keys[i]];
588 }
589 }
590 }
591 //hashmutex.unlock();
592 return out;
593 }
594
findAppFile(QString filename)595 XDGDesktop* XDGDesktopList::findAppFile(QString filename){
596 //hashmutex.lock();
597 QStringList keys = files.keys().filter(filename);
598 QString chk = filename.section("/",-1);
599 XDGDesktop *found = 0;
600 for(int i=0; i<keys.length(); i++){
601 if(keys[i] == filename || keys[i].endsWith("/"+chk)){ found = files[keys[i]]; }
602 }
603 //hashmutex.unlock();
604 //No matches
605 return found;
606 }
607
populateMenu(QMenu * topmenu,bool byCategory)608 void XDGDesktopList::populateMenu(QMenu *topmenu, bool byCategory){
609 topmenu->clear();
610 if(byCategory){
611 QHash<QString, QList<XDGDesktop*> > APPS = LXDG::sortDesktopCats( this->apps(false,false) );
612 QStringList cats = APPS.keys();
613 cats.sort(); //make sure they are alphabetical
614 for(int i=0; i<cats.length(); i++){
615 //Make sure they are translated and have the right icons
616 QString name, icon;
617 if(cats[i]=="All"){continue; } //skip this listing for the menu
618 else if(cats[i] == "Multimedia"){ name = tr("Multimedia"); icon = "applications-multimedia"; }
619 else if(cats[i] == "Development"){ name = tr("Development"); icon = "applications-development"; }
620 else if(cats[i] == "Education"){ name = tr("Education"); icon = "applications-education"; }
621 else if(cats[i] == "Game"){ name = tr("Games"); icon = "applications-games"; }
622 else if(cats[i] == "Graphics"){ name = tr("Graphics"); icon = "applications-graphics"; }
623 else if(cats[i] == "Network"){ name = tr("Network"); icon = "applications-internet"; }
624 else if(cats[i] == "Office"){ name = tr("Office"); icon = "applications-office"; }
625 else if(cats[i] == "Science"){ name = tr("Science"); icon = "applications-science"; }
626 else if(cats[i] == "Settings"){ name = tr("Settings"); icon = "preferences-system"; }
627 else if(cats[i] == "System"){ name = tr("System"); icon = "applications-system"; }
628 else if(cats[i] == "Utility"){ name = tr("Utility"); icon = "applications-utilities"; }
629 else if(cats[i] == "Wine"){ name = tr("Wine"); icon = "wine"; }
630 else{ name = tr("Unsorted"); icon = "applications-other"; }
631
632 QMenu *menu = new QMenu(name, topmenu);
633 menu->setIcon(LXDG::findIcon(icon,""));
634 QList<XDGDesktop*> appL = APPS.value(cats[i]);
635 for( int a=0; a<appL.length(); a++){ appL[a]->addToMenu(menu); }
636 topmenu->addMenu(menu);
637 } //end loop over cats
638 }else{
639 QList<XDGDesktop*> APPS = this->apps(false, false);
640 for(int i=0; i<APPS.length(); i++){ APPS[i]->addToMenu(topmenu); }
641 }
642 }
643
644 //==== LFileInfo Functions ====
645 //Need some extra information not usually available by a QFileInfo
646 /*void LFileInfo::loadExtraInfo(){
647 desk = 0;
648 //Now load the extra information
649 if( this->suffix().isEmpty() && (this->absoluteFilePath().startsWith("/net/") || this->isDir()) ){
650 mime = "inode/directory";
651 //Special directory icons
652 QString name = this->fileName().toLower();
653 if(name=="desktop"){ icon = "user-desktop"; }
654 else if(name=="tmp"){ icon = "folder-temp"; }
655 else if(name=="video" || name=="videos"){ icon = "folder-video"; }
656 else if(name=="music" || name=="audio"){ icon = "folder-sound"; }
657 else if(name=="projects" || name=="devel"){ icon = "folder-development"; }
658 else if(name=="notes"){ icon = "folder-txt"; }
659 else if(name=="downloads"){ icon = "folder-downloads"; }
660 else if(name=="documents"){ icon = "folder-documents"; }
661 else if(name=="images" || name=="pictures"){ icon = "folder-image"; }
662 else if(this->absoluteFilePath().startsWith("/net/")){ icon = "folder-shared"; }
663 else if( !this->isReadable() ){ icon = "folder-locked"; }
664 }else if( this->suffix()=="desktop"){
665 mime = "application/x-desktop";
666 icon = "application-x-desktop"; //default value
667 desk = new XDGDesktop(this->absoluteFilePath(), 0);
668 if(desk->type!=XDGDesktop::BAD){
669 //use the specific desktop file info (if possible)
670 if(!desk->icon.isEmpty()){ icon = desk->icon; }
671 }
672 }else{
673 //Generic file, just determine the mimetype
674 mime = LXDG::findAppMimeForFile(this->fileName());
675 }
676 }
677 LFileInfo::LFileInfo(){
678 desk = 0;
679 }
680 LFileInfo::LFileInfo(QString filepath){ //overloaded contructor
681 this->setFile(filepath);
682 loadExtraInfo();
683 }
684 LFileInfo::LFileInfo(QFileInfo info){ //overloaded contructor
685 this->swap(info); //use the given QFileInfo without re-loading it
686 loadExtraInfo();
687 }
688
689 //Functions for accessing the extra information
690 // -- Return the mimetype for the file
691 QString LFileInfo::mimetype(){
692 if(mime=="inode/directory"){ return ""; }
693 else{ return mime; }
694 }
695
696 // -- Return the icon to use for this file
697 QString LFileInfo::iconfile(){
698 if(!icon.isEmpty()){
699 return icon;
700 }else{
701 if(!mime.isEmpty()){
702 QString tmp = mime;
703 tmp.replace("/","-");
704 return tmp;
705 }else if(this->isExecutable()){
706 return "application-x-executable";
707 }
708 }
709 return ""; //Fall back to nothing
710 }
711
712 // -- Check if this is an XDG desktop file
713 bool LFileInfo::isDesktopFile(){
714 if(desk==0){ return false; }
715 return (!desk->filePath.isEmpty());
716 }
717
718 // -- Allow access to the XDG desktop data structure
719 XDGDesktop* LFileInfo::XDG(){
720 return desk;
721 }
722
723 // -- Check if this is a readable video file (for thumbnail support)
724 bool LFileInfo::isVideo(){
725 if(!mime.startsWith("video/")){ return false; }
726 //Check the hardcoded list of known supported video formats to see if the thumbnail can be generated
727 return ( !LUtils::videoExtensions().filter(this->suffix().toLower()).isEmpty() );
728 }
729
730 // -- Check if this is a readable image file
731 bool LFileInfo::isImage(){
732 if(!mime.startsWith("image/")){ return false; } //quick return for non-image files
733 //Check the Qt subsystems to see if this image file can be read
734 return ( !LUtils::imageExtensions().filter(this->suffix().toLower()).isEmpty() );
735 }
736
737 bool LFileInfo::isAVFile(){
738 return (mime.startsWith("audio/") || mime.startsWith("video/") );
739 }*/
740
741
742 //==== LXDG Functions ====
checkExec(QString exec)743 bool LXDG::checkExec(QString exec){
744 //Return true(good) or false(bad)
745 //Check for quotes around the exec, and remove them as needed
746 if(exec.startsWith("\"") && exec.count("\"")>=2){ exec = exec.section("\"",1,1).simplified(); }
747 if(exec.startsWith("\'") && exec.count("\'")>=2){ exec = exec.section("\'",1,1).simplified(); }
748 if(exec.startsWith("/")){ return QFile::exists(exec); }
749 else{
750 QStringList paths = QString(getenv("PATH")).split(":");
751 for(int i=0; i<paths.length(); i++){
752 if(QFile::exists(paths[i]+"/"+exec)){ return true; }
753 }
754 }
755 return false; //could not find the executable in the current path(s)
756 }
757
systemApplicationDirs()758 QStringList LXDG::systemApplicationDirs(){
759 //Returns a list of all the directories where *.desktop files can be found
760 QStringList appDirs = QString(getenv("XDG_DATA_HOME")).split(":");
761 appDirs << QString(getenv("XDG_DATA_DIRS")).split(":");
762 if(appDirs.isEmpty()){ appDirs << "/usr/local/share" << "/usr/share" << LOS::AppPrefix()+"/share" << LOS::SysPrefix()+"/share" << L_SHAREDIR; }
763 appDirs.removeDuplicates();
764 //Now create a valid list
765 QStringList out;
766 for(int i=0; i<appDirs.length(); i++){
767 if( QFile::exists(appDirs[i]+"/applications") ){
768 out << appDirs[i]+"/applications";
769 //Also check any subdirs within this directory
770 // (looking at you KDE - stick to the standards!!)
771 out << LUtils::listSubDirectories(appDirs[i]+"/applications");
772 }
773 }
774 //qDebug() << "System Application Dirs:" << out;
775 return out;
776 }
777
sortDesktopCats(QList<XDGDesktop * > apps)778 QHash<QString,QList<XDGDesktop*> > LXDG::sortDesktopCats(QList<XDGDesktop*> apps){
779 //Sort the list of applications into their different categories (main categories only)
780 //Create the category lists
781 QList<XDGDesktop*> multimedia, dev, ed, game, graphics, network, office, science, settings, sys, utility, other, wine;
782 //Sort the apps into the lists
783 for(int i=0; i<apps.length(); i++){
784 if(apps[i]->catList.contains("AudioVideo")){ multimedia << apps[i]; }
785 else if(apps[i]->catList.contains("Development")){ dev << apps[i]; }
786 else if(apps[i]->catList.contains("Education")){ ed << apps[i]; }
787 else if(apps[i]->catList.contains("Game")){ game << apps[i]; }
788 else if(apps[i]->catList.contains("Graphics")){ graphics << apps[i]; }
789 else if(apps[i]->catList.contains("Network")){ network << apps[i]; }
790 else if(apps[i]->catList.contains("Office")){ office << apps[i]; }
791 else if(apps[i]->catList.contains("Science")){ science << apps[i]; }
792 else if(apps[i]->catList.contains("Settings")){ settings << apps[i]; }
793 else if(apps[i]->catList.contains("System")){ sys << apps[i]; }
794 else if(apps[i]->catList.contains("Utility")){ utility << apps[i]; }
795 else if(apps[i]->catList.contains("Wine")){ wine << apps[i]; }
796 else{ other << apps[i]; }
797 }
798 //Now create the output hash
799 QHash<QString,QList<XDGDesktop*> > out;
800 if(!multimedia.isEmpty()){ out.insert("Multimedia", LXDG::sortDesktopNames(multimedia)); }
801 if(!dev.isEmpty()){ out.insert("Development", LXDG::sortDesktopNames(dev)); }
802 if(!ed.isEmpty()){ out.insert("Education", LXDG::sortDesktopNames(ed)); }
803 if(!game.isEmpty()){ out.insert("Game", LXDG::sortDesktopNames(game)); }
804 if(!graphics.isEmpty()){ out.insert("Graphics", LXDG::sortDesktopNames(graphics)); }
805 if(!network.isEmpty()){ out.insert("Network", LXDG::sortDesktopNames(network)); }
806 if(!office.isEmpty()){ out.insert("Office", LXDG::sortDesktopNames(office)); }
807 if(!science.isEmpty()){ out.insert("Science", LXDG::sortDesktopNames(science)); }
808 if(!settings.isEmpty()){ out.insert("Settings", LXDG::sortDesktopNames(settings)); }
809 if(!sys.isEmpty()){ out.insert("System", LXDG::sortDesktopNames(sys)); }
810 if(!utility.isEmpty()){ out.insert("Utility", LXDG::sortDesktopNames(utility)); }
811 if(!wine.isEmpty()){ out.insert("Wine", LXDG::sortDesktopNames(wine)); }
812 if(!other.isEmpty()){ out.insert("Unsorted", LXDG::sortDesktopNames(other)); }
813 //return the resulting hash
814 return out;
815 }
816
817 //Return the icon to use for the given category
DesktopCatToIcon(QString cat)818 QString LXDG::DesktopCatToIcon(QString cat){
819 QString icon = "applications-other";
820 if(cat=="Multimedia"){ icon = "applications-multimedia"; }
821 else if(cat=="Development"){ icon = "applications-development"; }
822 else if(cat=="Education"){ icon = "applications-education"; }
823 else if(cat=="Game"){ icon = "applications-games"; }
824 else if(cat=="Graphics"){ icon = "applications-graphics"; }
825 else if(cat=="Network"){ icon = "applications-internet"; }
826 else if(cat=="Office"){ icon = "applications-office"; }
827 else if(cat=="Science"){ icon = "applications-science"; }
828 else if(cat=="Settings"){ icon = "preferences-system"; }
829 else if(cat=="System"){ icon = "applications-system"; }
830 else if(cat=="Utility"){ icon = "applications-utilities"; }
831 else if(cat=="Wine"){ icon = "wine"; }
832 return icon;
833 }
834
sortDesktopNames(QList<XDGDesktop * > apps)835 QList<XDGDesktop*> LXDG::sortDesktopNames(QList<XDGDesktop*> apps){
836 //Sort the list by name of the application
837 QHash<QString, XDGDesktop*> sorter;
838 for(int i=0; i<apps.length(); i++){
839 sorter.insert(apps[i]->name.toLower(), apps[i]);
840 }
841 QStringList keys = sorter.keys();
842 keys.sort();
843 //Re-assemble the output list
844 QList<XDGDesktop*> out;
845 for(int i=0; i<keys.length(); i++){
846 out << sorter[keys[i]];
847 }
848 return out;
849 }
850
setEnvironmentVars()851 void LXDG::setEnvironmentVars(){
852 //Set the default XDG environment variables if not already set
853 setenv("XDG_DATA_HOME",QString(QDir::homePath()+"/.local/share").toUtf8(), 0);
854 setenv("XDG_CONFIG_HOME",QString(QDir::homePath()+"/.config").toUtf8(), 0);
855 setenv("XDG_DATA_DIRS","/usr/local/share:/usr/share", 0);
856 setenv("XDG_CONFIG_DIRS","/etc/xdg:/usr/local/etc/xdg", 0);
857 setenv("XDG_CACHE_HOME",QString(QDir::homePath()+"/.cache").toUtf8(), 0);
858 setenv("QT_QPA_PLATFORMTHEME", "lthemeengine", 0);
859 setenv("QT_NO_GLIB","1",0);
860 //Don't set "XDG_RUNTIME_DIR" yet - need to look into the specs
861 }
862
findIcon(QString iconName,QString fallback)863 QIcon LXDG::findIcon(QString iconName, QString fallback){
864 //With the addition of the Lumina theme engine (8/3/17), switch back to using the Qt icon from theme method for apps
865 QIcon tmp;
866 if(!iconName.contains("libreoffice") || !QIcon::themeName().startsWith("material-design") ){ //libreoffice is stupid - their svg icons are un-renderable with Qt
867 tmp = QIcon::fromTheme(iconName);
868 /*if(iconName.contains("start-here")){
869 qDebug() << "[ICON]" << iconName << "found:" << !tmp.isNull() << tmp.name();
870 }*/
871 //if(tmp.isNull()){ tmp = QIcon::fromTheme(fallback); }
872 }
873 if(!tmp.isNull() && tmp.name()==iconName){ return tmp; } //found this in the theme
874 else if(iconName=="start-here-lumina"){
875 //Additional fallback options for the OS-branded icon
876 QString osname = LOS::OSName().simplified().toLower();
877 QStringList possible; possible << "distributor-logo-"+osname << osname;
878 QStringList words;
879 if(osname.contains(" ")){ words = osname.split(" "); }
880 else if(osname.contains("-")){ words = osname.split("-"); }
881 for(int i=0; i<words.length(); i++){ possible << "distributor-logo-"+words[i] << words[i]; }
882 //qDebug() << "Looking for possible OS icons:" << possible;
883 for(int i=0; i<possible.length(); i++){
884 if(QIcon::hasThemeIcon(possible[i])){ return QIcon::fromTheme(possible[i]); }
885 }
886 }
887 if(!fallback.isEmpty() && QIcon::hasThemeIcon(fallback)){ tmp = QIcon::fromTheme(fallback); return tmp; } //found this in the theme
888
889
890 //NOTE: This was re-written on 11/10/15 to avoid using the QIcon::fromTheme() framework
891 // -- Too many issues with SVG files and/or search paths with the built-in system
892
893 //Check if the icon is an absolute path and exists
894 bool DEBUG =false;
895 if(DEBUG){ qDebug() << "[LXDG] Find icon for:" << iconName; }
896 if(QFile::exists(iconName) && iconName.startsWith("/")){ return QIcon(iconName); }
897 else if(iconName.startsWith("/")){ iconName.section("/",-1); } //Invalid absolute path, just look for the icon
898 //Check if the icon is actually given
899 if(iconName.isEmpty()){
900 if(fallback.isEmpty()){ return QIcon(); }
901 else{ return LXDG::findIcon(fallback, ""); }
902 }
903 //Now try to find the icon from the theme
904 if(DEBUG){ qDebug() << "[LXDG] Start search for icon"; }
905 //Get the currently-set theme
906 QString cTheme = QIcon::themeName();
907 if(cTheme.isEmpty()){
908 QIcon::setThemeName("material-design-light");
909 cTheme = "material-design-light";
910 }
911 //Make sure the current search paths correspond to this theme
912 if( QDir::searchPaths("icontheme").filter("/"+cTheme+"/").isEmpty() ){
913 //Need to reset search paths: setup the "icontheme" "material-design-light" and "fallback" sets
914 // - Get all the base icon directories
915 QStringList paths;
916 paths << QDir::homePath()+"/.icons/"; //ordered by priority - local user dirs first
917 QStringList xdd = QString(getenv("XDG_DATA_HOME")).split(":");
918 xdd << QString(getenv("XDG_DATA_DIRS")).split(":");
919 for(int i=0; i<xdd.length(); i++){
920 if(QFile::exists(xdd[i]+"/icons")){ paths << xdd[i]+"/icons/"; }
921 }
922 //Now load all the dirs into the search paths
923 QStringList theme, oxy, fall;
924 QStringList themedeps = getIconThemeDepChain(cTheme, paths);
925 for(int i=0; i<paths.length(); i++){
926 theme << getChildIconDirs( paths[i]+cTheme);
927 for(int j=0; j<themedeps.length(); j++){ theme << getChildIconDirs(paths[i]+themedeps[j]); }
928 oxy << getChildIconDirs(paths[i]+"material-design-light"); //Lumina base icon set
929 fall << getChildIconDirs(paths[i]+"hicolor"); //XDG fallback (apps add to this)
930 }
931 //Now load all the icon theme dependencies in order (Theme1 -> Theme2 -> Theme3 -> Fallback)
932
933 //fall << LOS::AppPrefix()+"share/pixmaps"; //always use this as well as a final fallback
934 QDir::setSearchPaths("icontheme", theme);
935 QDir::setSearchPaths("default", oxy);
936 QDir::setSearchPaths("fallback", fall);
937 //qDebug() << "Setting Icon Search Paths:" << "\nicontheme:" << theme << "\nmaterial-design-light:" << oxy << "\nfallback:" << fall;
938 }
939 //Find the icon in the search paths
940 QIcon ico;
941 QStringList srch; srch << "icontheme" << "default" << "fallback";
942 for(int i=0; i<srch.length() && ico.isNull(); i++){
943 //Look for a svg first
944 if(QFile::exists(srch[i]+":"+iconName+".svg") && !iconName.contains("libreoffice") ){
945 //Be careful about how an SVG is loaded - needs to render the image onto a paint device
946 /*QSvgRenderer svg;
947 if( svg.load(srch[i]+":"+iconName+".svg") ){
948 //Could be loaded - now check that it is version 1.1+ (Qt has issues with 1.0? (LibreOffice Icons) )
949 float version = 1.1; //only downgrade files that explicitly set the version as older
950 QString svginfo = LUtils::readFile(srch[i]+":"+iconName+".svg").join("\n").section("<svg",1,1).section(">",0,0);
951 svginfo.replace("\t"," "); svginfo.replace("\n"," ");
952 if(svginfo.contains(" version=")){ version = svginfo.section(" version=\"",1,1).section("\"",0,0).toFloat(); }
953 if(version>=1.1){*/
954 ico.addFile(srch[i]+":"+iconName+".svg"); //could be loaded/parsed successfully
955 /*}else{
956 //qDebug() << "Old SVG Version file:" << iconName+".svg Theme:" << srch[i];
957 //qDebug() << "SVGInfo:" << svginfo;
958 }
959 }else{
960 qDebug() << "Found bad SVG file:" << iconName+".svg Theme:" << srch[i];
961 }*/
962 }
963 if(QFile::exists(srch[i]+":"+iconName+".png")){
964 //simple PNG image - load directly into the QIcon structure
965 ico.addFile(srch[i]+":"+iconName+".png");
966 }
967
968 }
969 //If still no icon found, look for any image format in the "pixmaps" directory
970 if(ico.isNull()){
971 //qDebug() << "Null Icon:" << iconName << LOS::AppPrefix();
972 if(QFile::exists(LOS::AppPrefix()+"share/pixmaps/"+iconName)){
973 ico.addFile(LOS::AppPrefix()+"share/pixmaps/"+iconName);
974 }else{
975 //Need to scan for any close match in the directory
976 QDir pix(LOS::AppPrefix()+"share/pixmaps");
977 QStringList formats = LUtils::imageExtensions();
978 QStringList found = pix.entryList(QStringList() << iconName, QDir::Files, QDir::Unsorted);
979 if(found.isEmpty()){ found = pix.entryList(QStringList() << iconName+"*", QDir::Files, QDir::Unsorted); }
980 //qDebug() << "Found pixmaps:" << found << formats;
981 //Use the first one found that is a valid format
982 for(int i=0; i<found.length(); i++){
983 if( formats.contains(found[i].section(".",-1).toLower()) ){
984 //qDebug() << " - Found non-theme icon:" << found[i];
985 ico.addFile( pix.absoluteFilePath(found[i]) );
986 break;
987 }
988 }
989
990 }
991 }
992 //Use the fallback icon if necessary
993 if(ico.isNull() ){
994 if(!fallback.isEmpty()){ ico = LXDG::findIcon(fallback,""); }
995 else if(iconName.contains("-x-") && !iconName.endsWith("-x-generic")){
996 //mimetype - try to use the generic type icon
997 ico = LXDG::findIcon(iconName.section("-x-",0,0)+"-x-generic", "");
998 }else if(iconName.contains("-")){
999 ico = LXDG::findIcon(iconName.section("-",0,-2), ""); //chop the last modifier off the end and try again
1000 }
1001 }
1002 if(ico.isNull()){
1003 qDebug() << "Could not find icon:" << iconName << fallback;
1004 }
1005 //Return the icon
1006 return ico;
1007 }
1008
getChildIconDirs(QString parent)1009 QStringList LXDG::getChildIconDirs(QString parent){
1010 //This is a recursive function that returns the absolute path(s) of directories with *.png files
1011 QDir D(parent);
1012 QStringList out;
1013 QStringList dirs = D.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
1014 if(!dirs.isEmpty() && (dirs.contains("32x32") || dirs.contains("scalable")) ){
1015 //Need to sort these directories by image size
1016 //qDebug() << " - Parent:" << parent << "Dirs:" << dirs;
1017 for(int i=0; i<dirs.length(); i++){
1018 if(dirs[i].contains("x")){ dirs[i].prepend( QString::number(10-dirs[i].section("x",0,0).length())+QString::number(10-dirs[i].at(0).digitValue())+"::::"); }
1019 else if(dirs[i].at(0).isNumber()){dirs[i].prepend( QString::number(10-dirs[i].length())+QString::number(10-dirs[i].at(0).digitValue())+"::::"); }
1020 else{ dirs[i].prepend( "0::::"); }
1021 }
1022 dirs.sort();
1023 for(int i=0; i<dirs.length(); i++){ dirs[i] = dirs[i].section("::::",1,50); } //chop the sorter off the front again
1024 //qDebug() << "Sorted:" << dirs;
1025 }
1026 QStringList img = D.entryList(QStringList() << "*.png" << "*.svg", QDir::Files | QDir::NoDotAndDotDot, QDir::NoSort);
1027 if(img.length() > 0){ out << D.absolutePath(); }
1028 for(int i=0; i<dirs.length(); i++){
1029 img.clear();
1030 img = getChildIconDirs(D.absoluteFilePath(dirs[i])); //re-use the old list variable
1031 if(img.length() > 0){ out << img; }
1032 }
1033 return out;
1034 }
1035
getIconThemeDepChain(QString theme,QStringList paths)1036 QStringList LXDG::getIconThemeDepChain(QString theme, QStringList paths){
1037 QStringList results;
1038 for(int i=0; i<paths.length(); i++){
1039 if( QFile::exists(paths[i]+theme+"/index.theme") ){
1040 QStringList deps = LUtils::readFile(paths[i]+theme+"/index.theme").filter("Inherits=");
1041 if(!deps.isEmpty()){
1042 deps = deps.first().section("=",1,-1).split(";",QString::SkipEmptyParts);
1043 for(int j=0; j<deps.length(); j++){
1044 results << deps[j] << getIconThemeDepChain(deps[j],paths);
1045 }
1046 }
1047 break; //found primary theme index file - stop here
1048 }
1049 }
1050 return results;;
1051 }
1052
systemMimeDirs()1053 QStringList LXDG::systemMimeDirs(){
1054 //Returns a list of all the directories where *.xml MIME files can be found
1055 QStringList appDirs = QString(getenv("XDG_DATA_HOME")).split(":");
1056 appDirs << QString(getenv("XDG_DATA_DIRS")).split(":");
1057 if(appDirs.isEmpty()){ appDirs << "/usr/local/share" << "/usr/share"; }
1058 //Now create a valid list
1059 QStringList out;
1060 for(int i=0; i<appDirs.length(); i++){
1061 if( QFile::exists(appDirs[i]+"/mime") ){
1062 out << appDirs[i]+"/mime";
1063 }
1064 }
1065 return out;
1066 }
1067
findMimeIcon(QString extension)1068 QIcon LXDG::findMimeIcon(QString extension){
1069 QIcon ico;
1070 QString mime = LXDG::findAppMimeForFile(extension);
1071 if(mime.isEmpty()){ mime = LXDG::findAppMimeForFile(extension.toLower()); }
1072 mime.replace("/","-"); //translate to icon mime name
1073 if(!mime.isEmpty()){ ico = LXDG::findIcon(mime, "unknown");} //use the "unknown" mimetype icon as fallback
1074 if(ico.isNull()){ ico = LXDG::findIcon("unknown",""); } //just in case
1075 return ico;
1076 }
1077
findAppMimeForFile(QString filename,bool multiple)1078 QString LXDG::findAppMimeForFile(QString filename, bool multiple){
1079 QString out;
1080 QString extension = filename.section(".",1,-1);
1081 if("."+extension == filename){ extension.clear(); } //hidden file without extension
1082 //qDebug() << "MIME SEARCH:" << filename << extension;
1083 QStringList mimefull = LXDG::loadMimeFileGlobs2();
1084 QStringList mimes;
1085 //Just in case the filename is a mimetype itself
1086 if( mimefull.filter(":"+filename+":").length() == 1){
1087 return filename;
1088 }
1089 while(mimes.isEmpty()){
1090 //Check for an exact mimetype match
1091 if(mimefull.filter(":"+extension+":").length() == 1){
1092 return extension;
1093 }
1094 //Look for globs at the end of the filename
1095 if(!extension.isEmpty()){
1096 mimes = mimefull.filter(":*."+extension);
1097 //If nothing found, try a case-insensitive search
1098 if(mimes.isEmpty()){ mimes = mimefull.filter(":*."+extension, Qt::CaseInsensitive); }
1099 //Now ensure that the filter was accurate (*.<extention>.<something> will still be caught)
1100 for(int i=0; i<mimes.length(); i++){
1101 if(!filename.endsWith( mimes[i].section(":*",-1), Qt::CaseInsensitive )){ mimes.removeAt(i); i--; }
1102 else if(mimes[i].section(":",0,0).length()==2){ mimes[i].prepend("0"); } //ensure 3-character priority number
1103 else if(mimes[i].section(":",0,0).length()==1){ mimes[i].prepend("00"); } //ensure 3-character priority number
1104 }
1105 }
1106 //Look for globs at the start of the filename
1107 if(mimes.isEmpty()){
1108 mimes = mimefull.filter(":"+filename.left(2)); //look for the first 2 characters initially
1109 //Note: This initial filter will only work if the wildcard (*) is not within the first 2 characters of the pattern
1110 //Now ensure that the filter was accurate
1111 for(int i=0; i<mimes.length(); i++){
1112 if(!filename.startsWith( mimes[i].section(":",3,-1,QString::SectionSkipEmpty).section("*",0,0), Qt::CaseInsensitive )){ mimes.removeAt(i); i--; }
1113 }
1114 }
1115 if(mimes.isEmpty()){
1116 if(extension.contains(".")){ extension = extension.section(".",1,-1); }
1117 else{ break; }
1118 }
1119 } //end of mimes while loop
1120 mimes.sort(); //this automatically puts them in reverse weight order (100 on down)
1121 QStringList matches;
1122 //qDebug() << "Mimes:" << mimes;
1123 for(int m=mimes.length()-1; m>=0; m--){
1124 QString mime = mimes[m].section(":",1,1,QString::SectionSkipEmpty);
1125 matches << mime;
1126 }
1127 //qDebug() << "Matches:" << matches;
1128 if(multiple && !matches.isEmpty() ){ out = matches.join("::::"); }
1129 else if( !matches.isEmpty() ){ out = matches.first(); }
1130 else{ //no mimetype found - assign one (internal only - no system database changes)
1131 if(extension.isEmpty()){ out = "unknown/"+filename.toLower(); }
1132 else{ out = "unknown/"+extension.toLower(); }
1133 }
1134 //qDebug() << "Out:" << out;
1135 return out;
1136 }
1137
findFilesForMime(QString mime)1138 QStringList LXDG::findFilesForMime(QString mime){
1139 QStringList out;
1140 QStringList mimes = LXDG::loadMimeFileGlobs2().filter(mime);
1141 for(int i=0; i<mimes.length(); i++){
1142 out << mimes[i].section(":",2,2); // "*.<extension>"
1143 }
1144 //qDebug() << "Mime to Files:" << mime << out;
1145 return out;
1146 }
1147
listFileMimeDefaults()1148 QStringList LXDG::listFileMimeDefaults(){
1149 //This will spit out a itemized list of all the mimetypes and relevant info
1150 // Output format: <mimetype>::::<extension>::::<default>::::<localized comment>
1151 QStringList mimes = LXDG::loadMimeFileGlobs2();
1152 //Remove all the application files from the list (only a single app defines/uses this type in general)
1153 /*QStringList apps = mimes.filter(":application/");
1154 //qDebug() << "List Mime Defaults";
1155 for(int i=0; i<apps.length(); i++){ mimes.removeAll(apps[i]); }*/
1156 //Now start filling the output list
1157 QStringList out;
1158 for(int i=0; i<mimes.length(); i++){
1159 QString mimetype = mimes[i].section(":",1,1);
1160 QStringList tmp = mimes.filter(mimetype);
1161 //Collect all the different extensions with this mimetype
1162 QStringList extlist;
1163 for(int j=0; j<tmp.length(); j++){
1164 mimes.removeAll(tmp[j]);
1165 extlist << tmp[j].section(":",2,2);
1166 }
1167 extlist.removeDuplicates(); //just in case
1168 //Now look for a current default for this mimetype
1169 QString dapp = LXDG::findDefaultAppForMime(mimetype); //default app;
1170
1171 //Create the output entry
1172 //qDebug() << "Mime entry:" << i << mimetype << dapp;
1173 out << mimetype+"::::"+extlist.join(", ")+"::::"+dapp+"::::"+LXDG::findMimeComment(mimetype);
1174
1175 i--; //go back one (continue until the list is empty)
1176 }
1177 return out;
1178 }
1179
findMimeComment(QString mime)1180 QString LXDG::findMimeComment(QString mime){
1181 QString comment;
1182 QStringList dirs = LXDG::systemMimeDirs();
1183 QString lang = QString(getenv("LANG")).section(".",0,0);
1184 QString shortlang = lang.section("_",0,0);
1185 for(int i=0; i<dirs.length(); i++){
1186 if(QFile::exists(dirs[i]+"/"+mime+".xml")){
1187 QStringList info = LUtils::readFile(dirs[i]+"/"+mime+".xml");
1188 QStringList filter = info.filter("<comment xml:lang=\""+lang+"\">");
1189 //First look for a full language match, then short language, then general comment
1190 if(filter.isEmpty()){ filter = info.filter("<comment xml:lang=\""+shortlang+"\">"); }
1191 if(filter.isEmpty()){ filter = info.filter("<comment>"); }
1192 if(!filter.isEmpty()){
1193 comment = filter.first().section(">",1,1).section("</",0,0);
1194 break;
1195 }
1196 }
1197 }
1198 return comment;
1199 }
1200
findDefaultAppForMime(QString mime)1201 QString LXDG::findDefaultAppForMime(QString mime){
1202 //First get the priority-ordered list of default file locations
1203 QStringList dirs;
1204 dirs << QString(getenv("XDG_CONFIG_HOME"))+"/lumina-mimeapps.list" \
1205 << QString(getenv("XDG_CONFIG_HOME"))+"/mimeapps.list";
1206 QStringList tmp = QString(getenv("XDG_CONFIG_DIRS")).split(":");
1207 for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/lumina-mimeapps.list"; }
1208 for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/mimeapps.list"; }
1209 dirs << QString(getenv("XDG_DATA_HOME"))+"/applications/lumina-mimeapps.list" \
1210 << QString(getenv("XDG_DATA_HOME"))+"/applications/mimeapps.list";
1211 tmp = QString(getenv("XDG_DATA_DIRS")).split(":");
1212 for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/applications/lumina-mimeapps.list"; }
1213 for(int i=0; i<tmp.length(); i++){ dirs << tmp[i]+"/applications/mimeapps.list"; }
1214
1215 //Now go through all the files in order of priority until a default is found
1216 QString cdefault;
1217 for(int i=0; i<dirs.length() && cdefault.isEmpty(); i++){
1218 if(!QFile::exists(dirs[i])){ continue; }
1219 QStringList info = LUtils::readFile(dirs[i]);
1220 if(info.isEmpty()){ continue; }
1221 QStringList white; //lists to keep track of during the search (black unused at the moment)
1222 QString workdir = dirs[i].section("/",0,-2); //just the directory
1223 // qDebug() << "Check File:" << mime << dirs[i] << workdir;
1224 int def = info.indexOf("[Default Applications]"); //find this line to start on
1225 if(def>=0){
1226 for(int d=def+1; d<info.length(); d++){
1227 //qDebug() << "Check Line:" << info[d];
1228 if(info[d].startsWith("[")){ break; } //starting a new section now - finished with defaults
1229 if(info[d].contains(mime+"=") ){
1230 white = info[d].section("=",1,-1).split(";") + white; //exact mime match - put at front of list
1231 break; //already found exact match
1232 }else if(info[d].contains("*") && info[d].contains("=") ){
1233 QRegExp rg(info[d].section("=",0,0), Qt::CaseSensitive, QRegExp::WildcardUnix);
1234 if(rg.exactMatch(mime)){
1235 white << info[d].section("=",1,-1).split(";"); //partial mime match - put at end of list
1236 }
1237 }
1238 }
1239 }
1240 // Now check for any white-listed files in this work dir
1241 // find the full path to the file (should run even if nothing in this file)
1242 //qDebug() << "WhiteList:" << white;
1243 for(int w=0; w<white.length(); w++){
1244 if(white[w].isEmpty()){ continue; }
1245 //First check for absolute paths to *.desktop file
1246 if( white[w].startsWith("/") ){
1247 if( QFile::exists(white[w]) ){ cdefault=white[w]; break; }
1248 else{ white.removeAt(w); w--; } //invalid file path - remove it from the list
1249 }
1250 //Now check for relative paths to file (in current priority-ordered work dir)
1251 else if( QFile::exists(workdir+"/"+white[w]) ){ cdefault=workdir+"/"+white[w]; break; }
1252 //Now go through the XDG DATA dirs and see if the file is in there
1253 else{
1254 white[w] = LUtils::AppToAbsolute(white[w]);
1255 if(QFile::exists(white[w])){ cdefault = white[w]; }
1256 }
1257 }
1258 /* WRITTEN BUT UNUSED CODE FOR MIMETYPE ASSOCIATIONS
1259 //Skip using this because it is simply an alternate/unsupported standard that conflicts with
1260 the current mimetype database standards. It is better/faster to parse 1 or 2 database glob files
1261 rather than have to iterate through hundreds of *.desktop files *every* time you need to
1262 find an application
1263
1264 if(addI<0 && remI<0){
1265 //Simple Format: <mimetype>=<*.desktop file>;<*.desktop file>;.....
1266 // (usually only one desktop file listed)
1267 info = info.filter(mimetype+"=");
1268 //Load the listed default(s)
1269 for(int w=0; w<info.length(); w++){
1270 white << info[w].section("=",1,50).split(";");
1271 }
1272 }else{
1273 //Non-desktop specific mimetypes file: has a *very* convoluted/inefficient algorithm (required by spec)
1274 if(addI<0){ addI = info.length(); } //no add section
1275 if(remI<0){ remI = info.length(); } // no remove section:
1276 //Whitelist items
1277 for(int a=addI+1; a!=remI && a<info.length(); a++){
1278 if(info[a].contains(mimetype+"=")){
1279 QStringList tmp = info[a].section("=",1,50).split(";");
1280 for(int t=0; t<tmp.length(); t++){
1281 if(!black.contains(tmp[t])){ white << tmp[t]; } //make sure this item is not on the black list
1282 }
1283 break;
1284 }
1285 }
1286 //Blacklist items
1287 for(int a=remI+1; a!=addI && a<info.length(); a++){
1288 if(info[a].contains(mimetype+"=")){ black << info[a].section("=",1,50).split(";"); break;}
1289 }
1290
1291 //STEPS 3/4 not written yet
1292
1293 } //End of non-DE mimetypes file */
1294
1295 } //End loop over files
1296
1297 return cdefault;
1298 }
1299
findAvailableAppsForMime(QString mime)1300 QStringList LXDG::findAvailableAppsForMime(QString mime){
1301 QStringList dirs = LXDG::systemApplicationDirs();
1302 QStringList out;
1303 //Loop over all possible directories that contain *.destop files
1304 // and check for the mimeinfo.cache file
1305 for(int i=0; i<dirs.length(); i++){
1306 if(QFile::exists(dirs[i]+"/mimeinfo.cache")){
1307 QStringList matches = LUtils::readFile(dirs[i]+"/mimeinfo.cache").filter(mime+"=");
1308 //Find any matches for our mimetype in the cache
1309 for(int j=0; j<matches.length(); j++){
1310 QStringList files = matches[j].section("=",1,1).split(";",QString::SkipEmptyParts);
1311 //Verify that each file exists before putting the full path to the file in the output
1312 for(int m=0; m<files.length(); m++){
1313 if(QFile::exists(dirs[i]+"/"+files[m])){
1314 out << dirs[i]+"/"+files[m];
1315 }else if(files[m].contains("-")){ //kde4-<filename> -> kde4/<filename> (stupid KDE variations!!)
1316 files[m].replace("-","/");
1317 if(QFile::exists(dirs[i]+"/"+files[m])){
1318 out << dirs[i]+"/"+files[m];
1319 }
1320 }
1321 }
1322 }
1323 }
1324 }
1325 //qDebug() << "Found Apps for Mime:" << mime << out << dirs;
1326 return out;
1327 }
1328
setDefaultAppForMime(QString mime,QString app)1329 void LXDG::setDefaultAppForMime(QString mime, QString app){
1330 //qDebug() << "Set Default App For Mime:" << mime << app;
1331 QString filepath = QString(getenv("XDG_CONFIG_HOME"))+"/lumina-mimeapps.list";
1332 QStringList cinfo = LUtils::readFile(filepath);
1333 //If this is a new file, make sure to add the header appropriately
1334 if(cinfo.isEmpty()){ cinfo << "#Automatically generated with lumina-config" << "# DO NOT CHANGE MANUALLY" << "[Default Applications]"; }
1335 //Check for any current entry for this mime type
1336 QStringList tmp = cinfo.filter(mime+"=");
1337 int index = -1;
1338 if(!tmp.isEmpty()){ index = cinfo.indexOf(tmp.first()); }
1339 //Now add the new default entry (if necessary)
1340 if(app.isEmpty()){
1341 if(index>=0){ cinfo.removeAt(index); } //Remove entry
1342 }else{
1343 if(index<0){
1344 cinfo << mime+"="+app+";"; //new entry
1345 }else{
1346 cinfo[index] = mime+"="+app+";"; //overwrite existing entry
1347 }
1348 }
1349 LUtils::writeFile(filepath, cinfo, true);
1350 return;
1351 }
1352
findAVFileExtensions()1353 QStringList LXDG::findAVFileExtensions(){
1354 //output format: QDir name filter for valid A/V file extensions
1355 QStringList globs = LXDG::loadMimeFileGlobs2();
1356 QStringList av = globs.filter(":audio/");
1357 av << globs.filter(":video/");
1358 for(int i=0; i<av.length(); i++){
1359 //Just use all audio/video mimetypes (for now)
1360 av[i] = av[i].section(":",2,2);
1361 //Qt5 Auto detection (broken - QMediaPlayer seg faults with Qt 5.3 - 11/24/14)
1362 /*if( QMultimedia::NotSupported != QMediaPlayer::hasSupport(av[i].section(":",1,1)) ){ av[i] = av[i].section(":",2,2); }
1363 else{ av.removeAt(i); i--; }*/
1364 }
1365 av.removeDuplicates();
1366 return av;
1367 }
1368
loadMimeFileGlobs2()1369 QStringList LXDG::loadMimeFileGlobs2(){
1370 //output format: <weight>:<mime type>:<file extension (*.something)>
1371 if(mimeglobs.isEmpty() || (mimechecktime < (QDateTime::currentMSecsSinceEpoch()-30000)) ){
1372 //qDebug() << "Loading globs2 mime DB files";
1373 mimeglobs.clear();
1374 mimechecktime = QDateTime::currentMSecsSinceEpoch(); //save the current time this was last checked
1375 QStringList dirs = LXDG::systemMimeDirs();
1376 for(int i=0; i<dirs.length(); i++){
1377 if(QFile::exists(dirs[i]+"/globs2")){
1378 QFile file(dirs[i]+"/globs2");
1379 if(!file.exists()){ continue; }
1380 if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){ continue; }
1381 QTextStream in(&file);
1382 while(!in.atEnd()){
1383 QString line = in.readLine();
1384 if(!line.startsWith("#")){
1385 mimeglobs << line.simplified();
1386 }
1387 }
1388 file.close();
1389 }
1390 if(i==dirs.length()-1 && mimeglobs.isEmpty()){
1391 //Could not find the mimetype database on the system - use the fallback file distributed with Lumina
1392 dirs << LOS::LuminaShare();
1393 }
1394 }//end loop over dirs
1395 }
1396 return mimeglobs;
1397 }
1398
1399 //Find all the autostart *.desktop files
findAutoStartFiles(bool includeInvalid)1400 QList<XDGDesktop*> LXDG::findAutoStartFiles(bool includeInvalid){
1401
1402 //First get the list of directories to search (system first, user-provided files come later and overwrite sys files as needed)
1403 QStringList paths = QString(getenv("XDG_CONFIG_DIRS")).split(":");
1404 paths << QString(getenv("XDG_CONFIG_HOME")).split(":");
1405 //Now go through them and find any valid *.desktop files
1406 QList<XDGDesktop*> files;
1407 QStringList filenames; //make it easy to see if this filename is an override
1408 QDir dir;
1409 for(int i=0;i<paths.length(); i++){
1410 if(!QFile::exists(paths[i]+"/autostart")){ continue; }
1411 dir.cd(paths[i]+"/autostart");
1412 QStringList tmp = dir.entryList(QStringList() << "*.desktop", QDir::Files, QDir::Name);
1413 for(int t=0; t<tmp.length(); t++){
1414 XDGDesktop *desk = new XDGDesktop(dir.absoluteFilePath(tmp[t]));
1415 if(desk->type == XDGDesktop::BAD){ continue; } //could not read file
1416 //Now figure out what to do with it
1417 if(filenames.contains(tmp[t])){
1418 //This is an overwrite of a lower-priority (system?) autostart file
1419 // find the other file
1420 int old = -1;
1421 for(int o=0; o<files.length(); o++){
1422 if(files[o]->filePath.endsWith("/"+tmp[t])){ old = o; break; } //found it
1423 }
1424 if(desk->isValid(false)){
1425 //Full override of the lower-priority file (might be replacing exec/tryexec fields)
1426 files.takeAt(old)->deleteLater();
1427 files.insert(old,desk);
1428 }else{
1429 //Small override file (only the "Hidden" field listed in spec)
1430 files[old]->isHidden = desk->isHidden; //replace this value with the override
1431 //files << desk; //still add this to the array (will be ignored/skipped later)
1432 }
1433 }else{
1434 //This is a new autostart file
1435 files << desk;
1436 filenames << tmp[t];
1437 }
1438 }//end of loop over *.desktop files
1439 } //end of loop over directories
1440
1441 //Now filter the results by validity if desired
1442 if(!includeInvalid){
1443 for(int i=0; i<files.length(); i++){
1444 if( !files[i]->isValid(false) || files[i]->isHidden ){
1445 //Invalid file - go ahead and remove it from the output list
1446 files.takeAt(i)->deleteLater();
1447 i--;
1448 }
1449 }
1450 }
1451 return files;
1452 }
1453
1454 /*bool LXDG::setAutoStarted(bool autostart, XDGDesktop *app){
1455 //First get the list of system directories to search (system first, user-provided files come later and overwrite sys files as needed)
1456 QStringList paths = QString(getenv("XDG_CONFIG_DIRS")).split(":");
1457 QString upath = QString(getenv("XDG_CONFIG_HOME")).section(":",0,0);
1458 if(upath.isEmpty()){ upath = QDir::homePath()+"/.config/autostart/"; }
1459 else{ upath.append("/autostart/"); }
1460 //Verify that the autostart directory exists
1461 if(!QFile::exists(upath)){
1462 QDir dir;
1463 dir.mkpath(upath);
1464 }
1465
1466 //Quick check/finish for user-defined files which are getting disabled (just remove the file)
1467 if(app->filePath.startsWith(upath) && !autostart){
1468 return QFile::remove(app->filePath);
1469 }
1470 bool sysfile = false;
1471 for(int i=0; i<paths.length(); i++){
1472 if(app->filePath.startsWith(paths[i]+"/autostart/") ){
1473 sysfile = true;
1474 //Change it to the user-modifiable directory
1475 app.filePath = app.filePath.replace(paths[i]+"/autostart/", upath);
1476 }
1477 }
1478 //Make sure the user-autostart dir is specified, and clean the app structure as necessary
1479 if( !app.filePath.startsWith(upath) && autostart){
1480 //Some other non-override autostart file - set it up to open with lumina-open
1481 if(!app.filePath.endsWith(".desktop")){
1482 app.exec = "lumina-open \""+app.filePath+"\"";
1483 app.tryexec = app.filePath; //make sure this file exists
1484 if(app.name.isEmpty()){ app.name = app.filePath.section("/",-1); }
1485 if(app.icon.isEmpty()){ app.icon = LXDG::findAppMimeForFile(app.filePath); app.icon.replace("/","-"); }
1486 app.filePath = upath+app.filePath.section("/",-1)+".desktop";
1487 app.type = XDGDesktop::APP;
1488 }else{
1489 //Some other *.desktop file on the system (keep almost all the existing settings/values)
1490 // - setup a redirect to the other file
1491 app.exec = "lumina-open \""+app.filePath+"\"";
1492 app.tryexec = app.filePath; //make sure this file exists
1493 // - Adjust the actual path where this file will get saved
1494 app.filePath = upath+app.filePath.section("/",-1);
1495 }
1496 }
1497 //Now save the "hidden" value into the file
1498 app.isHidden = !autostart; //if hidden, it will not be autostarted
1499 //Now save the file as necessary
1500 bool saved = false;
1501 //qDebug() << " - Saving AutoStart File:" << app.filePath << app.name << app.isHidden;
1502 if(sysfile){
1503 //Just an override file for the "hidden" field - nothing more
1504 QStringList info;
1505 info << "[Desktop Entry]" << "Type=Application" << QString("Hidden=")+ (app.isHidden ? QString("true"): QString("false"));
1506 saved = LUtils::writeFile(app.filePath, info, true);
1507 }else{
1508 //Need to actually save the full file
1509 saved = LXDG::saveDesktopFile(app);
1510 }
1511 return saved;
1512 }*/
1513
setAutoStarted(bool autostart,QString filePath)1514 bool LXDG::setAutoStarted(bool autostart, QString filePath){
1515 //Convenience function for the auto-start setter
1516 XDGDesktop desk(filePath);
1517 if(!filePath.endsWith(".desktop")){
1518 //bool ok = false;
1519 //desk = LXDG::loadDesktopFile(filePath, ok);
1520 //if(!ok){ return false; } //error reading input file
1521 //}else{
1522 desk.filePath = filePath;
1523 desk.useTerminal = false;
1524 }
1525 return desk.setAutoStarted(autostart);
1526 }
1527