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