//=========================================== // Lumina-DE source code // Copyright (c) 2012-2015, Ken Moore // Available under the 3-clause BSD license // See the LICENSE file for full details //=========================================== #include "LSession.h" #include #include #include "LXcbEventFilter.h" #include "BootSplash.h" //LibLumina X11 class #include #include #include //for usleep() usage //X includes (these need to be last due to Qt compile issues) #include #include #include #include #include #ifndef DEBUG #define DEBUG 0 #endif XCBEventFilter *evFilter = 0; LSession::LSession(int &argc, char ** argv) : QApplication(argc, argv){ this->setApplicationName("Lumina Desktop Environment"); this->setApplicationVersion( LUtils::LuminaDesktopVersion() ); this->setOrganizationName("LuminaDesktopEnvironment"); this->setQuitOnLastWindowClosed(false); //since the LDesktop's are not necessarily "window"s //Enabled a few of the simple effects by default this->setEffectEnabled( Qt::UI_AnimateMenu, true); this->setEffectEnabled( Qt::UI_AnimateCombo, true); this->setEffectEnabled( Qt::UI_AnimateTooltip, true); //this->setAttribute(Qt::AA_UseHighDpiPixmaps); //allow pixmaps to be scaled up as well as down //this->setStyle( new MenuProxyStyle); //QMenu icon size override SystemTrayID = 0; VisualTrayID = 0; sysWindow = 0; TrayDmgEvent = 0; TrayDmgError = 0; cleansession = true; TrayStopping = false; for(int i=1; iinstallNativeEventFilter( evFilter ); } LSession::~LSession(){ WM->stopWM(); for(int i=0; istart(); qDebug() << " - Init srand:" << timer->elapsed();} //Seed random number generator (if needed) qsrand( QTime::currentTime().msec() ); //Setup the QSettings default paths splash.showScreen("settings"); if(DEBUG){ qDebug() << " - Init QSettings:" << timer->elapsed();} QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, QDir::homePath()+"/.lumina"); sessionsettings = new QSettings("LuminaDE", "sessionsettings"); DPlugSettings = new QSettings("pluginsettings","desktopsettings"); //Setup the user's lumina settings directory as necessary splash.showScreen("user"); if(DEBUG){ qDebug() << " - Init User Files:" << timer->elapsed();} checkUserFiles(); //adds these files to the watcher as well //Initialize the internal variables DESKTOPS.clear(); //Start the background system tray splash.showScreen("systray"); if(DEBUG){ qDebug() << " - Init System Tray:" << timer->elapsed();} startSystemTray(); //Launch Fluxbox splash.showScreen("wm"); if(DEBUG){ qDebug() << " - Init WM:" << timer->elapsed();} WM = new WMProcess(); WM->startWM(); //Initialize the global menus qDebug() << " - Initialize system menus"; splash.showScreen("apps"); if(DEBUG){ qDebug() << " - Init AppMenu:" << timer->elapsed();} appmenu = new AppMenu(); splash.showScreen("menus"); if(DEBUG){ qDebug() << " - Init SettingsMenu:" << timer->elapsed();} settingsmenu = new SettingsMenu(); if(DEBUG){ qDebug() << " - Init SystemWindow:" << timer->elapsed();} sysWindow = new SystemWindow(); //Initialize the desktops splash.showScreen("desktop"); if(DEBUG){ qDebug() << " - Init Desktops:" << timer->elapsed();} desktopFiles = QDir(QDir::homePath()+"/Desktop").entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs, QDir::Name | QDir::IgnoreCase | QDir::DirsFirst); updateDesktops(); //Now setup the system watcher for changes splash.showScreen("final"); qDebug() << " - Initialize file system watcher"; if(DEBUG){ qDebug() << " - Init QFileSystemWatcher:" << timer->elapsed();} watcher = new QFileSystemWatcher(this); //watcher->addPath( QDir::homePath()+"/.lumina/stylesheet.qss" ); watcher->addPath( QDir::homePath()+"/.lumina/LuminaDE/sessionsettings.conf" ); watcher->addPath( QDir::homePath()+"/.lumina/LuminaDE/desktopsettings.conf" ); watcher->addPath( QDir::homePath()+"/.lumina/fluxbox-init" ); watcher->addPath( QDir::homePath()+"/.lumina/fluxbox-keys" ); watcher->addPath( QDir::homePath() ); //connect internal signals/slots connect(this->desktop(), SIGNAL(screenCountChanged(int)), this, SLOT(updateDesktops()) ); connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(watcherChange(QString)) ); connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(watcherChange(QString)) ); connect(this, SIGNAL(aboutToQuit()), this, SLOT(SessionEnding()) ); if(DEBUG){ qDebug() << " - Init Finished:" << timer->elapsed(); delete timer;} //QTimer::singleShot(3000, this, SLOT(launchStartupApps()) ); //startup these processes in 3 seconds splash.close(); QApplication::processEvents(); launchStartupApps(); } void LSession::CleanupSession(){ //Close any running applications and tray utilities (Make sure to keep the UI interactive) LSession::processEvents(); QDateTime time = QDateTime::currentDateTime(); qDebug() << "Start closing down the session: " << time.toString( Qt::SystemLocaleShortDate); //Create a temporary flag to prevent crash dialogs from opening during cleanup LUtils::writeFile("/tmp/.luminastopping",QStringList() << "yes", true); //Start the logout chimes (if necessary) bool playaudio = sessionsettings->value("PlayLogoutAudio",true).toBool(); if( playaudio ){ playAudioFile(LOS::LuminaShare()+"Logout.ogg"); } //Stop the background system tray (detaching/closing apps as necessary) stopSystemTray(!cleansession); //Now perform any other cleanup if(cleansession){ //Close any open windows //qDebug() << " - Closing any open windows"; QList WL = XCB->WindowList(true); for(int i=0; iWindowClass(WL[i]) << WL[i]; XCB->CloseWindow(WL[i]); LSession::processEvents(); } //Now wait a moment for things to close down before quitting for(int i=0; i<20; i++){ LSession::processEvents(); usleep(25); } //1/2 second pause //Kill any remaining windows WL = XCB->WindowList(true); //all workspaces for(int i=0; iWindowClass(WL[i]) << WL[i]; XCB->KillClient(WL[i]); LSession::processEvents(); } } evFilter->StopEventHandling(); //Stop the window manager qDebug() << " - Stopping the window manager"; WM->stopWM(); //Now close down the desktop qDebug() << " - Closing down the desktop elements"; for(int i=0; iprepareToClose(); //don't actually close them yet - that will happen when the session exits // this will leave the wallpapers up for a few moments (preventing black screens) } //Now wait a moment for things to close down before quitting if(playaudio){ //wait a max of 3 seconds for audio to finish bool waitmore = true; for(int i=0; i<60 && waitmore; i++){ usleep(50000); //50ms = 50000 us waitmore = (mediaObj->state()==QMediaPlayer::PlayingState); //waitmore = !audioThread->wait(500); LSession::processEvents(); } if(waitmore){ mediaObj->stop(); } //timed out }else{ for(int i=0; i<20; i++){ LSession::processEvents(); usleep(25000); } //1/2 second pause } //Clean up the temporary flag if(QFile::exists("/tmp/.luminastopping")){ QFile::remove("/tmp/.luminastopping"); } //if(audioThread!=0){ audioThread->exit(0); } } int LSession::VersionStringToNumber(QString version){ version = version.section("-",0,0); //trim any extra labels off the end int maj, mid, min; //major/middle/minor version numbers (..) maj = mid = min = 0; bool ok = true; maj = version.section(".",0,0).toInt(&ok); if(ok){ mid = version.section(".",1,1).toInt(&ok); }else{ maj = 0; } if(ok){ min = version.section(".",2,2).toInt(&ok); }else{ mid = 0; } if(!ok){ min = 0; } //Now assemble the number //NOTE: This format allows numbers to be anywhere from 0->999 without conflict return (maj*1000000 + mid*1000 + min); } void LSession::launchStartupApps(){ //First start any system-defined startups, then do user defined qDebug() << "Launching startup applications"; //Enable Numlock if(LUtils::isValidBinary("numlockx")){ //make sure numlockx is installed if(sessionsettings->value("EnableNumlock",false).toBool()){ QProcess::startDetached("numlockx on"); } else{ QProcess::startDetached("numlockx off"); } } int tmp = LOS::ScreenBrightness(); LOS::setScreenBrightness( tmp ); qDebug() << " - - Screen Brightness:" << QString::number(tmp)+"%"; //Now get any XDG startup applications and launch them QList xdgapps = LXDG::findAutoStartFiles(); for(int i=0; i 250ms total wait LSession::processEvents(); } } //Re-load the screen brightness and volume settings from the previous session // Wait until after the XDG-autostart functions, since the audio system might be started that way qDebug() << " - Loading previous settings"; tmp = LOS::audioVolume(); LOS::setAudioVolume(tmp); qDebug() << " - - Audio Volume:" << QString::number(tmp)+"%"; //Now play the login music since we are finished if(sessionsettings->value("PlayStartupAudio",true).toBool()){ //Make sure to re-set the system volume to the last-used value at outset int vol = LOS::audioVolume(); if(vol>=0){ LOS::setAudioVolume(vol); } LSession::playAudioFile(LOS::LuminaShare()+"Login.ogg"); } qDebug() << " - Finished with startup routines"; } void LSession::StartLogout(){ CleanupSession(); QCoreApplication::exit(0); } void LSession::StartShutdown(){ CleanupSession(); LOS::systemShutdown(); QCoreApplication::exit(0); } void LSession::StartReboot(){ CleanupSession(); LOS::systemRestart(); QCoreApplication::exit(0); } void LSession::watcherChange(QString changed){ if(DEBUG){ qDebug() << "Session Watcher Change:" << changed; } if(changed.endsWith("fluxbox-init") || changed.endsWith("fluxbox-keys")){ refreshWindowManager(); } else if(changed.endsWith("sessionsettings.conf") ){ sessionsettings->sync(); emit SessionConfigChanged(); } else if(changed.endsWith("desktopsettings.conf") ){ emit DesktopConfigChanged(); } else if(changed == QDir::homePath()+"/Desktop"){ desktopFiles = QDir(QDir::homePath()+"/Desktop").entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs ,QDir::Name | QDir::IgnoreCase | QDir::DirsFirst); if(DEBUG){ qDebug() << "New Desktop Files:" << desktopFiles.length(); } emit DesktopFilesChanged(); } //Now double-check all the watches files to ensure that none of them got removed QStringList files = watcher->files(); if(files.length() < 5){ qDebug() << " - Resetting Watched Files..."; watcher->removePaths(files); //clear the current files before re-setting them watcher->addPath( QDir::homePath()+"/.lumina/LuminaDE/sessionsettings.conf" ); watcher->addPath( QDir::homePath()+"/.lumina/LuminaDE/desktopsettings.conf" ); watcher->addPath( QDir::homePath()+"/.lumina/fluxbox-init" ); watcher->addPath( QDir::homePath()+"/.lumina/fluxbox-keys" ); watcher->addPath( QDir::homePath()+"/Desktop"); } } void LSession::checkWindowGeoms(){ //Only do one window per run (this will be called once per new window - with time delays between) if(checkWin.isEmpty()){ return; } if(RunningApps.contains(checkWin[0]) ){ //just to make sure it did not close during the delay adjustWindowGeom( checkWin[0] ); } checkWin.removeAt(0); } void LSession::checkUserFiles(){ //internal version conversion examples: // [1.0.0 -> 1000000], [1.2.3 -> 1002003], [0.6.1 -> 6001] QString OVS = sessionsettings->value("DesktopVersion","0").toString(); //Old Version String int oldversion = VersionStringToNumber(OVS); bool newversion = ( oldversion < VersionStringToNumber(this->applicationVersion()) ); //increasing version number bool newrelease = ( OVS.contains("-devel", Qt::CaseInsensitive) && this->applicationVersion().contains("-release", Qt::CaseInsensitive) ); //Moving from devel to release //Check for the desktop settings file QString dset = QDir::homePath()+"/.lumina/LuminaDE/desktopsettings.conf"; bool firstrun = false; if(!QFile::exists(dset) || oldversion < 5000){ if( oldversion < 5000 ){ QFile::remove(dset); qDebug() << "Current desktop settings obsolete: Re-implementing defaults"; } else{ firstrun = true; } LUtils::LoadSystemDefaults(); } //Convert the favorites framework as necessary (change occured with 0.8.4) if(newversion || newrelease){ LUtils::upgradeFavorites(oldversion); } //Remove/convert any old desktop plugin files (Change occured with 0.8.5) // - TO-DO //Convert to the XDG autostart spec as necessary (Change occured with 0.8.5) if(QFile::exists(QDir::homePath()+"/.lumina/startapps") ){ QStringList cmds = LUtils::readFile(QDir::homePath()+"/.lumina/startapps"); for(int i=0; isetValue("DesktopVersion", this->applicationVersion()); } } void LSession::refreshWindowManager(){ WM->updateWM(); } void LSession::updateDesktops(){ //qDebug() << " - Update Desktops"; QDesktopWidget *DW = this->desktop(); bool firstrun = (DESKTOPS.length()==0); for(int i=0; iscreenCount(); i++){ bool found = false; for(int j=0; jScreen()==i || DW->screenGeometry(i)==DW->screenGeometry(DESKTOPS[j]->Screen()) ){ found = true; } } if(!found){ //Start the desktop on the new screen qDebug() << " - Start desktop on screen:" << i; if(firstrun && DW->screenGeometry(i).x()==0){ DESKTOPS << new LDesktop(i,true); //set this one as the default }else{ DESKTOPS << new LDesktop(i); } } } //qDebug() << " - Done Starting Desktops"; if(!firstrun){//Done right here on first run //Now go through and make sure to delete any desktops for detached screens for(int i=0; iScreen() >= DW->screenCount()){ qDebug() << " - Close desktop on screen:" << DESKTOPS[i]->Screen(); DESKTOPS[i]->prepareToClose(); delete DESKTOPS.takeAt(i); i--; }else{ qDebug() << " - Show desktop on screen:" << DESKTOPS[i]->Screen(); DESKTOPS[i]->show(); //QTimer::singleShot(0,DESKTOPS[i], SLOT(checkResolution())); } } QTimer::singleShot(1000,WM, SLOT(restartWM())); //Make sure fluxbox also gets prompted to re-load screen config } //Make sure all the background windows are registered on the system as virtual roots QTimer::singleShot(100,this, SLOT(registerDesktopWindows())); //qDebug() << " - Done Checking Desktops"; } void LSession::registerDesktopWindows(){ QList wins; for(int i=0; ibackgroundID(); } XCB->RegisterVirtualRoots(wins); } void LSession::adjustWindowGeom(WId win, bool maximize){ //Quick hack for making sure that new windows are not located underneath any panels // Get the window location QRect geom = XCB->WindowGeometry(win, false); //Get the frame size QList frame = XCB->WindowFrameGeometry(win); //[top,bottom,left,right] sizes of the frame //Calculate the full geometry (window + frame) QRect fgeom = QRect(geom.x()-frame[2], geom.y()-frame[0], geom.width()+frame[2]+frame[3], geom.height()+frame[0]+frame[1]); if(DEBUG){ qDebug() << "Check Window Geometry:" << XCB->WindowClass(win) << !geom.isNull() << geom << fgeom; } if(geom.isNull()){ return; } //Could not get geometry for some reason //Get the available geometry for the screen the window is on QRect desk; for(int i=0; idesktop()->screenGeometry(DESKTOPS[i]->Screen()).contains(geom.center()) ){ //Window is on this screen if(DEBUG){ qDebug() << " - On Screen:" << DESKTOPS[i]->Screen(); } desk = DESKTOPS[i]->availableScreenGeom(); if(DEBUG){ qDebug() << " - Screen Geom:" << desk; } break; } } //Adjust the window location if necessary if(maximize){ if(DEBUG){ qDebug() << " - Maximizing New Window:" << desk.width() << desk.height(); } geom = desk; //Use the full screen XCB->MoveResizeWindow(win, geom); XCB->MaximizeWindow(win, true); //directly set the appropriate "maximized" flags (bypassing WM) }else if(!desk.contains(fgeom) ){ //Adjust origin point for left/top margins if(fgeom.y() < desk.y()){ geom.moveTop(desk.y()+frame[0]); fgeom.moveTop(desk.y()); } //move down to the edge (top panel) if(fgeom.x() < desk.x()){ geom.moveLeft(desk.x()+frame[2]); fgeom.moveLeft(desk.x()); } //move right to the edge (left panel) //Adjust size for bottom margins (within reason, since window titles are on top normally) // if(geom.right() > desk.right() && (geom.width() > 100)){ geom.setRight(desk.right()); } if(fgeom.bottom() > desk.bottom() && geom.height() > 10){ int diff = fgeom.bottom()-desk.bottom(); //amount of overlap if( (fgeom.height()+ diff)< desk.height()){ //just move the window - there is room for it above geom.setBottom(desk.bottom()-frame[1]); fgeom.setBottom(desk.bottom()); }else{ //Need to resize the window - keeping the origin point the same geom.setHeight( geom.height()-diff-1 ); //shrink it by the difference (need an extra pixel somewhere) fgeom.setHeight( fgeom.height()-diff ); } } //Now move/resize the window if(DEBUG){ qDebug() << " - New Geom:" << geom << fgeom; } //Note: Fluxbox treats this weird, the origin point needs to be the total (frame included), // but the size needs to be the raw (no frame) value XCB->MoveResizeWindow(win, QRect(fgeom.topLeft(), geom.size()) ); } } void LSession::SessionEnding(){ stopSystemTray(); //just in case it was not stopped properly earlier } //=============== // SYSTEM ACCESS //=============== void LSession::LaunchApplication(QString cmd){ LSession::setOverrideCursor(QCursor(Qt::BusyCursor)); QProcess::startDetached(cmd); } QFileInfoList LSession::DesktopFiles(){ return desktopFiles; } AppMenu* LSession::applicationMenu(){ return appmenu; } SettingsMenu* LSession::settingsMenu(){ return settingsmenu; } QSettings* LSession::sessionSettings(){ return sessionsettings; } QSettings* LSession::DesktopPluginSettings(){ return DPlugSettings; } void LSession::systemWindow(){ if(sysWindow==0){ sysWindow = new SystemWindow(); } else{ sysWindow->updateWindow(); } sysWindow->show(); /*SystemWindow win; win.exec();*/ LSession::processEvents(); } //Play System Audio void LSession::playAudioFile(QString filepath){ //Setup the audio output systems for the desktop if(DEBUG){ qDebug() << "Play Audio File"; } if(mediaObj==0){ qDebug() << " - Initialize media player"; mediaObj = new QMediaPlayer(); } if(mediaObj !=0 ){ if(DEBUG){ qDebug() << " - starting playback:" << filepath; } mediaObj->setVolume(100); mediaObj->setMedia(QUrl::fromLocalFile(filepath)); mediaObj->play(); //if(!audioThread->isRunning()){ audioThread->start(); } LSession::processEvents(); } if(DEBUG){ qDebug() << " - Done with Audio File"; } } // ======================= // XCB EVENT FILTER FUNCTIONS // ======================= void LSession::WindowPropertyEvent(){ if(DEBUG){ qDebug() << "Window Property Event"; } QList newapps = XCB->WindowList(); if(RunningApps.length() < newapps.length()){ //New Window found LSession::restoreOverrideCursor(); //restore the mouse cursor back to normal (new window opened?) //Perform sanity checks on any new window geometries for(int i=0; i LSession::currentTrayApps(WId visualTray){ if(visualTray==VisualTrayID){ return RunningTrayApps; }else if( registerVisualTray(visualTray) ){ return RunningTrayApps; }else{ return QList(); } } void LSession::startSystemTray(){ if(SystemTrayID!=0){ return; } RunningTrayApps.clear(); //nothing running yet SystemTrayID = LX11::startSystemTray(0); TrayStopping = false; if(SystemTrayID!=0){ XSelectInput(QX11Info::display(), SystemTrayID, InputOutput); //make sure TrayID events get forwarded here XDamageQueryExtension( QX11Info::display(), &TrayDmgEvent, &TrayDmgError); evFilter->setTrayDamageFlag(TrayDmgEvent); qDebug() << "System Tray Started Successfully"; if(DEBUG){ qDebug() << " - System Tray Flags:" << TrayDmgEvent << TrayDmgError; } } } void LSession::stopSystemTray(bool detachall){ if(TrayStopping){ return; } //already run qDebug() << "Stopping system tray..."; TrayStopping = true; //make sure the internal list does not modified during this //Close all the running Tray Apps QList tmpApps = RunningTrayApps; RunningTrayApps.clear(); //clear this ahead of time so tray's do not attempt to re-access the apps if(!detachall){ for(int i=0; iWindowClass(tmpApps[i]); //XCB->CloseWindow(RunningTrayApps[i]); //Tray apps are special and closing the window does not close the app XCB->KillClient(tmpApps[i]); LSession::processEvents(); } } //Now close down the tray backend LX11::closeSystemTray(SystemTrayID); SystemTrayID = 0; TrayDmgEvent = 0; TrayDmgError = 0; evFilter->setTrayDamageFlag(0); //turn off tray event handling emit TrayListChanged(); LSession::processEvents(); } void LSession::attachTrayWindow(WId win){ //static int appnum = 0; if(TrayStopping){ return; } if(RunningTrayApps.contains(win)){ return; } //already managed RunningTrayApps << win; LSession::restoreOverrideCursor(); if(DEBUG){ qDebug() << "Tray List Changed"; } emit TrayListChanged(); } void LSession::removeTrayWindow(WId win){ if(SystemTrayID==0){ return; } for(int i=0; i