1 //===========================================
2 // Lumina-DE source code
3 // Copyright (c) 2013-2016, Ken Moore
4 // Available under the 3-clause BSD license
5 // See the LICENSE file for full details
6 //===========================================
7 #include "LUtils.h"
8
9 #include "LuminaOS.h"
10 #include "LuminaXDG.h"
11
12 #include <QApplication>
13 #include <QtConcurrent>
14
15 #include <unistd.h>
16
17 /*inline QStringList ProcessRun(QString cmd, QStringList args){
18 //Assemble outputs
19 QStringList out; out << "1" << ""; //error code, string output
20 QProcess proc;
21 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
22 env.insert("LANG", "C");
23 env.insert("LC_MESSAGES", "C");
24 proc.setProcessEnvironment(env);
25 proc.setProcessChannelMode(QProcess::MergedChannels);
26 if(args.isEmpty()){
27 proc.start(cmd, QIODevice::ReadOnly);
28 }else{
29 proc.start(cmd,args ,QIODevice::ReadOnly);
30 }
31 QString info;
32 while(!proc.waitForFinished(1000)){
33 if(proc.state() == QProcess::NotRunning){ break; } //somehow missed the finished signal
34 QString tmp = proc.readAllStandardOutput();
35 if(tmp.isEmpty()){ proc.terminate(); }
36 else{ info.append(tmp); }
37 }
38 out[0] = QString::number(proc.exitCode());
39 out[1] = info+QString(proc.readAllStandardOutput());
40 return out;
41 }*/
42
43 //=============
44 // LUtils Functions
45 //=============
46
47 //Return the path to one of the XDG standard directories
standardDirectory(StandardDir dir,bool createAsNeeded)48 QString LUtils::standardDirectory(StandardDir dir, bool createAsNeeded){
49 // enum StandardDir {Desktop, Documents, Downloads, Music, Pictures, PublicShare, Templates, Videos}
50 QString var="XDG_%1_DIR";
51 QString defval="$HOME";
52 QString val;
53 switch (dir){
54 case Desktop:
55 var = var.arg("DESKTOP");
56 defval.append("/Desktop");
57 break;
58 case Documents:
59 var = var.arg("DOCUMENTS");
60 defval.append("/Documents");
61 break;
62 case Downloads:
63 var = var.arg("DOWNLOAD");
64 defval.append("/Downloads");
65 break;
66 case Music:
67 var = var.arg("MUSIC");
68 defval.append("/Music");
69 break;
70 case Pictures:
71 var = var.arg("PICTURES");
72 defval.append("/Pictures");
73 break;
74 case PublicShare:
75 var = var.arg("PUBLICSHARE");
76 break;
77 case Templates:
78 var = var.arg("TEMPLATES");
79 break;
80 case Videos:
81 var = var.arg("VIDEOS");
82 defval.append("/Videos");
83 break;
84 }
85 //Read the XDG user dirs file (if it exists)
86 QString configdir = getenv("XDG_DATA_HOME");
87 if(configdir.isEmpty()){ configdir = QDir::homePath()+"/.config"; }
88 QString conffile=configdir+"/user-dirs.dirs";
89 if(QFile::exists(conffile)){
90 static QStringList _contents;
91 static QDateTime _lastread;
92 if(_contents.isEmpty() || _lastread < QFileInfo(conffile).lastModified()){
93 _contents = LUtils::readFile(conffile);
94 _lastread = QDateTime::currentDateTime();
95 }
96 QStringList match = _contents.filter(var+"=");
97 if(!match.isEmpty()){
98 val = match.first().section("=",-1).simplified();
99 if(val.startsWith("\"")){ val = val.remove(0,1); }
100 if(val.endsWith("\"")){ val.chop(1); }
101 }
102 }
103 //Now check the value and return it
104 if(val.isEmpty()){ val = defval; }; //use the default value
105 val = val.replace("$HOME", QDir::homePath());
106 if(createAsNeeded && !QFile::exists(val) ){
107 QDir dir;
108 dir.mkpath(val);
109 }
110 return val;
111 }
112
runCommand(bool & success,QString command,QStringList arguments,QString workdir,QStringList env)113 QString LUtils::runCommand(bool &success, QString command, QStringList arguments, QString workdir, QStringList env){
114 QProcess proc;
115 proc.setProcessChannelMode(QProcess::MergedChannels); //need output
116 //First setup the process environment as necessary
117 QProcessEnvironment PE = QProcessEnvironment::systemEnvironment();
118 if(!env.isEmpty()){
119 for(int i=0; i<env.length(); i++){
120 if(!env[i].contains("=")){ continue; }
121 PE.insert(env[i].section("=",0,0), env[i].section("=",1,100));
122 }
123 }
124 proc.setProcessEnvironment(PE);
125 //if a working directory is specified, check it and use it
126 if(!workdir.isEmpty()){
127 proc.setWorkingDirectory(workdir);
128 }
129 //Now run the command (with any optional arguments)
130 if(arguments.isEmpty()){ proc.start(command); }
131 else{ proc.start(command, arguments); }
132 //Wait for the process to finish (but don't block the event loop)
133 QString info;
134 while(!proc.waitForFinished(1000)){
135 if(proc.state() == QProcess::NotRunning){ break; } //somehow missed the finished signal
136 QString tmp = proc.readAllStandardOutput();
137 if(tmp.isEmpty()){ proc.terminate(); }
138 else{ info.append(tmp); }
139 }
140 info.append(proc.readAllStandardOutput()); //make sure we don't miss anything in the output
141 success = (proc.exitCode()==0); //return success/failure
142 return info;
143 }
144
runCmd(QString cmd,QStringList args)145 int LUtils::runCmd(QString cmd, QStringList args){
146 bool success;
147 LUtils::runCommand(success, cmd, args);
148 return success;
149
150 /*QFuture<QStringList> future = QtConcurrent::run(ProcessRun, cmd, args);
151 return future.result()[0].toInt(); //turn it back into an integer return code*/
152 }
153
getCmdOutput(QString cmd,QStringList args)154 QStringList LUtils::getCmdOutput(QString cmd, QStringList args){
155 bool success;
156 QString log = LUtils::runCommand(success, cmd, args);
157 return log.split("\n");
158 /*QFuture<QStringList> future = QtConcurrent::run(ProcessRun, cmd, args);
159 return future.result()[1].split("\n"); //Split the return message into lines*/
160 }
161
readFile(QString filepath)162 QStringList LUtils::readFile(QString filepath){
163 QStringList out;
164 QFile file(filepath);
165 if(file.open(QIODevice::Text | QIODevice::ReadOnly)){
166 QTextStream in(&file);
167 while(!in.atEnd()){
168 out << in.readLine();
169 }
170 file.close();
171 }
172 return out;
173 }
174
writeFile(QString filepath,QStringList contents,bool overwrite)175 bool LUtils::writeFile(QString filepath, QStringList contents, bool overwrite){
176 QFile file(filepath);
177 if(file.exists() && !overwrite){ return false; }
178 bool ok = false;
179 if(contents.isEmpty()){ contents << "\n"; }
180 if( file.open(QIODevice::WriteOnly | QIODevice::Truncate) ){
181 QTextStream out(&file);
182 out << contents.join("\n");
183 if(!contents.last().isEmpty()){ out << "\n"; } //always end with a new line
184 file.close();
185 ok = true;
186 }
187 return ok;
188 }
189
isValidBinary(QString & bin)190 bool LUtils::isValidBinary(QString& bin){
191 //Trim off any quotes
192 if(bin.startsWith("\"") && bin.endsWith("\"")){ bin.chop(1); bin = bin.remove(0,1); }
193 if(bin.startsWith("\'") && bin.endsWith("\'")){ bin.chop(1); bin = bin.remove(0,1); }
194 //Now look for relative/absolute path
195 if(!bin.startsWith("/")){
196 //Relative path: search for it on the current "PATH" settings
197 QStringList paths = QString(qgetenv("PATH")).split(":");
198 for(int i=0; i<paths.length(); i++){
199 if(QFile::exists(paths[i]+"/"+bin)){ bin = paths[i]+"/"+bin; break;}
200 }
201 }
202 //bin should be the full path by now
203 if(!bin.startsWith("/")){ return false; }
204 QFileInfo info(bin);
205 bool good = (info.exists() && info.isExecutable());
206 if(good){ bin = info.absoluteFilePath(); }
207 return good;
208 }
209
openSettings(QString org,QString name,QObject * parent)210 QSettings* LUtils::openSettings(QString org, QString name, QObject *parent){
211 //Start with the base configuration directory
212 QString path = QString(getenv("XDG_CONFIG_HOME")).simplified();
213 if(path.isEmpty()){ path = QDir::homePath()+"/.config"; }
214 //Now add the organization directory
215 path = path+"/"+org;
216 QDir dir(path);
217 if(!dir.exists()){ dir.mkpath(path); }
218 //Now generate/check the name of the file
219 unsigned int user = getuid();
220 QString filepath = dir.absoluteFilePath(name+".conf");
221 if(user==0){
222 //special case - make sure we don't clobber the user-permissioned file
223 QString rootfilepath = dir.absoluteFilePath(name+"_root.conf");
224 if(!QFileInfo::exists(rootfilepath) && QFileInfo::exists(filepath)){
225 QFile::copy(filepath, rootfilepath); //make a copy of the user settings before they start to diverge
226 }
227 return (new QSettings(rootfilepath, QSettings::IniFormat, parent));
228 }else{
229 return (new QSettings(filepath, QSettings::IniFormat, parent));
230 }
231
232 }
233
systemApplicationDirs()234 QStringList LUtils::systemApplicationDirs(){
235 //Returns a list of all the directories where *.desktop files can be found
236 QStringList appDirs = QString(getenv("XDG_DATA_HOME")).split(":");
237 appDirs << QString(getenv("XDG_DATA_DIRS")).split(":");
238 if(appDirs.isEmpty()){ appDirs << "/usr/local/share" << "/usr/share" << LOS::AppPrefix()+"/share" << LOS::SysPrefix()+"/share" << L_SHAREDIR; }
239 appDirs.removeDuplicates();
240 //Now create a valid list
241 QStringList out;
242 for(int i=0; i<appDirs.length(); i++){
243 if( QFile::exists(appDirs[i]+"/applications") ){
244 out << appDirs[i]+"/applications";
245 //Also check any subdirs within this directory
246 // (looking at you KDE - stick to the standards!!)
247 out << LUtils::listSubDirectories(appDirs[i]+"/applications");
248 }
249 }
250 //qDebug() << "System Application Dirs:" << out;
251 return out;
252 }
253
GenerateOpenTerminalExec(QString term,QString dirpath)254 QString LUtils::GenerateOpenTerminalExec(QString term, QString dirpath){
255 //Check the input terminal application (default/fallback - determined by calling application)
256 //if(!LUtils::isValidBinary(term)){
257 if(term.endsWith(".desktop")){
258 //Pull the binary name out of the shortcut
259 XDGDesktop DF(term);
260 if(DF.type == XDGDesktop::BAD){ term = "xterm"; }
261 else{ term= DF.exec.section(" ",0,0); } //only take the binary name - not any other flags
262 }else{
263 term = "xterm"; //fallback
264 }
265 //}
266 //Now create the calling command for the designated terminal
267 // NOTE: While the "-e" routine is supposed to be universal, many terminals do not properly use it
268 // so add some special/known terminals here as necessary
269 QString exec;
270 qWarning() << " - Reached terminal initialization" << term;
271 if(term=="mate-terminal" || term=="lxterminal" || term=="gnome-terminal"){
272 exec = term+" --working-directory=\""+dirpath+"\"";
273 }else if(term=="xfce4-terminal"){
274 exec = term+" --default-working-directory=\""+dirpath+"\"";
275 }else if(term=="konsole" || term == "qterminal"){
276 exec = term+" --workdir \""+dirpath+"\"";
277 }else{
278 //-e is the parameter for most of the terminal appliction to execute an external command.
279 //In this case we start a shell in the selected directory
280 //Need the user's shell first
281 QString shell = QString(getenv("SHELL"));
282 if(!LUtils::isValidBinary(shell)){ shell = "/bin/sh"; } //universal fallback for a shell
283 exec = term + " -e \"cd " + dirpath + " && " + shell + " \" ";
284 }
285 qDebug() << exec;
286 return exec;
287 }
288
listSubDirectories(QString dir,bool recursive)289 QStringList LUtils::listSubDirectories(QString dir, bool recursive){
290 //This is a recursive method for returning the full paths of all subdirectories (if recursive flag is enabled)
291 QDir maindir(dir);
292 QStringList out;
293 QStringList subs = maindir.entryList(QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name);
294 for(int i=0; i<subs.length(); i++){
295 out << maindir.absoluteFilePath(subs[i]);
296 if(recursive){
297 out << LUtils::listSubDirectories(maindir.absoluteFilePath(subs[i]), recursive);
298 }
299 }
300 return out;
301 }
302
PathToAbsolute(QString path)303 QString LUtils::PathToAbsolute(QString path){
304 //Convert an input path to an absolute path (this does not check existance ot anything)
305 if(path.startsWith("/")){ return path; } //already an absolute path
306 if(path.startsWith("~")){ path.replace(0,1,QDir::homePath()); }
307 if(!path.startsWith("/")){
308 //Must be a relative path
309 if(path.startsWith("./")){ path = path.remove(2); }
310 path.prepend( QDir::currentPath()+"/");
311 }
312 return path;
313 }
314
AppToAbsolute(QString path)315 QString LUtils::AppToAbsolute(QString path){
316 if(path.startsWith("~/")){ path = path.replace("~/", QDir::homePath()+"/" ); }
317 if(path.startsWith("/") || QFile::exists(path)){ return path; }
318 if(path.endsWith(".desktop")){
319 //Look in the XDG dirs
320 QStringList dirs = systemApplicationDirs();
321 for(int i=0; i<dirs.length(); i++){
322 if(QFile::exists(dirs[i]+"/"+path)){ return (dirs[i]+"/"+path); }
323 }
324 }else{
325 //Look on $PATH for the binary
326 QStringList paths = QString(getenv("PATH")).split(":");
327 for(int i=0; i<paths.length(); i++){
328 if(QFile::exists(paths[i]+"/"+path)){ return (paths[i]+"/"+path); }
329 }
330 }
331 return path;
332 }
333
videoExtensions()334 QStringList LUtils::videoExtensions() {
335 static QStringList vidExtensions;
336 vidExtensions << "avi" << "mkv" << "mp4" << "mov" << "webm" << "wmv";
337 return vidExtensions;
338 }
339
imageExtensions(bool wildcards)340 QStringList LUtils::imageExtensions(bool wildcards){
341 //Note that all the image extensions are lowercase!!
342 static QStringList imgExtensions;
343 static QStringList imgExtensionsWC;
344 if(imgExtensions.isEmpty()){
345 QList<QByteArray> fmt = QImageReader::supportedImageFormats();
346 for(int i=0; i<fmt.length(); i++){
347 imgExtensionsWC << "*."+QString::fromLocal8Bit(fmt[i]);
348 imgExtensions << QString::fromLocal8Bit(fmt[i]);
349 }
350 }
351 if(wildcards){ return imgExtensionsWC; }
352 return imgExtensions;
353 }
354
LoadTranslation(QApplication * app,QString appname,QString locale,QTranslator * cTrans)355 QTranslator* LUtils::LoadTranslation(QApplication *app, QString appname, QString locale, QTranslator *cTrans){
356 //Get the current localization
357 QString langEnc = "UTF-8"; //default value
358 QString langCode = locale; //provided locale
359 if(langCode.isEmpty()){ langCode = getenv("LC_ALL"); }
360 if(langCode.isEmpty()){ langCode = getenv("LANG"); }
361 if(langCode.isEmpty()){ langCode = "en_US.UTF-8"; } //default to US english
362 //See if the encoding is included and strip it out as necessary
363 if(langCode.contains(".")){
364 langEnc = langCode.section(".",-1);
365 langCode = langCode.section(".",0,0);
366 }
367 //Now verify the encoding for the locale
368 if(langCode =="C" || langCode=="POSIX" || langCode.isEmpty()){
369 langEnc = "System"; //use the Qt system encoding
370 }
371 if(app !=0){
372 qDebug() << "Loading Locale:" << appname << langCode << langEnc;
373 //If an existing translator was provided, remove it first (will be replaced)
374 if(cTrans!=0){ app->removeTranslator(cTrans); }
375 //Setup the translator
376 cTrans = new QTranslator();
377 //Use the shortened locale code if specific code does not have a corresponding file
378 if(!QFile::exists(LOS::LuminaShare()+"i18n/"+appname+"_" + langCode + ".qm") && langCode!="en_US" ){
379 langCode.truncate( langCode.indexOf("_") );
380 }
381 QString filename = appname+"_"+langCode+".qm";
382 //qDebug() << "FileName:" << filename << "Dir:" << LOS::LuminaShare()+"i18n/";
383 if( cTrans->load( filename, LOS::LuminaShare()+"i18n/" ) ){
384 app->installTranslator( cTrans );
385 }else{
386 //Translator could not be loaded for some reason
387 cTrans = 0;
388 if(langCode!="en_US"){
389 qWarning() << " - Could not load Locale:" << langCode;
390 }
391 }
392 }else{
393 //Only going to set the encoding since no application given
394 qDebug() << "Loading System Encoding:" << langEnc;
395 }
396 //Load current encoding for this locale
397 QTextCodec::setCodecForLocale( QTextCodec::codecForName(langEnc.toUtf8()) );
398 return cTrans;
399 }
400
knownLocales()401 QStringList LUtils::knownLocales(){
402 QDir i18n = QDir(LOS::LuminaShare()+"i18n");
403 if( !i18n.exists() ){ return QStringList(); }
404 QStringList files = i18n.entryList(QStringList() << "lumina-desktop_*.qm", QDir::Files, QDir::Name);
405 if(files.isEmpty()){ return QStringList(); }
406 //Now strip off the filename and just leave the locale tag
407 for(int i=0; i<files.length(); i++){
408 files[i].chop(3); //remove the ".qm" on the end
409 files[i] = files[i].section("_",1,50).simplified();
410 }
411 files << "en_US"; //default locale
412 files.sort();
413 return files;
414 }
415
setLocaleEnv(QString lang,QString msg,QString time,QString num,QString money,QString collate,QString ctype)416 void LUtils::setLocaleEnv(QString lang, QString msg, QString time, QString num,QString money,QString collate, QString ctype){
417 //Adjust the current locale environment variables
418 bool all = false;
419 if(msg.isEmpty() && time.isEmpty() && num.isEmpty() && money.isEmpty() && collate.isEmpty() && ctype.isEmpty() ){
420 if(lang.isEmpty()){ return; } //nothing to do - no changes requested
421 all = true; //set everything to the "lang" value
422 }
423 //If no lang given, but others are given, then use the current setting
424 if(lang.isEmpty()){ lang = getenv("LC_ALL"); }
425 if(lang.isEmpty()){ lang = getenv("LANG"); }
426 if(lang.isEmpty()){ lang = "en_US"; }
427 //Now go through and set/unset the environment variables
428 // - LANG & LC_ALL
429 if(!lang.contains(".")){ lang.append(".UTF-8"); }
430 setenv("LANG",lang.toUtf8() ,1); //overwrite setting (this is always required as the fallback)
431 if(all){ setenv("LC_ALL",lang.toUtf8() ,1); }
432 else{ unsetenv("LC_ALL"); } //make sure the custom settings are used
433 // - LC_MESSAGES
434 if(msg.isEmpty()){ unsetenv("LC_MESSAGES"); }
435 else{
436 if(!msg.contains(".")){ msg.append(".UTF-8"); }
437 setenv("LC_MESSAGES",msg.toUtf8(),1);
438 }
439 // - LC_TIME
440 if(time.isEmpty()){ unsetenv("LC_TIME"); }
441 else{
442 if(!time.contains(".")){ time.append(".UTF-8"); }
443 setenv("LC_TIME",time.toUtf8(),1);
444 }
445 // - LC_NUMERIC
446 if(num.isEmpty()){ unsetenv("LC_NUMERIC"); }
447 else{
448 if(!num.contains(".")){ num.append(".UTF-8"); }
449 setenv("LC_NUMERIC",num.toUtf8(),1);
450 }
451 // - LC_MONETARY
452 if(money.isEmpty()){ unsetenv("LC_MONETARY"); }
453 else{
454 if(!money.contains(".")){ money.append(".UTF-8"); }
455 setenv("LC_MONETARY",money.toUtf8(),1);
456 }
457 // - LC_COLLATE
458 if(collate.isEmpty()){ unsetenv("LC_COLLATE"); }
459 else{
460 if(!collate.contains(".")){ collate.append(".UTF-8"); }
461 setenv("LC_COLLATE",collate.toUtf8(),1);
462 }
463 // - LC_CTYPE
464 if(ctype.isEmpty()){ unsetenv("LC_CTYPE"); }
465 else{
466 if(!ctype.contains(".")){ ctype.append(".UTF-8"); }
467 setenv("LC_CTYPE",ctype.toUtf8(),1);
468 }
469 }
470
currentLocale()471 QString LUtils::currentLocale(){
472 QString curr = getenv("LC_ALL");// = QLocale::system();
473 if(curr.isEmpty()){ curr = getenv("LANG"); }
474 if(curr.isEmpty()){ curr = "en_US"; }
475 curr = curr.section(".",0,0); //remove any encodings off the end
476 return curr;
477 }
478
DisplaySizeToBytes(QString num)479 double LUtils::DisplaySizeToBytes(QString num){
480 //qDebug() << "Convert Num to Bytes:" << num;
481 num = num.toLower().simplified();
482 num = num.remove(" ");
483 if(num.isEmpty()){ return 0.0; }
484 if(num.endsWith("b")){ num.chop(1); } //remove the "bytes" marker (if there is one)
485 QString lab = "b";
486 if(!num[num.size()-1].isNumber()){
487 lab = num.right(1); num.chop(1);
488 }
489 double N = num.toDouble();
490 QStringList labs; labs <<"b"<<"k"<<"m"<<"g"<<"t"<<"p"; //go up to petabytes for now
491 for(int i=0; i<labs.length(); i++){
492 if(lab==labs[i]){ break; }//already at the right units - break out
493 N = N*1024.0; //Move to the next unit of measurement
494 }
495 //qDebug() << " - Done:" << QString::number(N) << lab << num;
496 return N;
497 }
498
BytesToDisplaySize(qint64 ibytes)499 QString LUtils::BytesToDisplaySize(qint64 ibytes){
500 static QStringList labs = QStringList();
501 if(labs.isEmpty()){ labs << "B" << "K" << "M" << "G" << "T" << "P"; }
502 //Now get the dominant unit
503 int c=0;
504 double bytes = ibytes; //need to keep decimel places for calculations
505 while(bytes>=1000 && c<labs.length() ){
506 bytes = bytes/1024;
507 c++;
508 } //labs[c] is the unit
509 //Bytes are now
510 //Now format the number (up to 3 digits, not including decimel places)
511 QString num;
512 if(bytes>=100){
513 //No decimel places
514 num = QString::number(qRound(bytes));
515 }else if(bytes>=10){
516 //need 1 decimel place
517 num = QString::number( (qRound(bytes*10)/10.0) );
518 }else if(bytes>=1){
519 //need 2 decimel places
520 num = QString::number( (qRound(bytes*100)/100.0) );
521 }else{
522 //Fully decimel (3 places)
523 num = "0."+QString::number(qRound(bytes*1000));
524 }
525 //qDebug() << "Bytes to Human-readable:" << bytes << c << num << labs[c];
526 return (num+labs[c]);
527 }
528
SecondsToDisplay(int secs)529 QString LUtils::SecondsToDisplay(int secs){
530 if(secs < 0){ return "??"; }
531 QString rem; //remaining
532 if(secs > 3600){
533 int hours = secs/3600;
534 rem.append( QString::number(hours)+"h ");
535 secs = secs - (hours*3600);
536 }
537 if(secs > 60){
538 int min = secs/60;
539 rem.append( QString::number(min)+"m ");
540 secs = secs - (min*60);
541 }
542 if(secs > 0){
543 rem.append( QString::number(secs)+"s");
544 }else{
545 rem.append( "0s" );
546 }
547 return rem;
548 }
549