source: lumina/libLumina/LuminaXDG.cpp @ 0494142

releng/10.0.3releng/10.1releng/10.1.1
Last change on this file since 0494142 was 0494142, checked in by Ken Moore <ken@…>, 8 months ago

Add auto-detection of multimedia file extensions into lumina-fm, and also add a function into libLumina for converting a mime-type into a list of file extensions.

  • Property mode set to 100644
File size: 15.4 KB
Line 
1//===========================================
2//  Lumina-DE source code
3//  Copyright (c) 2013, Ken Moore
4//  Available under the 3-clause BSD license
5//  See the LICENSE file for full details
6//===========================================
7#include "LuminaXDG.h"
8
9static QStringList mimeglobs;
10static qint64 mimechecktime;
11
12//==== LXDG Functions ====
13XDGDesktop LXDG::loadDesktopFile(QString filePath, bool& ok){
14  //Create the outputs
15  ok=false;
16  XDGDesktop DF;
17    DF.isHidden=false;
18    DF.useTerminal=false;
19    DF.startupNotify=false;
20    DF.type = XDGDesktop::BAD;
21    DF.filePath = filePath;
22  //Check input file path validity
23  QFile file(filePath);
24  if(!file.exists()){ return DF; } //invalid file
25  //Get the current localization code
26  QString lang = QLocale::system().name(); //lang code
27  //Open the file
28  if(!file.open(QIODevice::Text | QIODevice::ReadOnly)){
29    return DF; 
30  }
31  QTextStream os(&file);
32  //Read in the File
33  while(!os.atEnd()){
34    QString line = os.readLine();
35    //Now parse out the file
36    line = line.simplified();
37    QString var = line.section("=",0,0).simplified();
38    QString loc = var.section("[",1,1).section("]",0,0).simplified(); // localization
39    var = var.section("[",0,0).simplified(); //remove the localization
40    QString val = line.section("=",1,1).simplified();
41    //-------------------
42    if(var=="Name"){ 
43      if(DF.name.isEmpty() && loc.isEmpty()){ DF.name = val; }
44      else if(loc == lang){ DF.name = val; }
45    }else if(var=="GenericName"){ 
46      if(DF.genericName.isEmpty() && loc.isEmpty()){ DF.genericName = val; }
47      else if(loc == lang){ DF.genericName = val; }
48    }else if(var=="Comment"){ 
49      if(DF.comment.isEmpty() && loc.isEmpty()){ DF.comment = val; }
50      else if(loc == lang){ DF.comment = val; }
51    }else if(var=="Icon"){ 
52      if(DF.icon.isEmpty() && loc.isEmpty()){ DF.icon = val; }
53      else if(loc == lang){ DF.icon = val; }
54    }
55    else if(var=="TryExec"){ DF.tryexec = val; }
56    else if(var=="Exec"){ DF.exec = val; }
57    else if(var=="NoDisplay" && !DF.isHidden){ DF.isHidden = (val.toLower()=="true"); }
58    else if(var=="Hidden" && !DF.isHidden){ DF.isHidden = (val.toLower()=="true"); }
59    else if(var=="Categories"){ DF.catList = val.split(";"); }
60    else if(var=="OnlyShowIn"){ DF.showInList = val.split(";"); }
61    else if(var=="NotShowIn"){ DF.notShowInList = val.split(";"); }
62    else if(var=="Terminal"){ DF.useTerminal= (val.toLower()=="true"); }
63    else if(var=="Actions"){ DF.actionList = val.split(";"); }
64    else if(var=="MimeType"){ DF.mimeList = val.split(";"); }
65    else if(var=="Keywords"){ 
66      if(DF.keyList.isEmpty() && loc.isEmpty()){ DF.keyList = val.split(";"); }
67      else if(loc == lang){ DF.keyList = val.split(";"); }
68    }
69    else if(var=="StartupNotify"){ DF.startupNotify = (val.toLower()=="true"); }
70    else if(var=="StartupWMClass"){ DF.startupWM = val; }
71    else if(var=="URL"){ DF.url = val;}
72    else if(var=="Type"){
73      if(val.toLower()=="application"){ DF.type = XDGDesktop::APP; }
74      else if(val.toLower()=="link"){ DF.type = XDGDesktop::LINK; }
75      else if(val.toLower()=="dir"){ DF.type = XDGDesktop::DIR; }
76      else{ DF.type = XDGDesktop::BAD; }
77    }
78  } //end reading file
79  file.close();
80  //Return the structure
81  ok=true;
82  return DF;
83}
84
85bool LXDG::checkValidity(XDGDesktop dFile, bool showAll){
86  bool ok=true;
87  bool DEBUG = false;
88  if(DEBUG){ qDebug() << "[LXDG] Check File validity:" << dFile.name << dFile.filePath; }
89  switch (dFile.type){
90    case XDGDesktop::BAD:
91      ok=false; 
92      if(DEBUG){ qDebug() << " - Bad file type"; }
93      break;
94    case XDGDesktop::APP:
95      if(!dFile.tryexec.isEmpty() && !LXDG::checkExec(dFile.tryexec)){ ok=false; if(DEBUG){ qDebug() << " - tryexec does not exist";} }
96      else if(dFile.exec.isEmpty() || dFile.name.isEmpty()){ ok=false; if(DEBUG){ qDebug() << " - exec or name is empty";} }
97      else if(!LXDG::checkExec(dFile.exec.section(" ",0,0,QString::SectionSkipEmpty)) ){ ok=false; if(DEBUG){ qDebug() << " - first exec binary does not exist";} }
98      break;
99    case XDGDesktop::LINK:
100      ok = !dFile.url.isEmpty();
101      if(DEBUG && !ok){ qDebug() << " - Link with missing URL"; }
102      break;
103    case XDGDesktop::DIR:
104      ok = !dFile.path.isEmpty();
105      if(DEBUG && !ok){ qDebug() << " - Dir with missing path"; }
106      break;
107    default:
108      ok=false;
109      if(DEBUG){ qDebug() << " - Unknown file type"; } 
110  }
111  if(!showAll){
112    if(!dFile.showInList.isEmpty() && !dFile.showInList.contains("Lumina")){ ok=false; }
113    else if(!dFile.notShowInList.isEmpty() && dFile.notShowInList.contains("Lumina")){ ok=false; }
114  }
115  return ok;
116}
117
118bool LXDG::checkExec(QString exec){
119  //Return true(good) or false(bad)
120  if(exec.startsWith("/")){ return QFile::exists(exec); }
121  else{
122    QStringList paths = QString(getenv("PATH")).split(":");
123    for(int i=0; i<paths.length(); i++){
124      if(QFile::exists(paths[i]+"/"+exec)){ return true; }         
125    }
126  }
127  return false; //could not find the executable in the current path(s)
128}
129
130QStringList LXDG::systemApplicationDirs(){
131  //Returns a list of all the directories where *.desktop files can be found
132  QStringList appDirs = QString(getenv("XDG_DATA_HOME")).split(":");
133  appDirs << QString(getenv("XDG_DATA_DIRS")).split(":");
134  if(appDirs.isEmpty()){ appDirs << "/usr/local/share" << "/usr/share"; }
135  //Now create a valid list
136  QStringList out;
137  for(int i=0; i<appDirs.length(); i++){
138    if( QFile::exists(appDirs[i]+"/applications") ){
139      out << appDirs[i]+"/applications";           
140    }
141  }
142  return out;
143}
144
145QList<XDGDesktop> LXDG::systemDesktopFiles(bool showAll, bool showHidden){
146  //Returns a list of all the unique *.desktop files that were found
147  QStringList appDirs = LXDG::systemApplicationDirs();
148  QStringList found; //for avoiding duplicate apps
149  QList<XDGDesktop> out;
150  bool ok; //for internal use only
151  for(int i=0; i<appDirs.length(); i++){
152      QDir dir(appDirs[i]);
153      QStringList apps = dir.entryList(QStringList() << "*.desktop",QDir::Files, QDir::Name);
154      for(int a=0; a<apps.length(); a++){
155        ok=false;
156        XDGDesktop dFile = LXDG::loadDesktopFile(dir.absoluteFilePath(apps[a]),ok);
157        if( LXDG::checkValidity(dFile, showAll) ){
158          if( !found.contains(dFile.name) && (!dFile.isHidden || showHidden) ){
159            out << dFile;
160            found << dFile.name;
161          }
162        }
163      }
164  }
165  return out;
166}
167
168QHash<QString,QList<XDGDesktop> > LXDG::sortDesktopCats(QList<XDGDesktop> apps){
169  //Sort the list of applications into their different categories (main categories only)
170  //Create the category lists
171  QList<XDGDesktop> multimedia, dev, ed, game, graphics, network, office, science, settings, sys, utility, other;
172  //Sort the apps into the lists
173  for(int i=0; i<apps.length(); i++){
174    if(apps[i].catList.contains("AudioVideo")){ multimedia << apps[i]; }
175    else if(apps[i].catList.contains("Development")){ dev << apps[i]; }
176    else if(apps[i].catList.contains("Education")){ ed << apps[i]; }
177    else if(apps[i].catList.contains("Game")){ game << apps[i]; }
178    else if(apps[i].catList.contains("Graphics")){ graphics << apps[i]; }
179    else if(apps[i].catList.contains("Network")){ network << apps[i]; }
180    else if(apps[i].catList.contains("Office")){ office << apps[i]; }
181    else if(apps[i].catList.contains("Science")){ science << apps[i]; }
182    else if(apps[i].catList.contains("Settings")){ settings << apps[i]; }
183    else if(apps[i].catList.contains("System")){ sys << apps[i]; }
184    else if(apps[i].catList.contains("Utility")){ utility << apps[i]; }
185    else{ other << apps[i]; }
186  }
187  //Now create the output hash
188  QHash<QString,QList<XDGDesktop> > out;
189  if(!multimedia.isEmpty()){ out.insert("Multimedia", LXDG::sortDesktopNames(multimedia)); }
190  if(!dev.isEmpty()){ out.insert("Development", LXDG::sortDesktopNames(dev)); }
191  if(!ed.isEmpty()){ out.insert("Education", LXDG::sortDesktopNames(ed)); }
192  if(!game.isEmpty()){ out.insert("Game", LXDG::sortDesktopNames(game)); }
193  if(!graphics.isEmpty()){ out.insert("Graphics", LXDG::sortDesktopNames(graphics)); }
194  if(!network.isEmpty()){ out.insert("Network", LXDG::sortDesktopNames(network)); }
195  if(!office.isEmpty()){ out.insert("Office", LXDG::sortDesktopNames(office)); }
196  if(!science.isEmpty()){ out.insert("Science", LXDG::sortDesktopNames(science)); }
197  if(!settings.isEmpty()){ out.insert("Settings", LXDG::sortDesktopNames(settings)); }
198  if(!sys.isEmpty()){ out.insert("System", LXDG::sortDesktopNames(sys)); }
199  if(!utility.isEmpty()){ out.insert("Utility", LXDG::sortDesktopNames(utility)); }
200  if(!other.isEmpty()){ out.insert("Unsorted", LXDG::sortDesktopNames(other)); }
201  //return the resulting hash
202  return out;
203}
204
205QList<XDGDesktop> LXDG::sortDesktopNames(QList<XDGDesktop> apps){
206  //Sort the list by name of the application
207  QHash<QString, XDGDesktop> sorter;
208  for(int i=0; i<apps.length(); i++){
209    sorter.insert(apps[i].name, apps[i]);         
210  }
211  QStringList keys = sorter.keys();
212  keys.sort();
213  //Re-assemble the output list
214  QList<XDGDesktop> out;
215  for(int i=0; i<keys.length(); i++){
216    out << sorter[keys[i]];
217  }
218  return out;
219}
220
221QString LXDG::getDesktopExec(XDGDesktop app){
222  //Generate the executable line for the application
223  QString out;
224  if(app.exec.isEmpty()){ return ""; }
225  else if(app.useTerminal){
226    out = "xterm -lc -e "+app.exec; 
227  }else{
228    out = app.exec;       
229  }
230  return out;
231}
232
233void LXDG::setEnvironmentVars(){
234  //Set the default XDG environment variables if not already set
235  setenv("XDG_DATA_HOME",QString(QDir::homePath()+"/.local/share").toUtf8(), 0);
236  setenv("XDG_CONFIG_HOME",QString(QDir::homePath()+"/.config").toUtf8(), 0);
237  setenv("XDG_DATA_DIRS","/usr/local/share:/usr/share", 0);
238  setenv("XDG_CONFIG_DIRS","/etc/xdg", 0);
239  setenv("XDG_CACHE_HOME",QString(QDir::homePath()+"/.cache").toUtf8(), 0);
240  //Don't set "XDG_RUNTIME_DIR" yet - need to look into the specs
241}
242
243QIcon LXDG::findIcon(QString iconName, QString fallback){
244  //Check if the icon is an absolute path and exists
245  if(QFile::exists(iconName) && iconName.startsWith("/")){ return QIcon(iconName); }
246  else if(iconName.startsWith("/")){ iconName.section("/",-1); } //Invalid absolute path, just looks for the icon
247  //Check if the icon is actually given
248  if(iconName.isEmpty()){ return QIcon(fallback); }
249  //Now try to find the icon from the theme
250  bool DEBUG = false;
251  if(DEBUG){ qDebug() << "[LXDG] Find icon for:" << iconName; }
252  //Check the default theme search paths
253  QStringList paths = QIcon::themeSearchPaths();
254  if(paths.isEmpty()){
255    //Set the XDG default icon theme search paths
256    paths << QDir::homePath()+"/.icons";
257    QStringList xdd = QString(getenv("XDG_DATA_HOME")).split(":");
258    xdd << QString(getenv("XDG_DATA_DIRS")).split(":");
259    for(int i=0; i<xdd.length(); i++){
260      paths << xdd[i]+"/icons";     
261    }
262    paths << "/usr/local/share/pixmaps";
263    QIcon::setThemeSearchPaths(paths);
264  }
265  if(DEBUG){ qDebug() << "[LXDG] Icon search paths:" << paths; }
266  //Finding an icon from the current theme is already built into Qt, just use it
267  QString cTheme = QIcon::themeName();
268  if(cTheme.isEmpty()){ QIcon::setThemeName("oxygen"); } //set the XDG default theme
269  if(DEBUG){ qDebug() << "[LXDG] Current theme:" << cTheme; }
270  //Try to load the icon from the current theme
271  QIcon ico = QIcon::fromTheme(iconName);
272  //Try to load the icon from /usr/local/share/pixmaps
273  if( ico.isNull() ){
274    //qDebug() << "Could not find icon:" << iconName;
275    QDir base("/usr/local/share/pixmaps");
276    QStringList matches = base.entryList(QStringList() << "*"+iconName+"*", QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
277    if( !matches.isEmpty() ){
278      ico = QIcon("/usr/local/share/pixmaps/"+matches[0]); //just use the first match
279    }else{
280      //Fallback on a manual search over the default theme directories (hicolor, then oxygen)
281      if( QDir::searchPaths("fallbackicons").isEmpty() ){
282        //Set the fallback search paths
283        QString base = "/usr/local/share/icons/";
284        QDir::setSearchPaths("fallbackicons", QStringList() << getChildIconDirs(base+"hicolor") << getChildIconDirs(base+"oxygen") ); 
285      }
286      if(QFile::exists("fallbackicons:"+iconName+".png")){
287        ico = QIcon("fallbackicons:"+iconName+".png");
288      }
289    }
290  }
291  //Use the fallback icon if necessary
292  if(ico.isNull() && !fallback.isEmpty()){
293    ico = LXDG::findIcon(fallback,"");   
294  }
295  //Return the icon
296  return ico;
297}
298
299QStringList LXDG::getChildIconDirs(QString parent){
300  //This is a recursive function that returns the absolute path(s) of directories with *.png files
301  QDir D(parent);
302  QStringList out;
303  QStringList dirs = D.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed);
304  QStringList pngs = D.entryList(QStringList() << "*.png", QDir::Files | QDir::NoDotAndDotDot, QDir::NoSort);
305  if(pngs.length() > 0){ out << D.absolutePath(); }
306  for(int i=0; i<dirs.length(); i++){
307    pngs.clear();
308    pngs = getChildIconDirs(D.absoluteFilePath(dirs[i])); //re-use the old list variable
309    if(pngs.length() > 0){ out << pngs; }
310  }
311  return out;
312}
313
314QStringList LXDG::systemMimeDirs(){
315  //Returns a list of all the directories where *.xml MIME files can be found
316  QStringList appDirs = QString(getenv("XDG_DATA_HOME")).split(":");
317  appDirs << QString(getenv("XDG_DATA_DIRS")).split(":");
318  if(appDirs.isEmpty()){ appDirs << "/usr/local/share" << "/usr/share"; }
319  //Now create a valid list
320  QStringList out;
321  for(int i=0; i<appDirs.length(); i++){
322    if( QFile::exists(appDirs[i]+"/mime") ){
323      out << appDirs[i]+"/mime";           
324    }
325  }
326  return out;
327}
328
329QIcon LXDG::findMimeIcon(QString extension){
330  QIcon ico;
331  QString mime = LXDG::findAppMimeForFile(extension);
332  if(mime.isEmpty()){ mime = LXDG::findAppMimeForFile(extension.toLower()); }
333  mime.replace("/","-"); //translate to icon mime name
334  ico = LXDG::findIcon(mime, "unknown"); //use the "unknown" mimetype icon as fallback 
335  if(ico.isNull()){ ico = LXDG::findIcon("unknown",""); } //just in case
336  return ico;
337}
338
339QString LXDG::findAppMimeForFile(QString extension){
340  QString out;
341  int weight = 0;
342  QStringList mimes = LXDG::loadMimeFileGlobs2().filter(":*."+extension);
343  for(int m=0; m<mimes.length(); m++){
344    QString mime = mimes[m].section(":",1,1,QString::SectionSkipEmpty);
345    if(mimes[m].section(":",0,0,QString::SectionSkipEmpty).toInt() > weight ){
346      out = mime;
347    }
348  }
349  return out;
350}
351
352QStringList LXDG::findFilesForMime(QString mime){
353  QStringList out;
354  QStringList mimes = LXDG::loadMimeFileGlobs2().filter(mime);
355  for(int i=0; i<mimes.length(); i++){
356    out << mimes[i].section(":",2,2); // "*.<extension>"
357  }
358  //qDebug() << "Mime to Files:" << mime << out;
359  return out;
360}
361
362QStringList LXDG::loadMimeFileGlobs2(){
363  //output format: <weight>:<mime type>:<file extension (*.something)>
364  if(mimeglobs.isEmpty() || (mimechecktime < (QDateTime::currentMSecsSinceEpoch()-30000)) ){
365    //qDebug() << "Loading globs2 mime DB files";
366    mimeglobs.clear();
367    mimechecktime = QDateTime::currentMSecsSinceEpoch(); //save the current time this was last checked
368    QStringList dirs = LXDG::systemMimeDirs();
369    for(int i=0; i<dirs.length(); i++){
370      if(QFile::exists(dirs[i]+"/globs2")){
371        QFile file(dirs[i]+"/globs2");
372        if(!file.exists()){ continue; }
373        if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){ continue; }
374        QTextStream in(&file);
375        while(!in.atEnd()){
376          QString line = in.readLine();
377          if(!line.startsWith("#")){
378            mimeglobs << line.simplified();
379          }
380        }
381        file.close();
382      }
383    }   
384  }
385  return mimeglobs;
386}
387
Note: See TracBrowser for help on using the repository browser.