1 //===========================================
2 //  Lumina desktop source code
3 //  Copyright (c) 2017, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 // Internal, OS-agnostic functionality for managing the object itself
8 //===========================================
9 #include <framework-OSInterface.h>
10 #include <QtConcurrent>
11 #include <QIcon>
12 #include <QQmlEngine>
13 
OSInterface(QObject * parent)14 OSInterface::OSInterface(QObject *parent) : QObject(parent){
15   watcher = 0;
16   iodevice = 0;
17   netman = 0;
18 }
19 
~OSInterface()20 OSInterface::~OSInterface(){
21   if(watcher!=0){
22     QStringList paths; paths << watcher->files() << watcher->directories();
23     if(!paths.isEmpty()){ watcher->removePaths(paths); }
24     watcher->deleteLater();
25   }
26   if(iodevice!=0){
27     if(iodevice->isOpen()){ iodevice->close(); }
28     iodevice->deleteLater();
29   }
30   if(netman!=0){
31     netman->deleteLater();
32   }
33 }
34 
instance()35 OSInterface* OSInterface::instance(){
36   static OSInterface* m_os_object = 0;
37   if(m_os_object==0){
38     m_os_object = new OSInterface();
39   }
40   return m_os_object;
41 }
42 
RegisterType()43 void OSInterface::RegisterType(){
44   static bool done = false;
45   if(done){ return; }
46   done=true;
47   qmlRegisterType<OSInterface>("Lumina.Backend.OSInterface", 2, 0, "OSInterface");
48 }
49 
50 //Start/stop interface systems
start()51 void OSInterface::start(){
52   if(!mediaDirectories().isEmpty()){ setupMediaWatcher(); }//will create/connect the filesystem watcher automatically
53   setupNetworkManager(60000, 1); //will create/connect the network monitor automatically
54   if(batteryAvailable()){ setupBatteryMonitor(30000, 1); } //30 second updates, 1 ms init delay
55   if(brightnessSupported()){ setupBrightnessMonitor(60000, 1); } //1 minute updates, 1 ms init delay
56   if(volumeSupported()){ setupVolumeMonitor(60000, 2); } //1 minute updates, 2 ms init delay
57   if(updatesSupported()){ setupUpdateMonitor(12*60*60*1000, 5*60*1000); } //12-hour updates, 5 minute delay
58   if(cpuSupported()){ setupCpuMonitor(2000, 20); } //2 second updates, 20 ms init delay
59   if(memorySupported()){ setupMemoryMonitor(2000, 21); } //2 second updates, 21 ms init delay
60   if(diskSupported()){ setupDiskMonitor(60000, 25); } //1 minute updates, 25 ms init delay
61 }
62 
stop()63 void OSInterface::stop(){
64   if(watcher!=0){ watcher->deleteLater();  watcher=0; }
65   if(batteryTimer!=0){ batteryTimer->stop(); disconnect(batteryTimer); }
66   if(brightnessTimer!=0){ brightnessTimer->stop(); disconnect(brightnessTimer); }
67   if(volumeTimer!=0){ volumeTimer->stop(); disconnect(volumeTimer); }
68   if(updateTimer!=0){ updateTimer->stop(); disconnect(updateTimer); }
69   if(cpuTimer!=0){ cpuTimer->stop(); disconnect(cpuTimer); }
70   if(memTimer!=0){ memTimer->stop(); disconnect(memTimer); }
71   if(diskTimer!=0){ diskTimer->stop(); disconnect(diskTimer); }
72   if(netman!=0){ disconnect(netman); netman->deleteLater(); netman = 0; }
73 }
74 
isRunning()75 bool OSInterface::isRunning(){ return _started; } //status of the object - whether it has been started yet
76 
connectWatcher()77 void OSInterface::connectWatcher(){
78   if(watcher==0){ return; }
79   connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(watcherFileChanged(QString)) );
80   connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(watcherDirChanged(QString)) );
81 }
82 
connectIodevice()83 void OSInterface::connectIodevice(){
84   if(iodevice==0){ return; }
85   connect(iodevice, SIGNAL(readyRead()), this, SLOT(iodeviceReadyRead()) );
86 }
87 
connectNetman()88 void OSInterface::connectNetman(){
89   if(netman==0){ return; }
90   connect(netman, SIGNAL(networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility)), this, SLOT(NetworkTimerUpdate()) );
91   connect(netman, SIGNAL(finished(QNetworkReply*)), this, SLOT(netRequestFinished(QNetworkReply*)) );
92   connect(netman, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(netSslErrors(QNetworkReply*, const QList<QSslError>&)) );
93 }
94 
verifyAppOrBin(QString chk)95 bool OSInterface::verifyAppOrBin(QString chk){
96   bool valid = !chk.isEmpty();
97   if(chk.contains(" ")){ chk = chk.section(" ",0,0); }
98   if(valid && chk.endsWith(".desktop")){
99     if(chk.startsWith("/")){ return QFile::exists(chk); }
100     valid = false;
101     QStringList paths;
102       paths << QString(getenv("XDG_DATA_HOME")) << QString(getenv("XDG_DATA_DIRS")).split(":");
103     for(int i=0; i<paths.length() && !valid; i++){
104       if(QFile::exists(paths[i]+"/applications")){ valid = findInDirectory(chk, paths[i]+"/applications", true); }
105     }
106   }else if(valid){
107     //Find the absolute path for this binary
108     if(!chk.startsWith("/")){
109       QStringList paths = QString(getenv("PATH")).split(":");
110       for(int i=0; i<paths.length(); i++){
111         if(QFile::exists(paths[i]+"/"+chk)){ chk = paths[i]+"/"+chk; break; }
112       }
113       if(!chk.startsWith("/")){ return false; } //could not find the file
114     }else if(!QFile::exists(chk)){
115       return false; //file does not exist
116     }
117     //Make sure it is executable by the user
118     valid = QFileInfo(chk).isExecutable();
119   }
120   return valid;
121 }
122 
runProcess(int & retcode,QString command,QStringList arguments,QString workdir,QStringList env)123 QString OSInterface::runProcess(int &retcode, QString command, QStringList arguments, QString workdir, QStringList env){
124   QProcess proc;
125     proc.setProcessChannelMode(QProcess::MergedChannels); //need output
126   //First setup the process environment as necessary
127   QProcessEnvironment PE = QProcessEnvironment::systemEnvironment();
128     if(!env.isEmpty()){
129       for(int i=0; i<env.length(); i++){
130     if(!env[i].contains("=")){ continue; }
131         PE.insert(env[i].section("=",0,0), env[i].section("=",1,100));
132       }
133     }
134     proc.setProcessEnvironment(PE);
135   //if a working directory is specified, check it and use it
136   if(!workdir.isEmpty()){
137     proc.setWorkingDirectory(workdir);
138   }
139   //Now run the command (with any optional arguments)
140   if(arguments.isEmpty()){ proc.start(command); }
141   else{ proc.start(command, arguments); }
142   //Wait for the process to finish (but don't block the event loop)
143   for(int i=0; i<10 && !proc.waitForFinished(500); i++){ //maximum of 5 seconds for command to finish
144     if(proc.state() == QProcess::NotRunning){ break; } //somehow missed the finished signal - go ahead and stop now
145   }
146   if(proc.state() != QProcess::NotRunning){ proc.terminate(); } //just in case - make sure to kill off the process
147   QString info = proc.readAllStandardOutput();
148   retcode = proc.exitCode(); //return success/failure
149   return info;
150 }
151 
runCmd(QString command,QStringList args)152 int OSInterface::runCmd(QString command, QStringList args){
153   int retcode;
154   runProcess(retcode, command, args);
155   return retcode;
156 }
157 
getCmdOutput(QString command,QStringList args)158 QStringList OSInterface::getCmdOutput(QString command, QStringList args){
159   int retcode;
160   return runProcess(retcode, command, args).split("\n");
161 }
162 
findInDirectory(QString file,QString dirpath,bool recursive)163 bool OSInterface::findInDirectory(QString file, QString dirpath, bool recursive){
164   bool found = QFile::exists(dirpath+"/"+file);
165   if(!found && recursive){
166     QDir dir(dirpath);
167     QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
168     for(int i=0; i<dirs.length() && !found; i++){ found = findInDirectory(file, dir.absoluteFilePath(dirs[i]), recursive); }
169   }
170   return found;
171 }
172 
readFile(QString path)173 QString OSInterface::readFile(QString path){
174   QFile file(path);
175   QString info;
176   if(file.open(QIODevice::ReadOnly)){
177     QTextStream out(&file);
178     info = out.readAll();
179     file.close();
180   }
181   return info;
182 }
183 
184 // ===========================
185 //  OS SPECIFIC EXISTANCE CHECKS
186 // ===========================
hasControlPanel()187 bool OSInterface::hasControlPanel(){
188   return verifyAppOrBin(controlPanelShortcut());
189 }
190 
hasAudioMixer()191 bool OSInterface::hasAudioMixer(){
192   return verifyAppOrBin(audioMixerShortcut());
193 }
194 
hasAppStore()195 bool OSInterface::hasAppStore(){
196   return verifyAppOrBin(appStoreShortcut());
197 }
198 
199 // ========================
200 //        MEDIA DIRECTORIES
201 // ========================
202 
203 // External Media Management (if system uses *.desktop shortcuts)
setupMediaWatcher()204 void OSInterface::setupMediaWatcher(){
205   //Create/connect the watcher if needed
206   if(watcher == 0){ watcher = new QFileSystemWatcher(); connectWatcher(); }
207   QStringList dirs = this->mediaDirectories();
208   if(dirs.isEmpty()){ return; } //nothing to do
209   //Make sure each directory is scanned **right now** (if it exists)
210   for(int i=0; i<dirs.length(); i++){
211     if(QFile::exists(dirs[i])){
212       handleMediaDirChange(dirs[i]);
213     }
214   }
215 }
216 
handleMediaDirChange(QString dir)217 bool OSInterface::handleMediaDirChange(QString dir){ //returns true if directory was handled
218   if( !this->mediaDirectories().contains(dir) ){ return false; } //not a media directory
219   QDir qdir(dir);
220   QStringList files = qdir.entryList(QStringList() << "*.desktop", QDir::Files, QDir::Name);
221   for(int i=0; i<files.length(); i++){ files[i]  = qdir.absoluteFilePath(files[i]); }
222   QString key = "media_files/"+dir;
223   if(files.isEmpty() && INFO.contains(key)){ INFO.remove(key); emit mediaShortcutsChanged(); } //no files for this directory at the moment
224   else{ INFO.insert("media_files/"+dir, files); emit mediaShortcutsChanged(); } //save these file paths for later
225   //Make sure the directory is still watched (sometimes the dir is removed/recreated on modification)
226   if(!watcher->directories().contains(dir)){ watcher->addPath(dir); }
227   return true;
228 }
229 
autoHandledMediaFiles()230 QStringList OSInterface::autoHandledMediaFiles(){
231   QStringList files;
232   QStringList keys = INFO.keys().filter("media_files/");
233   for(int i=0; i<keys.length(); i++){
234     if(keys[i].startsWith("media_files/")){ files << INFO[keys[i]].toStringList(); }
235   }
236   return files;
237 }
238 
239 // =============================
240 //  NETWORK INTERFACE FUNCTIONS
241 // =============================
242 // Qt-based NetworkAccessManager usage
setupNetworkManager(int update_ms,int delay_ms)243 void OSInterface::setupNetworkManager(int update_ms, int delay_ms){
244   if(netman==0){
245     netman = new QNetworkAccessManager(this);
246     connectNetman();
247   }
248   networkTimer = new QTimer(this);
249     networkTimer->setSingleShot(true);
250     networkTimer->setInterval(update_ms);
251     connect(networkTimer, SIGNAL(timeout()), this, SLOT(NetworkTimerUpdate()) );
252   QTimer::singleShot(delay_ms, this, SLOT(NetworkTimerUpdate()) );
253 }
254 
networkAvailable()255 bool OSInterface::networkAvailable(){
256   if(INFO.contains("netaccess/available")){ return INFO.value("netaccess/available").toBool(); }
257   return false;
258 }
259 
networkType()260 QString OSInterface::networkType(){
261   if(INFO.contains("netaccess/type")){ return INFO.value("netaccess/type").toString(); } //"wifi", "wired", or "cell"
262   return "";
263 }
264 
networkStrength()265 float OSInterface::networkStrength(){
266   if(INFO.contains("netaccess/strength")){ return INFO.value("netaccess/strength").toFloat(); } //percentage
267   return -1;
268 }
269 
networkIcon()270 QString OSInterface::networkIcon(){
271   if(INFO.contains("netaccess/icon")){ return INFO.value("netaccess/icon").toString(); }
272   return "";
273 }
274 
networkHostname()275 QString OSInterface::networkHostname(){
276   return QHostInfo::localHostName();
277 }
278 
networkAddress()279 QStringList OSInterface::networkAddress(){
280   QString addr;
281   if(INFO.contains("netaccess/address")){ addr = INFO.value("netaccess/address").toString(); }
282   return addr.split(", ");
283 }
284 
hasNetworkManager()285 bool OSInterface::hasNetworkManager(){
286   return verifyAppOrBin(networkManagerUtility());
287 }
288 
networkStatus()289 QString OSInterface::networkStatus(){
290   QString stat = "<b>%1</b><br>%2<br>%3";
291   return stat.arg(networkHostname(), networkType(), networkAddress().join("<br>"));
292 }
293 
294 //NetworkAccessManager slots
syncNetworkInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)295 void OSInterface::syncNetworkInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
296   //qDebug() << "[DEBUG] Got Net Access Changed";
297   hash->insert("netaccess/available", netman->networkAccessible()== QNetworkAccessManager::Accessible);
298   //Update all the other network status info at the same time
299   QNetworkConfiguration active;
300   QList<QNetworkConfiguration> netconfigL = netman->configuration().children();
301   for(int i=0; i<netconfigL.length(); i++){
302     if(!netconfigL[i].state().testFlag(QNetworkConfiguration::Discovered) ){ continue; } //skip this interface
303     QList<QNetworkAddressEntry> addressList = QNetworkInterface::interfaceFromName(netconfigL[i].name()).addressEntries();
304     //NOTE: There are often 2 addresses, IPv4 and IPv6
305     bool ok = false;
306     for(int j=0; j<addressList.length() && !ok; j++){
307       if( addressList[j].ip().isLoopback() ){ continue; }
308       else if(addressList[i].ip().isEqual(QHostAddress(QHostAddress::LocalHost)) || addressList[i].ip().isEqual(QHostAddress::LocalHostIPv6) ){ continue; }
309       addressList[j].ip().toIPv4Address(&ok);
310     }
311     if(ok){ active = netconfigL[i]; break; } //found a good one with a valid IPv4
312     //else if(!active.isValid()){
313   }
314   if(!active.isValid()){ active = netman->activeConfiguration(); } //use the default Qt-detected interface
315   //Type of connection
316   QString type;
317   switch(active.bearerTypeFamily()){
318     case QNetworkConfiguration::BearerEthernet: type="wired"; break;
319     case QNetworkConfiguration::BearerWLAN: type="wifi"; break;
320     case QNetworkConfiguration::Bearer2G: type="cell-2G"; break;
321     case QNetworkConfiguration::Bearer3G: type="cell-3G"; break;
322     case QNetworkConfiguration::Bearer4G: type="cell-4G"; break;
323     default: type=OS_networkTypeFromDeviceName(active.name()); //could not be auto-determined - run the OS-specific routine
324   }
325   hash->insert("netaccess/type", type);
326   float strength = 100;
327   if(type!="wired"){ strength = OS_networkStrengthFromDeviceName(active.name()); }
328   hash->insert("netaccess/strength", strength);
329 
330   //qDebug() << "Detected Device Status:" << active.identifier() << type << stat;
331   QNetworkInterface iface = QNetworkInterface::interfaceFromName(active.name());
332   //qDebug() << " - Configuration: Name:" << active.name() << active.bearerTypeName() << active.identifier();
333   //qDebug() << " - Interface: MAC Address:" << iface.hardwareAddress() << "Name:" << iface.name() << iface.humanReadableName() << iface.isValid();
334   QList<QNetworkAddressEntry> addressList = iface.addressEntries();
335   QStringList address;
336   //NOTE: There are often 2 addresses, IPv4 and IPv6
337   for(int i=0; i<addressList.length(); i++){
338     address << addressList[i].ip().toString();
339   }
340   //qDebug() << " - IP Address:" << address;
341   //qDebug() << " - Hostname:" << networkHostname();
342   hash->insert("netaccess/address", address.join(", "));
343 
344   //Figure out the icon used for this type/strnegth
345   QStringList icons;
346   if(type.startsWith("cell")){
347     if(address.isEmpty()){ icons <<"network-cell-off"; }
348     else if(strength>80){ icons <<"network-cell-connected-100"; }
349     else if(strength>60){ icons <<"network-cell-connected-75"; }
350     else if(strength>40){ icons << "network-cell-connected-50"; }
351     else if(strength>10){ icons << "network-cell-connected-25"; }
352     else if(strength >=0){ icons << "network-cell-connected-00"; }
353     icons << "network-cell"; //fallback - just use generic icon so we at least get off/on visibility
354 
355   }else if(type=="wifi"){
356     if(address.isEmpty()){ icons << "network-wireless-disconnected" << "network-wireless-00" << "network-wireless-off"; }
357     else if(strength>80){ icons << "network-wireless-100"; }
358     else if(strength>60){ icons << "network-wireless-75"; }
359     else if(strength>40){ icons << "network-wireless-50"; }
360     else if(strength>10){ icons << "network-wireless-25"; }
361     else if(strength >=0){ icons << "network-wireless-00"; }
362     icons << "network-wireless";  //fallback - just use generic icon so we at least get off/on visibility
363 
364   }else if(type=="wired"){
365     if(strength==100 && !address.isEmpty()){ icons << "network-wired-connected" << "network-wired"; }
366     else if(strength==100){ icons << "network-wired-pending" << "network-wired-aquiring"; }
367     else{ icons << "network-wired-unavailable" << "network-wired-disconnected" ; }
368   }
369   icons << "network-workgroup" << "network-unknown";
370   for(int i=0; i<icons.length(); i++){
371     if(QIcon::hasThemeIcon(icons[i])){ hash->insert("netaccess/icon",icons[i]); break; }
372   }
373   //qDebug() << "[DEBUG] Emit NetworkStatusChanged";
374   os->emit networkStatusChanged();
375   QTimer::singleShot(0, timer, SLOT(start()));
376 }
377 
378 
379 // ========================
380 //     TIMER-BASED MONITORS
381 // ========================
382 //Timer slots
383 
NetworkTimerUpdate()384 void OSInterface::NetworkTimerUpdate(){
385   if(networkTimer->isActive()){ networkTimer->stop(); } //just in case this was manually triggered
386   QtConcurrent::run(this, &OSInterface::syncNetworkInfo, this, &INFO, networkTimer);
387 }
388 
BatteryTimerUpdate()389 void OSInterface::BatteryTimerUpdate(){
390   if(batteryTimer->isActive()){ batteryTimer->stop(); } //just in case this was manually triggered
391   QtConcurrent::run(this, &OSInterface::syncBatteryInfo, this, &INFO, batteryTimer);
392 }
393 
UpdateTimerUpdate()394 void OSInterface::UpdateTimerUpdate(){
395   if(updateTimer->isActive()){ updateTimer->stop(); } //just in case this was manually triggered
396   QtConcurrent::run(this, &OSInterface::syncUpdateInfo, this, &INFO, updateTimer);
397 }
398 
BrightnessTimerUpdate()399 void OSInterface::BrightnessTimerUpdate(){
400   if(brightnessTimer->isActive()){ brightnessTimer->stop(); } //just in case this was manually triggered
401   QtConcurrent::run(this, &OSInterface::syncBrightnessInfo, this, &INFO, brightnessTimer);
402 }
403 
VolumeTimerUpdate()404 void OSInterface::VolumeTimerUpdate(){
405   if(volumeTimer->isActive()){ volumeTimer->stop(); } //just in case this was manually triggered
406   QtConcurrent::run(this, &OSInterface::syncVolumeInfo, this, &INFO, volumeTimer);
407 }
408 
CpuTimerUpdate()409 void OSInterface::CpuTimerUpdate(){
410   if(cpuTimer->isActive()){ cpuTimer->stop(); } //just in case this was manually triggered
411   QtConcurrent::run(this, &OSInterface::syncCpuInfo, this, &INFO, cpuTimer);
412 }
413 
MemTimerUpdate()414 void OSInterface::MemTimerUpdate(){
415   if(memTimer->isActive()){ memTimer->stop(); } //just in case this was manually triggered
416   QtConcurrent::run(this, &OSInterface::syncMemoryInfo, this, &INFO, memTimer);
417 }
418 
DiskTimerUpdate()419 void OSInterface::DiskTimerUpdate(){
420   if(diskTimer->isActive()){ diskTimer->stop(); } //just in case this was manually triggered
421   QtConcurrent::run(this, &OSInterface::syncDiskInfo, this, &INFO, diskTimer);
422 }
423 
424 // Timer Setup functions
setupBatteryMonitor(int update_ms,int delay_ms)425 void OSInterface::setupBatteryMonitor(int update_ms, int delay_ms){
426   batteryTimer = new QTimer(this);
427     batteryTimer->setSingleShot(true);
428     batteryTimer->setInterval(update_ms);
429     connect(batteryTimer, SIGNAL(timeout()), this, SLOT(BatteryTimerUpdate()) );
430   QTimer::singleShot(delay_ms, this, SLOT(BatteryTimerUpdate()) );
431 }
setupUpdateMonitor(int update_ms,int delay_ms)432 void OSInterface::setupUpdateMonitor(int update_ms, int delay_ms){
433   updateTimer = new QTimer(this);
434     updateTimer->setSingleShot(true);
435     updateTimer->setInterval(update_ms);
436     connect(updateTimer, SIGNAL(timeout()), this, SLOT(UpdateTimerUpdate()) );
437   QTimer::singleShot(delay_ms, this, SLOT(UpdateTimerUpdate()) );
438 }
setupBrightnessMonitor(int update_ms,int delay_ms)439 void OSInterface::setupBrightnessMonitor(int update_ms, int delay_ms){
440   brightnessTimer = new QTimer(this);
441     brightnessTimer->setSingleShot(true);
442     brightnessTimer->setInterval(update_ms);
443     connect(brightnessTimer, SIGNAL(timeout()), this, SLOT(BrightnessTimerUpdate()) );
444   QTimer::singleShot(delay_ms, this, SLOT(BrightnessTimerUpdate()) );
445 }
setupVolumeMonitor(int update_ms,int delay_ms)446 void OSInterface::setupVolumeMonitor(int update_ms, int delay_ms){
447   volumeTimer = new QTimer(this);
448     volumeTimer->setSingleShot(true);
449     volumeTimer->setInterval(update_ms);
450     connect(volumeTimer, SIGNAL(timeout()), this, SLOT(VolumeTimerUpdate()) );
451   QTimer::singleShot(delay_ms, this, SLOT(VolumeTimerUpdate()) );
452 }
setupCpuMonitor(int update_ms,int delay_ms)453 void OSInterface::setupCpuMonitor(int update_ms, int delay_ms){
454   cpuTimer = new QTimer(this);
455     cpuTimer->setSingleShot(true);
456     cpuTimer->setInterval(update_ms);
457     connect(cpuTimer, SIGNAL(timeout()), this, SLOT(CpuTimerUpdate()) );
458   QTimer::singleShot(delay_ms, this, SLOT(CpuTimerUpdate()) );
459 }
setupMemoryMonitor(int update_ms,int delay_ms)460 void OSInterface::setupMemoryMonitor(int update_ms, int delay_ms){
461   memTimer = new QTimer(this);
462     memTimer->setSingleShot(true);
463     memTimer->setInterval(update_ms);
464     connect(memTimer, SIGNAL(timeout()), this, SLOT(MemTimerUpdate()) );
465   QTimer::singleShot(delay_ms, this, SLOT(MemTimerUpdate()) );
466 }
setupDiskMonitor(int update_ms,int delay_ms)467 void OSInterface::setupDiskMonitor(int update_ms, int delay_ms){
468   diskTimer = new QTimer(this);
469     diskTimer->setSingleShot(true);
470     diskTimer->setInterval(update_ms);
471     connect(diskTimer, SIGNAL(timeout()), this, SLOT(DiskTimerUpdate()) );
472   QTimer::singleShot(delay_ms, this, SLOT(DiskTimerUpdate()) );
473 }
474 
475 // Timer-based monitor update routines (NOTE: these are all run in a separate thread!!)
syncBatteryInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)476 void OSInterface::syncBatteryInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
477   float charge = OS_batteryCharge();
478   bool charging = OS_batteryCharging();
479   double secs = OS_batterySecondsLeft();
480   //Check for any alert generations
481   if(charging && hash->value("battery/percent",100).toFloat() <= 99 && charge>99){ os->emit BatteryFullAlert(); }
482   else if(!charging && hash->value("battery/percent", 50).toFloat()>10 && charge<10){ os->emit BatteryEmptyAlert(); }
483 
484   hash->insert("battery/percent",charge);
485   hash->insert("battery/charging",charging);
486   //Convert the seconds to human-readable
487   QString time;
488     if(secs>3600){
489       time = QString::number( qRound(secs/360.0)/10.0 )+" h";
490     }else if(secs>60){
491       time = QString::number( qRound(secs/6.0)/10.0 )+" m";
492     }else if(secs>0){
493       time = QString::number(secs)+" s";
494     }
495   hash->insert("battery/time", time);
496   //Determine the icon which should be used for this status
497   QStringList icons;
498   if(charging){
499     if(charge>=99){ icons << "battery-100-charging" << "battery-charging-100" << "battery-full-charging" << "battery-charging-full" << "battery-charging"; }
500     else if(charge >80){ icons << "battery-charging-80"<< "battery-80-charging"<< "battery-charging-080" << "battery-080-charging" << "battery-good-charging" << "battery-charging-good"; }
501     else if(charge >60){ icons << "battery-charging-60"<< "battery-60-charging"<< "battery-charging-060" << "battery-060-charging" << "battery-good-charging" << "battery-charging-good"; }
502     else if(charge >40){ icons << "battery-charging-40"<< "battery-40-charging"<< "battery-charging-040" << "battery-040-charging" << "battery-good-charging" << "battery-charging-good"; }
503     else if(charge >20){ icons << "battery-charging-20"<< "battery-20-charging"<< "battery-charging-020" << "battery-020-charging" << "battery-low-charging" << "battery-charging-low"; }
504     else if(charge > 0){ icons << "battery-charging-00"<< "battery-00-charging"<< "battery-charging-000" << "battery-000-charging" << "battery-caution-charging" << "battery-charging-caution"; }
505   }else{
506     if(charge>=99){ icons << "battery-100" << "battery-full-charged" << "battery-full" << "battery"; }
507     else if(charge >80){ icons << "battery-80"<< "battery-080" << "battery-good"; }
508     else if(charge >60){ icons << "battery-60"<< "battery-060" << "battery-good"; }
509     else if(charge >40){ icons << "battery-40"<< "battery-040" << "battery-good"; }
510     else if(charge >20){ icons << "battery-20"<< "battery-020" << "battery-low"; }
511     else if(charge > 0){ icons << "battery-00" << "battery-000" << "battery-caution"; }
512   }
513   icons << "battery-unknown" << "battery";
514   for(int i=0; i<icons.length(); i++){
515     if(QIcon::hasThemeIcon(icons[i])){ hash->insert("battery/icon",icons[i]); break; }
516   }
517   //Now emit the change signal and restart the timer
518   os->emit batteryChanged();
519   QTimer::singleShot(0, timer, SLOT(start()));
520 }
521 
syncUpdateInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)522 void OSInterface::syncUpdateInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
523   //Get the current status
524   QString status;
525   QStringList icons;
526   if(OS_updatesRunning()){
527     status="running"; icons << "state-download" << "update-medium" << "sync";
528   }else if(OS_updatesFinished()){
529     status="finished"; icons << "state-ok" << "update-high" << "security-high";
530   }else if(OS_updatesAvailable()){
531     status="available"; icons << "state-warning" << "update-medium" << "security-medium";
532   }
533   icons << "state-offline" << "update-none";
534   //qDebug() << "Update Sync:" << status << icons;
535   //Save the current info into the hash (if different)
536   if(status != updateStatus()){
537     hash->insert("updates/status", status);
538     for(int i=0; i<icons.length(); i++){
539       if(QIcon::hasThemeIcon(icons[i])){ hash->insert("updates/icon", icons[i]); break;}
540     }
541     os->emit updateStatusChanged();
542   }
543   QTimer::singleShot(0, timer, SLOT(start()));
544 }
545 
syncBrightnessInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)546 void OSInterface::syncBrightnessInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
547 
548   QTimer::singleShot(0, timer, SLOT(start()));
549 }
550 
syncVolumeInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)551 void OSInterface::syncVolumeInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
552   int oldvol = volume();
553   int newvol = OS_volume();
554   if(oldvol!=newvol && newvol>=0){
555     hash->insert("volume/current",newvol);
556     QString icon;
557     if(newvol>66){ icon = "audio-volume-high"; }
558     else if(newvol>33){ icon = "audio-volume-medium"; }
559     else if(newvol>0){ icon = "audio-volume-low"; }
560     else{ icon = "audio-volume-muted"; }
561     hash->insert("volume/icon",icon);
562     os->emit volumeChanged();
563   }
564   QTimer::singleShot(0, timer, SLOT(start()));
565 }
566 
syncCpuInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)567 void OSInterface::syncCpuInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
568 
569   QTimer::singleShot(0, timer, SLOT(start()));
570 }
571 
syncMemoryInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)572 void OSInterface::syncMemoryInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
573 
574   QTimer::singleShot(0, timer, SLOT(start()));
575 }
576 
syncDiskInfo(OSInterface * os,QHash<QString,QVariant> * hash,QTimer * timer)577 void OSInterface::syncDiskInfo(OSInterface *os, QHash<QString, QVariant> *hash, QTimer *timer){
578 
579   QTimer::singleShot(0, timer, SLOT(start()));
580 }
581 
582 // = Battery =
batteryAvailable()583 bool OSInterface::batteryAvailable(){ return OS_batteryAvailable(); }
batteryCharge()584 float OSInterface::batteryCharge(){
585   if(INFO.contains("battery/percent")){ return INFO.value("battery/percent").toFloat(); }
586   return -1;
587 }
batteryCharging()588 bool OSInterface::batteryCharging(){
589   if(INFO.contains("battery/charging")){ return INFO.value("battery/charging").toBool(); }
590   return false;
591 }
batteryRemaining()592 QString OSInterface::batteryRemaining(){
593   if(INFO.contains("battery/time")){ return INFO.value("battery/time").toString(); }
594   return "";
595 }
batteryIcon()596 QString OSInterface::batteryIcon(){
597   if(INFO.contains("battery/icon")){ return INFO.value("battery/icon").toString(); }
598   return "";
599 }
600 
batteryStatus()601 QString OSInterface::batteryStatus(){
602   QString text = QString::number(batteryCharge())+"%";
603   if(!batteryCharging()){
604     QString time = batteryRemaining();
605     if(!time.isEmpty()){
606       text.append(" ("+time+")");
607     }
608   }
609   return text;
610 }
611 
612 // = Volume =
volumeSupported()613 bool OSInterface::volumeSupported(){ return OS_volumeSupported(); }
volume()614 int OSInterface::volume(){
615   if(INFO.contains("volume/current")){ return INFO.value("volume/current").toInt(); }
616   return 0;
617 }
618 
setVolume(int vol)619 void OSInterface::setVolume(int vol){
620   OS_setVolume(vol);
621   VolumeTimerUpdate(); //update the internal cache
622 }
623 
volumeIcon()624 QString OSInterface::volumeIcon(){
625   if(INFO.contains("volume/icon")){ return INFO.value("volume/icon").toString(); }
626   return "";
627 }
628 
629 // = Media =
mediaDirectories()630 QStringList OSInterface::mediaDirectories(){ return OS_mediaDirectories(); }
mediaShortcuts()631 QStringList OSInterface::mediaShortcuts(){ return autoHandledMediaFiles(); } //List of currently-available XDG shortcut file paths
632 
633 // = Updates =
updatesSupported()634 bool OSInterface::updatesSupported(){ return OS_updatesSupported(); }
updateStatus()635 QString OSInterface::updateStatus(){
636   if(INFO.contains("updates/status")){ return INFO.value("updates/status").toString(); }
637   return "";
638 }
updateInfoAvailable()639 bool OSInterface::updateInfoAvailable(){
640   return !updateStatus().isEmpty();
641 }
642 
updateIcon()643 QString OSInterface::updateIcon(){
644   if(INFO.contains("updates/icon")){ return INFO.value("updates/icon").toString(); }
645   return "";
646 }
647 
updateStatusInfo()648 QString OSInterface::updateStatusInfo(){
649   QString status = updateStatus();
650   if(status=="available"){ return updateDetails(); }
651   else if(status=="running"){ return updateLog(); }
652   else if(status=="finished"){ return updateResults(); }
653   return "";
654 }
655 
updateDetails()656 QString OSInterface::updateDetails(){
657   return OS_updateDetails(); //don't cache these types of logs - too large
658 }
659 
updateLog()660 QString OSInterface::updateLog(){
661   return OS_updateLog(); //don't cache these types of logs - too large and change too often
662 }
663 
updateResults()664 QString OSInterface::updateResults(){
665   return OS_updateResults(); //don't cache these types of logs - too large
666 }
667 
startUpdates()668 void OSInterface::startUpdates(){ OS_startUpdates(); }
updateOnlyOnReboot()669 bool OSInterface::updateOnlyOnReboot(){ return OS_updateOnlyOnReboot(); }
updateCausesReboot()670 bool OSInterface::updateCausesReboot(){ return OS_updateCausesReboot(); }
671 
lastUpdate()672 QDateTime OSInterface::lastUpdate(){ return OS_lastUpdate(); }
lastUpdateResults()673 QString OSInterface::lastUpdateResults(){ return OS_lastUpdateResults(); }
674 
675 // = System Power =
canReboot()676 bool OSInterface::canReboot(){ return OS_canReboot(); }
startReboot()677 void OSInterface::startReboot(){ OS_startReboot(); }
canShutdown()678 bool OSInterface::canShutdown(){ return OS_canShutdown(); }
startShutdown()679 void OSInterface::startShutdown(){ OS_startShutdown(); }
canSuspend()680 bool OSInterface::canSuspend(){ return OS_canSuspend(); }
startSuspend()681 void OSInterface::startSuspend(){ OS_startSuspend(); }
682 
683 // = Screen Brightness =
brightnessSupported()684 bool OSInterface::brightnessSupported(){ return OS_brightnessSupported(); }
brightness()685 int OSInterface::brightness(){
686   if(INFO.contains("brightness/percent")){ return INFO.value("brightness/percent").toInt(); }
687   return 100;
688 }
setBrightness(int percent)689 void OSInterface::setBrightness(int percent){
690   OS_setBrightness(percent);
691   BrightnessTimerUpdate(); //update internal cache ASAP
692 }
693 
694 // = System Status Monitoring
cpuSupported()695 bool OSInterface::cpuSupported(){ return OS_cpuSupported(); }
cpuPercentage()696 QList<int> OSInterface::cpuPercentage(){ return QList<int>(); } // (one per CPU) percentage: 0-100 with empty list for errors
cpuTemperatures()697 QStringList OSInterface::cpuTemperatures(){ return QStringList(); } // (one per CPU) Temperature of CPU ("50C" for example)
698 
memorySupported()699 bool OSInterface::memorySupported(){ return false; }
memoryUsedPercentage()700 int OSInterface::memoryUsedPercentage(){ return -1; } //percentage: 0-100 with -1 for errors
memoryTotal()701 QString OSInterface::memoryTotal(){ return QString(); } //human-readable form - does not tend to change within a session
diskIO()702 QStringList OSInterface::diskIO(){ return QStringList(); } //Returns list of current read/write stats for each device
703 
diskSupported()704 bool OSInterface::diskSupported(){ return false; }
fileSystemPercentage(QString dir)705 int OSInterface::fileSystemPercentage(QString dir){ return -1; } //percentage of capacity used: 0-100 with -1 for errors
fileSystemCapacity(QString dir)706 QString OSInterface::fileSystemCapacity(QString dir){ return QString(); } //human-readable form - total capacity
707