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