1 //===========================================
2 //  Lumina-DE source code
3 //  Copyright (c) 2012-2015, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #include "LSession.h"
8 #include <LuminaOS.h>
9 
10 #include <QTime>
11 #include <QScreen>
12 #include <QtConcurrent>
13 #include <QMimeData>
14 #include "LXcbEventFilter.h"
15 #include "BootSplash.h"
16 
17 //LibLumina X11 class
18 #include <LuminaX11.h>
19 #include <LUtils.h>
20 #include <ExternalProcess.h>
21 #include <LIconCache.h>
22 
23 #include <unistd.h> //for usleep() usage
24 
25 #ifndef DEBUG
26 #define DEBUG 0
27 #endif
28 
29 XCBEventFilter *evFilter = 0;
30 LIconCache *ICONS = 0;
31 
LSession(int & argc,char ** argv)32 LSession::LSession(int &argc, char ** argv) : LSingleApplication(argc, argv, "lumina-desktop"){
33  xchange = false;
34  if(this->isPrimaryProcess()){
35   connect(this, SIGNAL(InputsAvailable(QStringList)), this, SLOT(NewCommunication(QStringList)) );
36   this->setApplicationName("Lumina Desktop Environment");
37   this->setApplicationVersion( LDesktopUtils::LuminaDesktopVersion() );
38   this->setOrganizationName("LuminaDesktopEnvironment");
39   this->setQuitOnLastWindowClosed(false); //since the LDesktop's are not necessarily "window"s
40   //Enabled a few of the simple effects by default
41   this->setEffectEnabled( Qt::UI_AnimateMenu, true);
42   this->setEffectEnabled( Qt::UI_AnimateCombo, true);
43   this->setEffectEnabled( Qt::UI_AnimateTooltip, true);
44   //this->setAttribute(Qt::AA_UseDesktopOpenGL);
45   this->setAttribute(Qt::AA_UseHighDpiPixmaps); //allow pixmaps to be scaled up as well as down
46   //this->setStyle( new MenuProxyStyle); //QMenu icon size override
47   SystemTrayID = 0; VisualTrayID = 0;
48   sysWindow = 0;
49   TrayDmgEvent = 0;
50   TrayDmgError = 0;
51   lastActiveWin = 0;
52   cleansession = true;
53   TrayStopping = false;
54   xchange = false;
55   ICONS = new LIconCache(this);
56   screenTimer = new QTimer(this);
57     screenTimer->setSingleShot(true);
58     screenTimer->setInterval(50);
59     connect(screenTimer, SIGNAL(timeout()), this, SLOT(updateDesktops()) );
60   for(int i=1; i<argc; i++){
61     if( QString::fromLocal8Bit(argv[i]) == "--noclean" ){ cleansession = false; break; }
62   }
63   XCB = new LXCB(); //need access to XCB data/functions right away
64   //initialize the empty internal pointers to 0
65   appmenu = 0;
66   settingsmenu = 0;
67   currTranslator=0;
68   mediaObj=0;
69   sessionsettings=0;
70   //Setup the event filter for Qt5
71   evFilter =  new XCBEventFilter(this);
72   this->installNativeEventFilter( evFilter );
73   connect(this, SIGNAL(screenAdded(QScreen*)), this, SLOT(screensChanged()) );
74   connect(this, SIGNAL(screenRemoved(QScreen*)), this, SLOT(screensChanged()) );
75   connect(this, SIGNAL(primaryScreenChanged(QScreen*)), this, SLOT(screensChanged()) );
76 
77   // Clipboard
78   ignoreClipboard = false;
79   qRegisterMetaType<QClipboard::Mode>("QClipboard::Mode");
80   connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), this, SLOT(handleClipboard(QClipboard::Mode)));
81  } //end check for primary process
82 }
83 
~LSession()84 LSession::~LSession(){
85  if(this->isPrimaryProcess()){
86   //WM->stopWM();
87   for(int i=0; i<DESKTOPS.length(); i++){
88     DESKTOPS[i]->deleteLater();
89   }
90   //delete WM;
91   settingsmenu->deleteLater();
92   appmenu->deleteLater();
93   delete currTranslator;
94   if(mediaObj!=0){delete mediaObj;}
95  }
96 }
97 
98 //Static function so everything can get the same icon name
batteryIconName(int charge,bool charging)99 QString LSession::batteryIconName(int charge, bool charging){
100   int icon = -1;
101   if (charge > 90) { icon = 4; }
102   else if (charge > 70) { icon = 3; }
103   else if (charge > 20) { icon = 2; }
104   else if (charge > 5) { icon = 1; }
105   else if (charge > 0 ) { icon = 0; }
106   if(charging){ icon = icon+10; }
107   QStringList iconList;
108     switch (icon) {
109       case 0:
110         iconList << "battery-20" << "battery-020" << "battery-empty" << "battery-caution";
111         break;
112       case 1:
113         iconList << "battery-40" << "battery-040" << "battery-low" << "battery-caution";
114         break;
115       case 2:
116         iconList << "battery-60" << "battery-060" << "battery-good";
117         break;
118       case 3:
119         iconList << "battery-80" << "battery-080" << "battery-good";
120         break;
121       case 4:
122         iconList << "battery-100" << "battery-full";
123         break;
124       case 10:
125         iconList << "battery-20-charging" << "battery-020-charging" << "battery-empty-charging" << "battery-caution-charging"
126 		<< "battery-charging-20" << "battery-charging-020" << "battery-charging-empty" << "battery-charging-caution";
127         break;
128       case 11:
129         iconList << "battery-40-charging" << "battery-040-charging" << "battery-low-charging" << "battery-caution-charging"
130 		<< "battery-charging-40" << "battery-charging-040" << "battery-charging-low" << "battery-charging-caution";
131         break;
132       case 12:
133         iconList << "battery-60-charging" << "battery-060-charging" << "battery-good-charging"
134 		<< "battery-charging-60" << "battery-charging-060" << "battery-charging-good";
135         break;
136       case 13:
137         iconList << "battery-80-charging" << "battery-080-charging" << "battery-good-charging"
138 		<< "battery-charging-80" << "battery-charging-080" << "battery-charging-good";
139         break;
140       case 14:
141         if(charge==100){ iconList << "battery-full-charged"; }
142         iconList << "battery-100-charging" << "battery-full-charging"
143 		<< "battery-charging-100" << "battery-charging-full";
144         break;
145       default:
146         iconList << "battery-unknown" << "battery-missing";
147         break;
148     }
149     iconList << "battery"; //generic battery icon
150     for(int i=0; i<iconList.length(); i++){
151       if( QIcon::hasThemeIcon(iconList[i]) ){ return iconList[i]; }
152     }
153     return ""; //no icon found
154 }
155 
setupSession()156 void LSession::setupSession(){
157   //Seed random number generator (if needed)
158   qsrand( QTime::currentTime().msec() );
159 
160   currTranslator = LUtils::LoadTranslation(this, "lumina-desktop");
161   BootSplash splash;
162     splash.showScreen("init");
163   qDebug() << "Initializing Session";
164   if(QFile::exists("/tmp/.luminastopping")){ QFile::remove("/tmp/.luminastopping"); }
165   QTime* timer = 0;
166   if(DEBUG){ timer = new QTime(); timer->start(); qDebug() << " - Init srand:" << timer->elapsed();}
167 
168   //Setup the QSettings default paths
169     splash.showScreen("settings");
170   if(DEBUG){ qDebug() << " - Init QSettings:" << timer->elapsed();}
171   sessionsettings = new QSettings("lumina-desktop", "sessionsettings");
172   DPlugSettings = new QSettings("lumina-desktop","pluginsettings/desktopsettings");
173   //Load the proper translation files
174   if(sessionsettings->value("ForceInitialLocale",false).toBool()){
175     //Some system locale override it in place - change the env first
176     LUtils::setLocaleEnv( sessionsettings->value("InitLocale/LANG","").toString(), \
177 				sessionsettings->value("InitLocale/LC_MESSAGES","").toString(), \
178 				sessionsettings->value("InitLocale/LC_TIME","").toString(), \
179 				sessionsettings->value("InitLocale/LC_NUMERIC","").toString(), \
180 				sessionsettings->value("InitLocale/LC_MONETARY","").toString(), \
181 				sessionsettings->value("InitLocale/LC_COLLATE","").toString(), \
182 				sessionsettings->value("InitLocale/LC_CTYPE","").toString() );
183   }
184 //use the system settings
185   //Setup the user's lumina settings directory as necessary
186     //splash.showScreen("user");
187   //if(DEBUG){ qDebug() << " - Init User Files:" << timer->elapsed();}
188   //checkUserFiles(); //adds these files to the watcher as well
189 
190   //Initialize the internal variables
191   DESKTOPS.clear();
192 
193   //Start the background system tray
194     splash.showScreen("systray");
195   if(DEBUG){ qDebug() << " - Init System Tray:" << timer->elapsed();}
196   startSystemTray();
197 
198   //Initialize the global menus
199   qDebug() << " - Initialize system menus";
200     splash.showScreen("apps");
201   if(DEBUG){ qDebug() << " - Init AppMenu:" << timer->elapsed();}
202   appmenu = new AppMenu();
203 
204     splash.showScreen("menus");
205   if(DEBUG){ qDebug() << " - Init SettingsMenu:" << timer->elapsed();}
206   settingsmenu = new SettingsMenu();
207   if(DEBUG){ qDebug() << " - Init SystemWindow:" << timer->elapsed();}
208   sysWindow = new SystemWindow();
209 
210   //Initialize the desktops
211     splash.showScreen("desktop");
212   if(DEBUG){ qDebug() << " - Init Desktops:" << timer->elapsed();}
213   desktopFiles = QDir(LUtils::standardDirectory(LUtils::Desktop)).entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs, QDir::Name | QDir::IgnoreCase | QDir::DirsFirst);
214   updateDesktops();
215   //if(DEBUG){ qDebug() << " - Process Events (6x):" << timer->elapsed();}
216   //for(int i=0; i<6; i++){ LSession::processEvents(); } //Run through this a few times so the interface systems get up and running
217 
218   //Now setup the system watcher for changes
219     splash.showScreen("final");
220   //qDebug() << " - Initialize file system watcher";
221   if(DEBUG){ qDebug() << " - Init QFileSystemWatcher:" << timer->elapsed();}
222   watcher = new QFileSystemWatcher(this);
223     QString confdir = sessionsettings->fileName().section("/",0,-2);
224     watcherChange(sessionsettings->fileName() );
225     watcherChange( confdir+"/desktopsettings.conf" );
226     watcherChange( confdir+"/fluxbox-init" );
227     watcherChange( confdir+"/fluxbox-keys" );
228     watcherChange( confdir+"/favorites.list" );
229     //Try to watch the localized desktop folder too
230     watcherChange( LUtils::standardDirectory(LUtils::Desktop) );
231     //And watch the /media directory, and /run/media/USERNAME directory
232    if(QFile::exists("/media")){  watcherChange("/media"); }
233    QString userMedia = QString("/run/media/%1").arg(QDir::homePath().split("/").takeLast());
234    if (QFile::exists(userMedia)) { watcherChange(userMedia); }
235    if(!QFile::exists("/tmp/.autofs_change")){ system("touch /tmp/.autofs_change"); }
236    watcherChange("/tmp/.autofs_change");
237   //connect internal signals/slots
238   connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(watcherChange(QString)) );
239   connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(watcherChange(QString)) );
240   connect(this, SIGNAL(aboutToQuit()), this, SLOT(SessionEnding()) );
241   //if(DEBUG){ qDebug() << " - Process Events (4x):" << timer->elapsed();}
242   //for(int i=0; i<4; i++){ LSession::processEvents(); } //Again, just a few event loops here so thing can settle before we close the splash screen
243   if(DEBUG){ qDebug() << " - Launch Startup Apps:" << timer->elapsed();}
244   //launchStartupApps();
245   QTimer::singleShot(500, this, SLOT(launchStartupApps()) );
246   //if(DEBUG){ qDebug() << " - Hide Splashscreen:" << timer->elapsed();}
247   //splash.hide();
248   //LSession::processEvents();
249   if(DEBUG){ qDebug() << " - Close Splashscreen:" << timer->elapsed();}
250   splash.close();
251   //LSession::processEvents();
252   if(DEBUG){ qDebug() << " - Init Finished:" << timer->elapsed(); delete timer;}
253 }
254 
CleanupSession()255 void LSession::CleanupSession(){
256   //Close any running applications and tray utilities (Make sure to keep the UI interactive)
257   LSession::processEvents();
258   QDateTime time = QDateTime::currentDateTime();
259   qDebug() << "Start closing down the session: " << time.toString( Qt::SystemLocaleShortDate);
260   //Create a temporary flag to prevent crash dialogs from opening during cleanup
261   LUtils::writeFile("/tmp/.luminastopping",QStringList() << "yes", true);
262   //Start the logout chimes (if necessary)
263   int vol = LOS::audioVolume();
264   if(vol>=0){ sessionsettings->setValue("last_session_state/audio_volume", vol); }
265   bool playaudio = sessionsettings->value("PlayLogoutAudio",true).toBool();
266   if( playaudio ){
267       QString sfile = sessionsettings->value("audiofiles/logout", "").toString();
268        if(sfile.isEmpty() || !QFile::exists(sfile)){ sfile = LOS::LuminaShare()+"Logout.ogg"; }
269       playAudioFile(sfile);
270   }
271   //Stop the background system tray (detaching/closing apps as necessary)
272   stopSystemTray(!cleansession);
273   //Now perform any other cleanup
274   if(cleansession){
275     //Close any open windows
276     //qDebug() << " - Closing any open windows";
277     QList<WId> WL = XCB->WindowList(true);
278     for(int i=0; i<WL.length(); i++){
279       qDebug() << " - Closing window:" << XCB->WindowClass(WL[i]) << WL[i];
280       XCB->CloseWindow(WL[i]);
281       LSession::processEvents();
282     }
283     //Now wait a moment for things to close down before quitting
284     for(int i=0; i<20; i++){ LSession::processEvents(); usleep(25); } //1/2 second pause
285     //Kill any remaining windows
286     WL = XCB->WindowList(true); //all workspaces
287     for(int i=0; i<WL.length(); i++){
288       qDebug() << " - Window did not close, killing application:" << XCB->WindowClass(WL[i]) << WL[i];
289       XCB->KillClient(WL[i]);
290       LSession::processEvents();
291     }
292   }
293   evFilter->StopEventHandling();
294   //Stop the window manager
295   //qDebug() << " - Stopping the window manager";
296   //WM->stopWM();
297   //Now close down the desktop
298   qDebug() << " - Closing down the desktop elements";
299   for(int i=0; i<DESKTOPS.length(); i++){
300     DESKTOPS[i]->prepareToClose();
301     //don't actually close them yet - that will happen when the session exits
302     // this will leave the wallpapers up for a few moments (preventing black screens)
303   }
304   //Now wait a moment for things to close down before quitting
305   if(playaudio && mediaObj!=0){
306     //wait a max of 5 seconds for audio to finish
307     bool waitmore = true;
308     for(int i=0; i<100 && waitmore; i++){
309       usleep(50000); //50ms = 50000 us
310       waitmore = (mediaObj->state()==QMediaPlayer::PlayingState);
311       LSession::processEvents();
312     }
313     if(waitmore){ mediaObj->stop(); } //timed out
314   }else{
315     for(int i=0; i<20; i++){ LSession::processEvents(); usleep(25000); } //1/2 second pause
316   }
317   //Clean up the temporary flag
318   if(QFile::exists("/tmp/.luminastopping")){ QFile::remove("/tmp/.luminastopping"); }
319 }
320 
VersionStringToNumber(QString version)321 int LSession::VersionStringToNumber(QString version){
322   version = version.section("-",0,0); //trim any extra labels off the end
323   int maj, mid, min; //major/middle/minor version numbers (<Major>.<Middle>.<Minor>)
324   maj = mid = min = 0;
325   bool ok = true;
326   maj = version.section(".",0,0).toInt(&ok);
327   if(ok){ mid = version.section(".",1,1).toInt(&ok); }else{ maj = 0; }
328   if(ok){ min = version.section(".",2,2).toInt(&ok); }else{ mid = 0; }
329   if(!ok){ min = 0; }
330   //Now assemble the number
331   //NOTE: This format allows numbers to be anywhere from 0->999 without conflict
332   return (maj*1000000 + mid*1000 + min);
333 }
334 
NewCommunication(QStringList list)335 void LSession::NewCommunication(QStringList list){
336   if(DEBUG){ qDebug() << "New Communications:" << list; }
337   for(int i=0; i<list.length(); i++){
338     if(list[i]=="--check-geoms"){
339       screensChanged();
340     }else if(list[i]=="--show-start"){
341       emit StartButtonActivated();
342     }else if(list[i]=="--logout"){ QTimer::singleShot(1000, this, SLOT(StartLogout()));}
343   }
344 }
345 
launchStartupApps()346 void LSession::launchStartupApps(){
347   //First start any system-defined startups, then do user defined
348   qDebug() << "Launching startup applications";
349   //Enable Numlock
350   if(LUtils::isValidBinary("numlockx")){ //make sure numlockx is installed
351     if(sessionsettings->value("EnableNumlock",false).toBool()){
352       QProcess::startDetached("numlockx on");
353     }else{
354       QProcess::startDetached("numlockx off");
355     }
356   }
357   int tmp = LOS::ScreenBrightness();
358   if(tmp>0){
359     LOS::setScreenBrightness( tmp );
360     qDebug() << " - - Screen Brightness:" << QString::number(tmp)+"%";
361   }
362   //QProcess::startDetached("nice lumina-open -autostart-apps");
363   ExternalProcess::launch("lumina-open", QStringList() << "-autostart-apps", false);
364 
365   //Re-load the screen brightness and volume settings from the previous session
366   // Wait until after the XDG-autostart functions, since the audio system might be started that way
367   qDebug() << " - Loading previous settings";
368   tmp = sessionsettings->value("last_session_state/audio_volume",50).toInt();
369   if(tmp>=0){ LOS::setAudioVolume(tmp); }
370   qDebug() << " - - Audio Volume:" << QString::number(tmp)+"%";
371 
372   //Now play the login music since we are finished
373   if(sessionsettings->value("PlayStartupAudio",true).toBool()){
374      QString sfile = sessionsettings->value("audiofiles/login", "").toString();
375      if(sfile.isEmpty() || !QFile::exists(sfile)){ sfile = LOS::LuminaShare()+"Login.ogg"; }
376      playAudioFile(sfile);
377   }
378   //qDebug() << "[DESKTOP INIT FINISHED]";
379 }
380 
StartLogout()381 void LSession::StartLogout(){
382   CleanupSession();
383   QCoreApplication::exit(0);
384 }
385 
StartShutdown(bool skipupdates)386 void LSession::StartShutdown(bool skipupdates){
387   CleanupSession();
388   LOS::systemShutdown(skipupdates);
389   QCoreApplication::exit(0);
390 }
391 
StartReboot(bool skipupdates)392 void LSession::StartReboot(bool skipupdates){
393   CleanupSession();
394   LOS::systemRestart(skipupdates);
395   QCoreApplication::exit(0);
396 }
397 
reloadIconTheme()398 void LSession::reloadIconTheme(){
399   //Wait a moment for things to settle before sending out the signal to the interfaces
400   QApplication::processEvents();
401   QApplication::processEvents();
402   emit IconThemeChanged();
403 }
404 
watcherChange(QString changed)405 void LSession::watcherChange(QString changed){
406   if(DEBUG){ qDebug() << "Session Watcher Change:" << changed; }
407   //if(changed.endsWith("fluxbox-init") || changed.endsWith("fluxbox-keys")){ refreshWindowManager(); }
408   if(changed.endsWith("sessionsettings.conf") ){
409     sessionsettings->sync();
410     //qDebug() << "Session Settings Changed";
411     if(sessionsettings->contains("Qt5_theme_engine")){
412       QString engine = sessionsettings->value("Qt5_theme_engine","").toString();
413       //qDebug() << "Set Qt5 theme engine: " << engine;
414       if(engine.isEmpty()){ unsetenv("QT_QPA_PLATFORMTHEME"); }
415       else{ setenv("QT_QPA_PLATFORMTHEME", engine.toUtf8().data(),1); }
416     }else{
417       setenv("QT_QPA_PLATFORMTHEME", "lthemeengine",1); //ensure the lumina theme engine is always enabled
418     }
419     emit SessionConfigChanged();
420   }else if(changed.endsWith("desktopsettings.conf") ){ emit DesktopConfigChanged(); }
421   else if(changed == LUtils::standardDirectory(LUtils::Desktop) ){
422     desktopFiles = QDir(changed).entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs ,QDir::Name | QDir::IgnoreCase | QDir::DirsFirst);
423     if(DEBUG){ qDebug() << "New Desktop Files:" << desktopFiles.length(); }
424     emit DesktopFilesChanged();
425   }else if(changed.toLower() == "/media" || changed.toLower().startsWith("/run/media/") || changed == "/tmp/.autofs_change" ){
426     emit MediaFilesChanged();
427   }else if(changed.endsWith("favorites.list")){ emit FavoritesChanged(); }
428   //Now ensure this file was not removed from the watcher
429   if(!watcher->files().contains(changed) && !watcher->directories().contains(changed)){
430     if(!QFile::exists(changed)){
431       //Create the file really quick to ensure it can be watched
432       //TODO
433     }
434     watcher->addPath(changed);
435   }
436 }
437 
screensChanged()438 void LSession::screensChanged(){
439   qDebug() << "Screen Number Changed";
440   if(screenTimer->isActive()){ screenTimer->stop(); }
441   screenTimer->start();
442   xchange = true;
443 }
444 
screenResized(int scrn)445 void LSession::screenResized(int scrn){
446   qDebug() << "Screen Resized:" << scrn;
447   if(screenTimer->isActive()){ screenTimer->stop(); }
448   screenTimer->start();
449   xchange = true;
450 }
451 
checkWindowGeoms()452 void LSession::checkWindowGeoms(){
453   //Only do one window per run (this will be called once per new window - with time delays between)
454   if(checkWin.isEmpty()){ return; }
455   WId win = checkWin.takeFirst();
456   if(RunningApps.contains(win) ){ //just to make sure it did not close during the delay
457     adjustWindowGeom( win );
458   }
459 }
460 
checkUserFiles()461 bool LSession::checkUserFiles(){
462   //internal version conversion examples:
463   //  [1.0.0 -> 1000000], [1.2.3 -> 1002003], [0.6.1 -> 6001]
464   qDebug() << "Check User Files";
465     //char tmp[] = "junk\0";
466     //int tmpN = 0;
467   //QApplication A(tmpN, (char **)&tmp);
468   QSettings sset("lumina-desktop", "sessionsettings");
469   QString OVS = sset.value("DesktopVersion","0").toString(); //Old Version String
470   qDebug() << " - Old Version:" << OVS;
471   qDebug() << " - Current Version:" << LDesktopUtils::LuminaDesktopVersion();
472   bool changed = LDesktopUtils::checkUserFiles(OVS, LDesktopUtils::LuminaDesktopVersion());
473   qDebug() << " - Made Changes:" << changed;
474   if(changed){
475     //Save the current version of the session to the settings file (for next time)
476     sset.setValue("DesktopVersion", LDesktopUtils::LuminaDesktopVersion());
477   }
478   qDebug() << "Finished with user files check";
479   //delete A;
480   return changed;
481 }
482 
refreshWindowManager()483 void LSession::refreshWindowManager(){
484   LUtils::runCmd("touch \""+QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/fluxbox-init\"" );
485 }
486 
updateDesktops()487 void LSession::updateDesktops(){
488   qDebug() << " - Update Desktops";
489   QList<QScreen*> screens = QGuiApplication::screens();
490   int sC = screens.count();
491   qDebug() << "  Screen Count:" << sC;
492   qDebug() << "  DESKTOPS Length:" << DESKTOPS.length();
493   if(sC<1){ return; } //stop here - no screens available temporarily (displayport/4K issue)
494   screenRect = QRect(); //clear it
495   QList<QScreen*>::const_iterator it;
496   int i = 0;
497   for(it = screens.constBegin(); it != screens.constEnd(); ++it, ++i) {
498     screenRect = screenRect.united((*it)->availableGeometry());
499     qDebug() << " -- Screen["+QString::number(i)+"]:" << (*it)->availableGeometry();
500   }
501 
502   bool firstrun = (DESKTOPS.length()==0);
503   QSettings dset("lumina-desktop", "desktopsettings");
504   if(firstrun && sC==1){
505     //Sanity check - ensure the monitor ID did not change between sessions for single-monitor setups
506     QString name = QApplication::screens().at(0)->name();
507     if(!dset.contains("desktop-"+name+"/screen/lastHeight")){
508       //Empty Screen - find the previous one and migrate the settings over
509       QStringList old = dset.allKeys().filter("desktop-").filter("/screen/lastHeight");
510 	QStringList lastused = dset.value("last_used_screens").toStringList();
511       QString oldname;
512       for(int i=0; i<old.length(); i++){
513         QString tmp = old[i].section("/",0,0).section("-",1,-1); //old desktop ID
514         if(lastused.contains(tmp)){
515           oldname = tmp; break; //use the first screen that was last used
516         }
517       }
518       if(!oldname.isEmpty()){ LDesktopUtils::MigrateDesktopSettings(&dset, oldname, name); }
519     }
520   }
521 
522   // If the screen count is changing on us
523   if ( sC != QGuiApplication::screens().count() ) {
524     qDebug() << "Screen Count changed while running";
525     return;
526   }
527 
528   //First clean up any current desktops
529   QList<int> dnums; //keep track of which screens are already managed
530   QList<QRect> geoms;
531   for(int i=0; i<DESKTOPS.length(); i++){
532     if ( DESKTOPS[i]->Screen() < 0 || DESKTOPS[i]->Screen() >= sC || geoms.contains(screens.at(i)->availableGeometry())) {
533         //qDebug() << " - Close desktop:" << i;
534         qDebug() << " - Close desktop on screen:" << DESKTOPS[i]->Screen();
535         DESKTOPS[i]->prepareToClose();
536         DESKTOPS.takeAt(i)->deleteLater();
537         i--;
538       } else {
539         //qDebug() << " - Show desktop:" << i;
540         qDebug() << " - Show desktop on screen:" << DESKTOPS[i]->Screen();
541         DESKTOPS[i]->UpdateGeometry();
542         DESKTOPS[i]->show();
543 	dnums << DESKTOPS[i]->Screen();
544 	geoms << screens.at(i)->availableGeometry();
545       }
546   }
547 
548   //Now add any new desktops
549   QStringList allNames;
550   QList<QScreen*> scrns = QApplication::screens();
551   for(int i=0; i<sC; i++){
552     allNames << scrns.at(i)->name();
553     if(!dnums.contains(i) && !geoms.contains(screens.at(i)->availableGeometry()) ){
554       //Start the desktop on this screen
555       qDebug() << " - Start desktop on screen:" << i;
556       DESKTOPS << new LDesktop(i);
557       geoms << screens.at(i)->availableGeometry();
558     }
559   }
560   dset.setValue("last_used_screens", allNames);
561   //Make sure fluxbox also gets prompted to re-load screen config if the number of screens changes in the middle of a session
562     if(!firstrun && xchange){
563       qDebug() << "Update WM";
564       //QProcess::startDetached("killall fluxbox");
565       xchange = false;
566     }
567 
568   //Make sure all the background windows are registered on the system as virtual roots
569   QTimer::singleShot(100,this, SLOT(registerDesktopWindows()));
570   //Determine if any High-DPI screens are available and enable auto-scaling as needed
571   /*for(int i=0; i<scrns.length(); i++){
572     qDebug() << "Check Screen DPI:" << scrns[i]->name();
573     qDebug() << " -- Physical DPI:" << scrns[i]->physicalDotsPerInchX() << "x" << scrns[i]->physicalDotsPerInchY();
574     qDebug() << " -- Logical DPI:" << scrns[i]->logicalDotsPerInchX() << "x" << scrns[i]->logicalDotsPerInchY();
575     if(scrns[i]->logicalDotsPerInchX() > 110 || scrns[i]->logicalDotsPerInchY()>110){ //4K is ~196, 3K is ~150
576       setenv("QT_AUTO_SCREEN_SCALE_FACTOR","1",true); //Enable the automatic Qt5 DPI scaling for apps
577       break;
578     }else if(i==(scrns.length()-1)){
579       unsetenv("QT_AUTO_SCREEN_SCALE_FACTOR");
580     }
581   }*/
582 }
583 
registerDesktopWindows()584 void LSession::registerDesktopWindows(){
585   QList<WId> wins;
586   for(int i=0; i<DESKTOPS.length(); i++){
587     wins << DESKTOPS[i]->backgroundID();
588   }
589   XCB->RegisterVirtualRoots(wins);
590 }
591 
adjustWindowGeom(WId win,bool maximize)592 void LSession::adjustWindowGeom(WId win, bool maximize){
593   //return; //temporary disable
594   if(DEBUG){ qDebug() << "AdjustWindowGeometry():" << win << maximize << XCB->WindowClass(win); }
595   if(XCB->WindowIsFullscreen(win) >=0 ){ return; } //don't touch it
596   //Quick hack for making sure that new windows are not located underneath any panels
597   // Get the window location
598   QRect geom = XCB->WindowGeometry(win, false);
599   //Get the frame size
600   QList<int> frame = XCB->WindowFrameGeometry(win); //[top,bottom,left,right] sizes of the frame
601   //Calculate the full geometry (window + frame)
602   QRect fgeom = QRect(geom.x()-frame[2], geom.y()-frame[0], geom.width()+frame[2]+frame[3], geom.height()+frame[0]+frame[1]);
603   if(DEBUG){
604     qDebug() << "Check Window Geometry:" << XCB->WindowClass(win) << !geom.isNull() << geom << fgeom;
605   }
606   if(geom.isNull()){ return; } //Could not get geometry for some reason
607   //Get the available geometry for the screen the window is on
608   QRect desk;
609   QList<QScreen *> screens = QGuiApplication::screens();
610   for(int i=0; i<DESKTOPS.length(); i++){
611     if( screens.at(i)->availableGeometry().contains(geom.center()) ){
612       //Window is on this screen
613       if(DEBUG){ qDebug() << " - On Screen:" << DESKTOPS[i]->Screen(); }
614       desk = DESKTOPS[i]->availableScreenGeom();
615       if(DEBUG){ qDebug() << " - Screen Geom:" << desk; }
616       break;
617     }
618   }
619   if(desk.isNull()){ return; } //Unable to determine screen
620   //Adjust the window location if necessary
621   if(maximize){
622     if(DEBUG){ qDebug() << " - Maximizing New Window:" << desk.width() << desk.height(); }
623     geom = desk; //Use the full screen
624     XCB->MoveResizeWindow(win, geom);
625     XCB->MaximizeWindow(win, true); //directly set the appropriate "maximized" flags (bypassing WM)
626 
627   }else if(!desk.contains(fgeom) ){
628     //Adjust origin point for left/top margins
629     if(fgeom.y() < desk.y()){ geom.moveTop(desk.y()+frame[0]); fgeom.moveTop(desk.y()); } //move down to the edge (top panel)
630     if(fgeom.x() < desk.x()){ geom.moveLeft(desk.x()+frame[2]); fgeom.moveLeft(desk.x()); } //move right to the edge (left panel)
631     //Adjust size for bottom margins (within reason, since window titles are on top normally)
632    // if(geom.right() > desk.right() && (geom.width() > 100)){ geom.setRight(desk.right()); }
633     if(fgeom.bottom() > desk.bottom() && geom.height() > 10){
634       if(DEBUG){ qDebug() << "Adjust Y:" << fgeom << geom << desk; }
635       int diff = fgeom.bottom()-desk.bottom(); //amount of overlap
636       if(DEBUG){ qDebug() << "Y-Diff:" << diff; }
637       if(diff < 0){ diff = -diff; } //need a positive value
638       if( (fgeom.height()+ diff)< desk.height()){
639         //just move the window - there is room for it above
640 	geom.setBottom(desk.bottom()-frame[1]);
641 	fgeom.setBottom(desk.bottom());
642       }else if(geom.height() > diff){ //window bigger than the difference
643 	//Need to resize the window - keeping the origin point the same
644 	geom.setHeight( geom.height()-diff-1 ); //shrink it by the difference (need an extra pixel somewhere)
645 	fgeom.setHeight( fgeom.height()-diff );
646       }
647     }
648     //Now move/resize the window
649     if(DEBUG){
650       qDebug() << " - New Geom:" << geom << fgeom;
651     }
652     XCB->WM_Request_MoveResize_Window(win, geom);
653   }
654 
655 }
656 
SessionEnding()657 void LSession::SessionEnding(){
658   stopSystemTray(); //just in case it was not stopped properly earlier
659 }
660 
handleClipboard(QClipboard::Mode mode)661 void LSession::handleClipboard(QClipboard::Mode mode){
662   if ( !ignoreClipboard && mode == QClipboard::Clipboard ){ //only support Clipboard
663     const QMimeData *mime = QApplication::clipboard()->mimeData(mode);
664     if (mime==NULL) { return; }
665     if (mime->hasText() && !QApplication::clipboard()->ownsClipboard()) {
666       //preserve the entire mimeData set, not just the text
667       //Note that even when we want to "save" the test, we should keep the whole thing
668       //  this preserves formatting codes and more that apps might need
669       QMimeData *copy = new QMimeData();
670       QStringList fmts = mime->formats();
671       for(int i=0; i<fmts.length(); i++){ copy->setData(fmts[i], mime->data(fmts[i])); }
672       ignoreClipboard = true;
673       QApplication::clipboard()->setMimeData(copy, mode);
674       ignoreClipboard = false;
675       //QMetaObject::invokeMethod(this, "storeClipboard", Qt::QueuedConnection, Q_ARG(QString, mime->text()), Q_ARG(QClipboard::Mode, mode));
676     }
677   }
678 }
679 
storeClipboard(QString text,QClipboard::Mode mode)680 void LSession::storeClipboard(QString text, QClipboard::Mode mode){
681   ignoreClipboard = true;
682   QApplication::clipboard()->setText(text, mode);
683   ignoreClipboard = false;
684 }
685 
686 //===============
687 //  SYSTEM ACCESS
688 //===============
LaunchApplication(QString cmd)689 void LSession::LaunchApplication(QString cmd){
690   //LSession::setOverrideCursor(QCursor(Qt::BusyCursor));
691   ExternalProcess::launch(cmd, QStringList(), true);
692   //QProcess::startDetached(cmd);
693 }
694 
DesktopFiles()695 QFileInfoList LSession::DesktopFiles(){
696   return desktopFiles;
697 }
698 
screenGeom(int num)699 QRect LSession::screenGeom(int num){
700   QList<QScreen *> screens = QGuiApplication::screens();
701   if(num < 0 || num >= screens.count() ){ return QRect(); }
702   QRect geom = screens.at(num)->geometry();
703   return geom;
704 }
705 
applicationMenu()706 AppMenu* LSession::applicationMenu(){
707   return appmenu;
708 }
709 
settingsMenu()710 SettingsMenu* LSession::settingsMenu(){
711   return settingsmenu;
712 }
713 
sessionSettings()714 QSettings* LSession::sessionSettings(){
715   return sessionsettings;
716 }
717 
DesktopPluginSettings()718 QSettings* LSession::DesktopPluginSettings(){
719   return DPlugSettings;
720 }
721 
activeWindow()722 WId LSession::activeWindow(){
723   //Check the last active window pointer first
724   WId active = XCB->ActiveWindow();
725   //qDebug() << "Check Active Window:" << active << lastActiveWin;
726   if(RunningApps.contains(active)){ lastActiveWin = active; }
727   else if(RunningApps.contains(lastActiveWin) && XCB->WindowState(lastActiveWin) >= LXCB::VISIBLE){} //no change needed
728   else if(RunningApps.contains(lastActiveWin) && RunningApps.length()>1){
729     int start = RunningApps.indexOf(lastActiveWin);
730     if(start<1){ lastActiveWin = RunningApps.length()-1; } //wrap around to the last item
731     else{ lastActiveWin = RunningApps[start-1]; }
732   }else{
733     //Need to change the last active window - find the first one which is visible
734     lastActiveWin = 0; //fallback value - nothing active
735     for(int i=0; i<RunningApps.length(); i++){
736       if(XCB->WindowState(RunningApps[i]) >= LXCB::VISIBLE){
737         lastActiveWin = RunningApps[i];
738 	break;
739       }
740     }
741     //qDebug() << " -- New Last Active Window:" << lastActiveWin;
742   }
743   return lastActiveWin;
744 }
745 
746 //Temporarily change the session locale (nothing saved between sessions)
switchLocale(QString localeCode)747 void LSession::switchLocale(QString localeCode){
748   currTranslator = LUtils::LoadTranslation(this, "lumina-desktop", localeCode, currTranslator);
749   if(currTranslator!=0 || localeCode=="en_US"){
750     LUtils::setLocaleEnv(localeCode); //will set everything to this locale (no custom settings)
751   }
752   emit LocaleChanged();
753 }
754 
systemWindow()755 void LSession::systemWindow(){
756   if(sysWindow==0){ sysWindow = new SystemWindow(); }
757   else{ sysWindow->updateWindow(); }
758   sysWindow->show();
759   //LSession::processEvents();
760 }
761 
762 //Play System Audio
playAudioFile(QString filepath)763 void LSession::playAudioFile(QString filepath){
764   if( !QFile::exists(filepath) ){ return; }
765   //Setup the audio output systems for the desktop
766   if(DEBUG){ qDebug() << "Play Audio File"; }
767   if(mediaObj==0){   qDebug() << " - Initialize media player"; mediaObj = new QMediaPlayer(); }
768   if(mediaObj !=0 ){
769     if(DEBUG){ qDebug() << " - starting playback:" << filepath; }
770     mediaObj->setVolume(100);
771     mediaObj->setMedia(QUrl::fromLocalFile(filepath));
772     mediaObj->play();
773     LSession::processEvents();
774   }
775   if(DEBUG){ qDebug() << " - Done with Audio File"; }
776 }
777 // =======================
778 //  XCB EVENT FILTER FUNCTIONS
779 // =======================
RootSizeChange()780 void LSession::RootSizeChange(){
781   if(DESKTOPS.isEmpty() || screenRect.isNull()){ return; } //Initial setup not run yet
782 
783   QRect tmp;
784   QList<QScreen*> screens = QGuiApplication::screens();
785   QList<QScreen*>::const_iterator it;
786   for(it = screens.constBegin(); it != screens.constEnd(); ++it) {
787     tmp = tmp.united( (*it)->availableGeometry() );
788   }
789   if(tmp == screenRect){ return; } //false event - session size did not change
790   qDebug() << "Got Root Size Change";
791   xchange = true;
792   screenTimer->start();
793 }
794 
WindowPropertyEvent()795 void LSession::WindowPropertyEvent(){
796   if(DEBUG){ qDebug() << "Window Property Event"; }
797   QList<WId> newapps = XCB->WindowList();
798   if(RunningApps.length() < newapps.length()){
799     //New Window found
800     //qDebug() << "New window found";
801     //LSession::restoreOverrideCursor(); //restore the mouse cursor back to normal (new window opened?)
802     //Perform sanity checks on any new window geometries
803     for(int i=0; i<newapps.length() && !TrayStopping; i++){
804       if(!RunningApps.contains(newapps[i])){
805         checkWin << newapps[i];
806 	XCB->SelectInput(newapps[i]); //make sure we get property/focus events for this window
807 	if(DEBUG){ qDebug() << "New Window - check geom in a moment:" << XCB->WindowClass(newapps[i]); }
808 	QTimer::singleShot(50, this, SLOT(checkWindowGeoms()) );
809       }
810     }
811   }
812 
813   //Now save the list and send out the event
814   RunningApps = newapps;
815   emit WindowListEvent();
816 }
817 
WindowPropertyEvent(WId win)818 void LSession::WindowPropertyEvent(WId win){
819   //Emit the single-app signal if the window in question is one used by the task manager
820   if(RunningApps.contains(win)){
821     if(DEBUG){ qDebug() << "Single-window property event"; }
822     /*if( XCB->WindowClass(win).contains("VirtualBox")){
823       qDebug() << "Found VirtualBox Window:";
824       QList<LXCB::WINDOWSTATE> states = XCB->WM_Get_Window_States(win);
825       if(states.contains(LXCB::S_FULLSCREEN) && !states.contains(LXCB::S_HIDDEN)){
826        qDebug() << "Adjusting VirtualBox Window (fullscreen)";
827         XCB->WM_Set_Window_Type(win, QList<LXCB::WINDOWTYPE>() << LXCB::T_NORMAL << LXCB::T_UTILITY );
828         XCB->RestoreWindow(win);
829       }
830     }*/
831     //emit WindowListEvent();
832     WindowPropertyEvent(); //Run through the entire routine for window checks
833   }else if(RunningTrayApps.contains(win)){
834     emit TrayIconChanged(win);
835   }
836 }
837 
SysTrayDockRequest(WId win)838 void LSession::SysTrayDockRequest(WId win){
839   if(TrayStopping){ return; }
840   attachTrayWindow(win); //Check to see if the window is already registered
841 }
842 
WindowClosedEvent(WId win)843 void LSession::WindowClosedEvent(WId win){
844   if(TrayStopping){ return; }
845   removeTrayWindow(win); //Check to see if the window is a tray app
846 }
847 
WindowConfigureEvent(WId win)848 void LSession::WindowConfigureEvent(WId win){
849   if(TrayStopping){ return; }
850     if(RunningTrayApps.contains(win)){
851       if(DEBUG){ qDebug() << "SysTray: Configure Event"; }
852       emit TrayIconChanged(win); //trigger a repaint event
853     }else if(RunningApps.contains(win)){
854       WindowPropertyEvent();
855     }
856 }
857 
WindowDamageEvent(WId win)858 void LSession::WindowDamageEvent(WId win){
859   if(TrayStopping){ return; }
860     if(RunningTrayApps.contains(win)){
861       if(DEBUG){ qDebug() << "SysTray: Damage Event"; }
862       emit TrayIconChanged(win); //trigger a repaint event
863     }
864 }
865 
WindowSelectionClearEvent(WId win)866 void LSession::WindowSelectionClearEvent(WId win){
867   if(win==SystemTrayID && !TrayStopping){
868     qDebug() << "Stopping system tray";
869     stopSystemTray(true); //make sure to detach all windows
870   }
871 }
872 
873 
874 //======================
875 //   SYSTEM TRAY FUNCTIONS
876 //======================
registerVisualTray(WId visualTray)877 bool LSession::registerVisualTray(WId visualTray){
878   //Only one visual tray can be registered at a time
879   //  (limitation of how tray apps are embedded)
880   if(TrayStopping){ return false; }
881   else if(VisualTrayID==0){ VisualTrayID = visualTray; return true; }
882   else if(VisualTrayID==visualTray){ return true; }
883   else{ return false; }
884 }
885 
unregisterVisualTray(WId visualTray)886 void LSession::unregisterVisualTray(WId visualTray){
887   if(VisualTrayID==visualTray){
888     qDebug() << "Unregistered Visual Tray";
889     VisualTrayID=0;
890     if(!TrayStopping){ emit VisualTrayAvailable(); }
891   }
892 }
893 
currentTrayApps(WId visualTray)894 QList<WId> LSession::currentTrayApps(WId visualTray){
895   if(visualTray==VisualTrayID){
896     //Check the validity of all the current tray apps (make sure nothing closed erratically)
897     for(int i=0; i<RunningTrayApps.length(); i++){
898       if(XCB->WindowClass(RunningTrayApps[i]).isEmpty()){ RunningTrayApps.removeAt(i); i--; }
899     }
900     return RunningTrayApps;
901   }else if( registerVisualTray(visualTray) ){
902     return RunningTrayApps;
903   }else{
904     return QList<WId>();
905   }
906 }
907 
startSystemTray()908 void LSession::startSystemTray(){
909   if(SystemTrayID!=0){ return; }
910   RunningTrayApps.clear(); //nothing running yet
911   SystemTrayID = XCB->startSystemTray(0);
912   TrayStopping = false;
913   if(SystemTrayID!=0){
914     XCB->SelectInput(SystemTrayID); //make sure TrayID events get forwarded here
915     TrayDmgEvent = XCB->GenerateDamageID(SystemTrayID);
916     evFilter->setTrayDamageFlag(TrayDmgEvent);
917     qDebug() << "System Tray Started Successfully";
918     if(DEBUG){ qDebug() << " - System Tray Flags:" << TrayDmgEvent << TrayDmgError; }
919   }
920 }
921 
stopSystemTray(bool detachall)922 void LSession::stopSystemTray(bool detachall){
923   if(TrayStopping){ return; } //already run
924   qDebug() << "Stopping system tray...";
925   TrayStopping = true; //make sure the internal list does not modified during this
926   //Close all the running Tray Apps
927   QList<WId> tmpApps = RunningTrayApps;
928   RunningTrayApps.clear(); //clear this ahead of time so tray's do not attempt to re-access the apps
929   if(!detachall){
930     for(int i=0; i<tmpApps.length(); i++){
931       qDebug() << " - Stopping tray app:" << XCB->WindowClass(tmpApps[i]);
932       //Tray apps are special and closing the window does not close the app
933       XCB->KillClient(tmpApps[i]);
934       LSession::processEvents();
935     }
936   }
937   //Now close down the tray backend
938  XCB->closeSystemTray(SystemTrayID);
939   SystemTrayID = 0;
940   TrayDmgEvent = 0;
941   TrayDmgError = 0;
942   evFilter->setTrayDamageFlag(0); //turn off tray event handling
943   emit TrayListChanged();
944   LSession::processEvents();
945 }
946 
attachTrayWindow(WId win)947 void LSession::attachTrayWindow(WId win){
948   //static int appnum = 0;
949   if(TrayStopping){ return; }
950   if(RunningTrayApps.contains(win)){ return; } //already managed
951   qDebug() << "Session Tray: Window Added";
952   RunningTrayApps << win;
953   //LSession::restoreOverrideCursor();
954   if(DEBUG){ qDebug() << "Tray List Changed"; }
955   emit TrayListChanged();
956 }
957 
removeTrayWindow(WId win)958 void LSession::removeTrayWindow(WId win){
959   if(SystemTrayID==0){ return; }
960   for(int i=0; i<RunningTrayApps.length(); i++){
961     if(win==RunningTrayApps[i]){
962       qDebug() << "Session Tray: Window Removed";
963       RunningTrayApps.removeAt(i);
964       emit TrayListChanged();
965       break;
966     }
967   }
968 }
969 //=========================
970 //  START MENU FUNCTIONS
971 //=========================
registerStartButton(QString ID)972 bool LSession::registerStartButton(QString ID){
973   if(StartButtonID.isEmpty()){ StartButtonID = ID; }
974   return (StartButtonID==ID);
975 }
976 
unregisterStartButton(QString ID)977 void LSession::unregisterStartButton(QString ID){
978   if(StartButtonID == ID){
979     StartButtonID.clear();
980     emit StartButtonAvailable();
981   }
982 }
983