1 //===========================================
2 //  Lumina-DE source code
3 //  Copyright (c) 2014-2015, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #ifdef __FreeBSD__
8 #include "LuminaOS.h"
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <sys/sysctl.h>
12 
13 #include <QDebug>
14 //can't read xbrightness settings - assume invalid until set
15 static int screenbrightness = -1;
16 static int audiovolume = -1;
17 
OSName()18 QString LOS::OSName(){ return "FreeBSD"; }
19 
20 //OS-specific prefix(s)
21 // NOTE: PREFIX, L_ETCDIR, L_SHAREDIR are defined in the OS-detect.pri project file and passed in
LuminaShare()22 QString LOS::LuminaShare(){ return (L_SHAREDIR+"/lumina-desktop/"); } //Install dir for Lumina share files
AppPrefix()23 QString LOS::AppPrefix(){ return "/usr/local/"; } //Prefix for applications
SysPrefix()24 QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system
25 
26 //OS-specific application shortcuts (*.desktop files)
ControlPanelShortcut()27 QString LOS::ControlPanelShortcut(){ return "/usr/local/share/applications/pccontrol.desktop"; } //system control panel
AppStoreShortcut()28 QString LOS::AppStoreShortcut(){ return "/usr/local/share/applications/appcafe.desktop"; } //graphical app/pkg manager
29 
30 //OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; )
RSSFeeds()31 QStringList LOS::RSSFeeds(){
32   QStringList feeds;
33     feeds << "FreeBSD News Feed::::https://www.freebsd.org/news/rss.xml";
34     feeds << "TrueOS News Feed::::https://www.trueos.org/feed/";
35     feeds << "Project Trident News Feed::::http://project-trident.org/index.xml";
36   return feeds;
37  }
38 
39 // ==== ExternalDevicePaths() ====
ExternalDevicePaths()40 QStringList LOS::ExternalDevicePaths(){
41     //Returns: QStringList[<type>::::<filesystem>::::<path>]
42       //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN]
43   QStringList devs = LUtils::getCmdOutput("mount");
44   //Now check the output
45   for(int i=0; i<devs.length(); i++){
46     if(devs[i].startsWith("/dev/")){
47       devs[i].replace("\t"," ");
48       QString type = devs[i].section(" on ",0,0);
49 	type.remove("/dev/");
50       //Determine the type of hardware device based on the dev node
51       if(type.startsWith("da")){ type = "USB"; }
52       else if(type.startsWith("ada")){ type = "HDRIVE"; }
53       else if(type.startsWith("mmsd")){ type = "SDCARD"; }
54       else if(type.startsWith("cd")||type.startsWith("acd")){ type="DVD"; }
55       else{ type = "UNKNOWN"; }
56       //Now put the device in the proper output format
57       devs[i] = type+"::::"+devs[i].section("(",1,1).section(",",0,0)+"::::"+devs[i].section(" on ",1,50).section("(",0,0).simplified();
58     }else{
59       //invalid device - remove it from the list
60       devs.removeAt(i);
61       i--;
62     }
63   }
64   //Also add info about anything in the "/media" directory
65   QDir media("/media");
66   QFileInfoList list = media.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Type | QDir::Name);
67   //qDebug() << "Media files found:" << list.length();
68   for(int i=0; i<list.length(); i++){
69     //qDebug() << "Found media entry:" << list[i].fileName();
70     if(list[i].isDir()){
71       devs << "UNKNOWN::::::::/media/"+list[i].fileName();
72     }else if(list[i].fileName().endsWith(".desktop")){
73       QString type = list[i].fileName().section(".desktop",0,-2);
74       //Determine the type of hardware device based on the dev node
75       if(type.startsWith("da")){ type = "USB"; }
76       else if(type.startsWith("ada")){ type = "HDRIVE"; }
77       else if(type.startsWith("mmsd")){ type = "SDCARD"; }
78       else if(type.startsWith("cd")||type.startsWith("acd")){ type="DVD"; }
79       else{ type = "UNKNOWN"; }
80       devs << type+"::::::::/media/"+list[i].fileName();
81     }
82   }
83   return devs;
84 }
85 
86 //Read screen brightness information
ScreenBrightness()87 int LOS::ScreenBrightness(){
88   //First run a quick check to ensure this is not a VirtualBox VM (no brightness control)
89   static int goodsys = -1; //This will not change over time - only check/set once
90   if(goodsys<0){
91       //Make sure we are not running in VirtualBox (does not work in a VM)
92       QStringList info = LUtils::getCmdOutput("pciconf -lv");
93       if( info.filter("VirtualBox", Qt::CaseInsensitive).isEmpty() ){ goodsys = 1; }
94       else{ goodsys = 0; } //not a good system
95   }
96   if(goodsys<=0){ return -1; } //not a good system
97 
98   //Returns: Screen Brightness as a percentage (0-100, with -1 for errors)
99   if( !LUtils::isValidBinary("xbrightness") ){ return -1; } //incomplete install
100   //Now perform the standard brightness checks
101   if(screenbrightness==-1){ //memory value
102     if(QFile::exists(QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/.currentxbrightness")){ //saved file value
103       int val = LUtils::readFile(QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/.currentxbrightness").join("").simplified().toInt();
104       screenbrightness = val;
105     }
106   }
107   //If it gets to this point, then we have a valid (but new) installation
108   if(screenbrightness<0){ screenbrightness = 100; } //default value for systems
109   return screenbrightness;
110 }
111 
112 //Set screen brightness
setScreenBrightness(int percent)113 void LOS::setScreenBrightness(int percent){
114   if(percent == -1){ return; } //This is usually an invalid value passed directly to the setter
115   //ensure bounds
116   if(percent<0){percent=0;}
117   else if(percent>100){ percent=100; }
118   //Run the command(s)
119   bool success = false;
120   // - try hardware setting first (TrueOS || or intel_backlight)
121   bool remoteSession = !QString(getenv("PICO_CLIENT_LOGIN")).isEmpty();
122   /*if( LUtils::isValidBinary("pc-sysconfig") && !remoteSession){
123     //Use TrueOS tool (direct sysctl control)
124     QString ret = LUtils::getCmdOutput("pc-sysconfig", QStringList() <<"setscreenbrightness "+QString::number(percent)).join("");
125     success = ret.toLower().contains("success");
126     qDebug() << "Set hardware brightness:" << percent << success;
127   }*/
128   if( !success && LUtils::isValidBinary("intel_backlight") && !remoteSession){
129     //qDebug() << "Run intel_backlight";
130     //Use the intel_backlight utility (only for Intel mobo/hardware?)
131     // Note: this returns an integer value corresponding to the percent brightess the screen was just set to
132     int ret = LUtils::runCmd("intel_backlight", QStringList() << QString::number(percent));
133     //qDebug() << " - Return value:" << ret;
134     success = (percent ==  ret);
135   }
136   // - if hardware brightness does not work, use software brightness
137   if(!success && LUtils::isValidBinary("xbrightness") ){
138     //qDebug() << "Run xbrightness";
139     QString cmd = "xbrightness  %1";
140     float pf = percent/100.0; //convert to a decimel
141     cmd = cmd.arg( QString::number( int(65535*pf) ) );
142     LUtils::runCmd(cmd);
143     //qDebug() << " - Return value:" << ret;
144     success = true;
145   }
146   //Save the result for later
147   //qDebug() << "Got Brightness:" << percent << success;
148   if(!success){ screenbrightness = -1; }
149   else{ screenbrightness = percent; }
150   //qDebug() << " - Write to file:" << screenbrightness;
151   LUtils::writeFile(QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/.currentxbrightness", QStringList() << QString::number(screenbrightness), true);
152 }
153 
154 //Read the current volume
audioVolume()155 int LOS::audioVolume(){ //Returns: audio volume as a percentage (0-100, with -1 for errors)
156   int out = audiovolume;
157   if(out < 0){
158     //First time session check: Load the last setting for this user
159     QString info = LUtils::readFile(QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/.currentvolume").join("");
160     if(!info.isEmpty()){
161       out = info.simplified().toInt();
162       audiovolume = out; //reset this internal flag
163       return out;
164     }
165   }
166   bool remoteSession = !QString(getenv("PICO_CLIENT_LOGIN")).isEmpty();
167   if(remoteSession){
168      QStringList info = LUtils::getCmdOutput("pactl list short sinks");
169      qDebug() << "Got PA sinks:" << info;
170      out = 50; //TEMPORARY - still need to write up the info parsing
171      audiovolume = out;
172   }else{
173     //probe the system for the current volume (other utils could be changing it)
174       QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines
175       if(!info.isEmpty()){
176         int L = info.section(":",1,1).toInt();
177         int R = info.section(":",2,2).toInt();
178         if(L>R){ out = L; }
179         else{ out = R; }
180 	if(out != audiovolume){
181 	  //Volume changed by other utility: adjust the saved value as well
182 	  LUtils::writeFile(QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/.currentvolume", QStringList() << QString::number(out), true);
183 	}
184 	audiovolume = out;
185       }
186   }
187   return out;
188 }
189 
190 //Set the current volume
setAudioVolume(int percent)191 void LOS::setAudioVolume(int percent){
192   if(percent<0){percent=0;}
193   else if(percent>100){percent=100;}
194   bool remoteSession = !QString(getenv("PICO_CLIENT_LOGIN")).isEmpty();
195   if(remoteSession){
196     LUtils::runCmd(QString("pactl set-sink-volume @DEFAULT_SINK@ ")+QString::number(percent)+"%");
197   }else{
198     QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines
199     if(!info.isEmpty()){
200       int L = info.section(":",1,1).toInt();
201       int R = info.section(":",2,2).toInt();
202       int diff = L-R;
203       if((percent == L) && (L==R)){ return; } //already set to that volume
204       if(diff<0){ R=percent; L=percent+diff; } //R Greater
205       else{ L=percent; R=percent-diff; } //L Greater or equal
206       //Check bounds
207       if(L<0){L=0;}else if(L>100){L=100;}
208       if(R<0){R=0;}else if(R>100){R=100;}
209       //Run Command
210       LUtils::runCmd("mixer vol "+QString::number(L)+":"+QString::number(R));
211     }
212   }
213   audiovolume = percent; //save for checking later
214   LUtils::writeFile(QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/.currentvolume", QStringList() << QString::number(percent), true);
215 }
216 
217 //Change the current volume a set amount (+ or -)
changeAudioVolume(int percentdiff)218 void LOS::changeAudioVolume(int percentdiff){
219   bool remoteSession = !QString(getenv("PICO_CLIENT_LOGIN")).isEmpty();
220   if(remoteSession){
221     LUtils::runCmd(QString("pactl set-sink-volume @DEFAULT_SINK@ ")+((percentdiff>0)?"+" : "") + QString::number(percentdiff)+"%");
222   }else{
223     QString info = LUtils::getCmdOutput("mixer -S vol").join(":").simplified(); //ignores any other lines
224     if(!info.isEmpty()){
225       int L = info.section(":",1,1).toInt() + percentdiff;
226       int R = info.section(":",2,2).toInt() + percentdiff;
227       //Check bounds
228       if(L<0){L=0;}else if(L>100){L=100;}
229       if(R<0){R=0;}else if(R>100){R=100;}
230       //Run Command
231       LUtils::runCmd("mixer vol "+QString::number(L)+":"+QString::number(R));
232     }
233   }
234 }
235 
236 //Check if a graphical audio mixer is installed
hasMixerUtility()237 bool LOS::hasMixerUtility(){
238   return QFile::exists("/usr/local/bin/pc-mixer");
239 }
240 
241 //Launch the graphical audio mixer utility
startMixerUtility()242 void LOS::startMixerUtility(){
243   QProcess::startDetached("pc-mixer -notray");
244 }
245 
246 //Check for user system permission (shutdown/restart)
userHasShutdownAccess()247 bool LOS::userHasShutdownAccess(){
248   //User needs to be a part of the operator group to be able to run the shutdown command
249   QStringList groups = LUtils::getCmdOutput("id -Gn").join(" ").split(" ");
250   return groups.contains("operator");
251 }
252 
systemPerformingUpdates()253 bool LOS::systemPerformingUpdates(){
254   return (QProcess::execute("pgrep -F /tmp/.updateInProgress")==0); //this is 0 if updating right now
255 }
256 
257 //Return the details of any updates which are waiting to apply on shutdown
systemPendingUpdates()258 QString LOS::systemPendingUpdates(){
259   if(QFile::exists("/tmp/.trueos-update-staged")){ return LUtils::readFile("/tmp/.trueos-update-staged").join("\n"); }
260   else{ return ""; }
261 }
262 
263 //System Shutdown
systemShutdown(bool skipupdates)264 void LOS::systemShutdown(bool skipupdates){ //start poweroff sequence
265   if(skipupdates){QProcess::startDetached("shutdown -po now"); }
266   else{ QProcess::startDetached("shutdown -p now"); }
267 }
268 
269 //System Restart
systemRestart(bool skipupdates)270 void LOS::systemRestart(bool skipupdates){ //start reboot sequence
271   if(skipupdates || !LUtils::isValidBinary("trueos-update") ){
272     QProcess::startDetached("shutdown -ro now");
273   }else if(LUtils::isValidBinary("sudo")){{
274     QProcess::startDetached("sudo -n trueos-update upgrade"); }
275   }
276 }
277 
278 //Check for suspend support
systemCanSuspend()279 bool LOS::systemCanSuspend(){
280   QString state = LUtils::getCmdOutput("sysctl -n hw.acpi.suspend_state").join("").simplified();
281   bool ok = LUtils::getCmdOutput("sysctl -n hw.acpi.supported_sleep_state").join("").split(" ",QString::SkipEmptyParts).contains(state);
282   return ok;
283 }
284 
285 //Put the system into the suspend state
systemSuspend()286 void LOS::systemSuspend(){
287   QString state = LUtils::getCmdOutput("sysctl -n hw.acpi.suspend_state").join("").simplified();
288   QProcess::startDetached("acpiconf", QStringList() << "-s" << state );
289 }
290 
291 //Battery Availability
hasBattery()292 bool LOS::hasBattery(){
293   static int hasbat = -1;
294   if(hasbat < 0 ){
295     int val = batteryCharge();
296     if(val >= 0 && val <= 100){ hasbat = 1; }
297     else{ hasbat = 0; }
298   }
299   return (hasbat==1);
300 }
301 
302 //Battery Charge Level
batteryCharge()303 int LOS::batteryCharge(){ //Returns: percent charge (0-100), anything outside that range is counted as an error
304   int charge = LUtils::getCmdOutput("apm -l").join("").toInt();
305   if(charge > 100){ charge = -1; } //invalid charge
306   return charge;
307 }
308 
309 //Battery Charging State
batteryIsCharging()310 bool LOS::batteryIsCharging(){
311   return (LUtils::getCmdOutput("apm -a").join("").simplified() == "1");
312 }
313 
314 //Battery Time Remaining
batterySecondsLeft()315 int LOS::batterySecondsLeft(){ //Returns: estimated number of seconds remaining
316   return LUtils::getCmdOutput("apm -t").join("").toInt();
317 }
318 
319 //File Checksums
Checksums(QStringList filepaths)320 QStringList LOS::Checksums(QStringList filepaths){ //Return: checksum of the input file
321   QStringList info = LUtils::getCmdOutput("md5 \""+filepaths.join("\" \"")+"\"");
322   for(int i=0; i<info.length(); i++){
323     if( !info[i].contains(" = ") ){ info.removeAt(i); i--; }
324     else{
325       //Strip out the extra information
326       info[i] = info[i].section(" = ",1,1);
327     }
328   }
329  return info;
330 }
331 
332 //file system capacity
FileSystemCapacity(QString dir)333 QString LOS::FileSystemCapacity(QString dir) { //Return: percentage capacity as give by the df command
334   QStringList mountInfo = LUtils::getCmdOutput("df \"" + dir+"\"");
335   QString::SectionFlag skipEmpty = QString::SectionSkipEmpty;
336   //we take the 5th word on the 2 line
337   QString capacity = mountInfo[1].section(" ",4,4, skipEmpty);
338   return capacity;
339 }
340 
CPUTemperatures()341 QStringList LOS::CPUTemperatures(){ //Returns: List containing the temperature of any CPU's ("50C" for example)
342   static QStringList vars = QStringList();
343   QStringList temps;
344   if(vars.isEmpty()){
345     temps = LUtils::getCmdOutput("sysctl -i dev.cpu").filter(".temperature:");  //try direct readings first
346     if(temps.isEmpty()){ temps = LUtils::getCmdOutput("sysctl -i hw.acpi").filter(".temperature:"); } // then try acpi values
347   }else{ temps = LUtils::getCmdOutput("sysctl "+vars.join(" ")); vars.clear(); }
348 
349     temps.sort();
350     for(int i=0; i<temps.length(); i++){
351       if(temps[i].contains(".acpi.") || temps[i].contains(".cpu")){
352         vars << temps[i].section(":",0,0); //save this variable for later checks
353         temps[i] = temps[i].section(":",1,5).simplified(); //only pull out the value, not the variable
354       }else{
355         //non CPU temperature - skip it
356         temps.removeAt(i); i--;
357       }
358     }
359   /*}else{
360     //Already have the known variables - use the library call directly (much faster)
361     for(int i=0; i<vars.length(); i++){
362        float result[1000];
363        size_t len = sizeof(result);
364        if(0 != sysctlbyname(vars[i].toLocal8Bit(), result, &len, NULL, 0)){ continue; } //error returned
365        //result[len] = '\0'; //make sure to null-terminate the output
366        QString res;
367 	for(int r=0; r<((int) len); r++){ res.append(QString::number(result[r])); }
368        temps << res;
369        qDebug() << "Temp Result:" << vars[i] << res << result << len;
370     }
371   }*/
372   return temps;
373 }
374 
CPUUsagePercent()375 int LOS::CPUUsagePercent(){ //Returns: Overall percentage of the amount of CPU cycles in use (-1 for errors)
376     //Calculate the percentage based on the kernel information directly - no extra utilities
377     QStringList result = LUtils::getCmdOutput("sysctl -n kern.cp_times").join("").split(" ");
378     static QStringList last = QStringList();
379     if(last.isEmpty()){ last = result; return 0; } //need two ticks before it works properly
380 
381     double tot = 0;
382     int cpnum = 0;
383     for(int i=4; i<result.length(); i+=5){
384       //The values come in blocks of 5 per CPU: [user,nice,system,interrupt,idle]
385       cpnum++; //the number of CPU's accounted for (to average out at the end)
386       //qDebug() <<"CPU:" << cpnum;
387       long sum = 0;
388       //Adjust/all the data to correspond to diffs from the previous check
389       for(int j=0; j<5; j++){
390         QString tmp = result[i-j];
391 	result[i-j] = QString::number(result[i-j].toLong()-last[i-j].toLong()); //need the difference between last run and this one
392 	sum += result[i-j].toLong();
393 	last[i-j] = tmp; //make sure to keep the original value around for the next run
394       }
395       //Calculate the percentage used for this CPU (100% - IDLE%)
396       tot += 100.0L - ( (100.0L*result[i].toLong())/sum ); //remember IDLE is the last of the five values per CPU
397     }
398   return qRound(tot/cpnum);
399 
400 }
401 
MemoryUsagePercent()402 int LOS::MemoryUsagePercent(){
403   //SYSCTL: vm.stats.vm.v_<something>_count
404   QStringList info = LUtils::getCmdOutput("sysctl -n vm.stats.vm.v_page_count vm.stats.vm.v_wire_count vm.stats.vm.v_active_count");
405   if(info.length()<3){ return -1; } //error in fetching information
406   //List output: [total, wired, active]
407   double perc = 100.0* (info[1].toLong()+info[2].toLong())/(info[0].toDouble());
408   return qRound(perc);
409 }
410 
DiskUsage()411 QStringList LOS::DiskUsage(){ //Returns: List of current read/write stats for each device
412   QStringList info = LUtils::getCmdOutput("iostat -dx -c 2 -w 0.1 -t IDE -t SCSI -t da");
413   //Note: This returns the statistics *twice*: the first set is average for entire system
414   //    - the second set is the actual average stats over that 0.1 second
415   if(info.length()<6){ return QStringList(); } //nothing from command
416   QStringList labs = info[1].split(" ",QString::SkipEmptyParts);
417   QStringList out;
418   QString fmt = "%1: %2 %3";
419   for(int i=(info.length()/2)+2; i<info.length(); i++){ //skip the first data entry , just labels
420     info[i].replace("\t"," ");
421     if(i==1){ labs = info[i].split(" ", QString::SkipEmptyParts); }//the labels for each column
422     else{
423       QStringList data = info[i].split(" ",QString::SkipEmptyParts); //data[0] is always the device
424       //qDebug() << "Data Line:" << data;
425       if(data.length()>2 && labs.length()>2){
426         out << fmt.arg(data[0], data[1]+" "+labs[1], data[2]+" "+labs[2]);
427       }
428     }
429   }
430 
431   return out;
432 }
433 
434 #endif
435