diff --git a/ksmserver/legacy.cpp b/ksmserver/legacy.cpp index 57c852ed8..b6c7e5c29 100644 --- a/ksmserver/legacy.cpp +++ b/ksmserver/legacy.cpp @@ -1,420 +1,395 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include "server.h" #include #include #include #include #include #include #include #include #include /* * Legacy session management */ #ifndef NO_LEGACY_SESSION_MANAGEMENT static WindowMap* windowMapPtr = nullptr; static Atom wm_save_yourself = XNone; static Atom wm_protocols = XNone; static Atom wm_client_leader = XNone; static Atom sm_client_id = XNone; static int winsErrorHandler(Display *, XErrorEvent *ev) { if (windowMapPtr) { WindowMap::Iterator it = windowMapPtr->find(ev->resourceid); if (it != windowMapPtr->end()) (*it).type = SM_ERROR; } return 0; } void KSMServer::performLegacySessionSave() { qCDebug(KSMSERVER) << "Saving legacy session apps"; if (state == ClosingSubSession) return; //FIXME implement later KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration(); // config may have changed in the KControl module KConfigGroup cg( config, "General" ); int wmSaveYourselfTimeout = cg.readEntry( "legacySaveTimeoutSecs", 4 ) * 1000; // Setup error handler legacyWindows.clear(); windowMapPtr = &legacyWindows; XErrorHandler oldHandler = XSetErrorHandler(winsErrorHandler); // Compute set of leader windows that need legacy session management // and determine which style (WM_COMMAND or WM_SAVE_YOURSELF) if( wm_save_yourself == (Atom)XNone ) { Atom atoms[ 4 ]; const char* const names[] = { "WM_SAVE_YOURSELF", "WM_PROTOCOLS", "WM_CLIENT_LEADER", "SM_CLIENT_ID" }; XInternAtoms( QX11Info::display(), const_cast< char** >( names ), 4, False, atoms ); wm_save_yourself = atoms[ 0 ]; wm_protocols = atoms[ 1 ]; wm_client_leader = atoms[ 2 ]; sm_client_id = atoms[ 3 ]; } const QList windows = KWindowSystem::windows(); for ( QList::ConstIterator it = windows.begin(); it != windows.end(); ++it) { WId leader = windowWmClientLeader( *it ); if (!legacyWindows.contains(leader) && windowSessionId( *it, leader ).isEmpty()) { SMType wtype = SM_WMCOMMAND; int nprotocols = 0; Atom *protocols = nullptr; if( XGetWMProtocols(QX11Info::display(), leader, &protocols, &nprotocols)) { for (int i=0; isave yourself< to legacy app " << (*it).wmclass1 << (*it).wmclass2; } } // Wait for change in WM_COMMAND with timeout XFlush(newdisplay); QElapsedTimer start; while (awaiting_replies > 0) { if (XPending(newdisplay)) { /* Process pending event */ XNextEvent(newdisplay, &ev); if ( ( ev.xany.type == UnmapNotify ) || ( ev.xany.type == PropertyNotify && ev.xproperty.atom == XA_WM_COMMAND ) ) { WindowMap::Iterator it = legacyWindows.find( ev.xany.window ); if ( it != legacyWindows.end() && (*it).type != SM_WMCOMMAND ) { awaiting_replies -= 1; if ( (*it).type != SM_ERROR ) (*it).type = SM_WMCOMMAND; } } } else { /* Check timeout */ int msecs = start.elapsed(); if (msecs >= wmSaveYourselfTimeout) { qCDebug(KSMSERVER) << "legacy timeout expired"; break; } /* Wait for more events */ fd_set fds; FD_ZERO(&fds); int fd = ConnectionNumber(newdisplay); FD_SET(fd, &fds); struct timeval tmwait; tmwait.tv_sec = (wmSaveYourselfTimeout - msecs) / 1000; tmwait.tv_usec = ((wmSaveYourselfTimeout - msecs) % 1000) * 1000; ::select(fd+1, &fds, nullptr, &fds, &tmwait); } } // Terminate work in new display XAllowEvents(newdisplay, ReplayPointer, CurrentTime); XAllowEvents(newdisplay, ReplayKeyboard, CurrentTime); XSync(newdisplay, False); XCloseDisplay(newdisplay); // Restore old error handler XSync(QX11Info::display(), False); XSetErrorHandler(oldHandler); for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) { if ( (*it).type != SM_ERROR) { WId w = it.key(); (*it).wmCommand = windowWmCommand(w); (*it).wmClientMachine = windowWmClientMachine(w); } } qCDebug(KSMSERVER) << "Done saving " << legacyWindows.count() << " legacy session apps"; } /*! Stores legacy session management data */ void KSMServer::storeLegacySession( KConfig* config ) { if (state == ClosingSubSession) return; //FIXME implement later // Write LegacySession data config->deleteGroup( QStringLiteral( "Legacy" ) + sessionGroup ); KConfigGroup group( config, QStringLiteral( "Legacy" ) + sessionGroup ); int count = 0; for (WindowMap::ConstIterator it = legacyWindows.constBegin(); it != legacyWindows.constEnd(); ++it) { if ( (*it).type != SM_ERROR) { if( excludeApps.contains( (*it).wmclass1.toLower()) || excludeApps.contains( (*it).wmclass2.toLower())) continue; if ( !(*it).wmCommand.isEmpty() && !(*it).wmClientMachine.isEmpty() ) { count++; QString n = QString::number(count); group.writeEntry( QStringLiteral("command")+n, (*it).wmCommand ); group.writeEntry( QStringLiteral("clientMachine")+n, (*it).wmClientMachine ); } } } group.writeEntry( "count", count ); } /*! Restores legacy session management data (i.e. restart applications) */ void KSMServer::restoreLegacySession( KConfig* config ) { if( config->hasGroup( QStringLiteral( "Legacy" ) + sessionGroup )) { KConfigGroup group( config, QStringLiteral( "Legacy" ) + sessionGroup ); restoreLegacySessionInternal( &group ); - } else if( wm == QLatin1String( "kwin" ) ) { // backwards comp. - get it from kwinrc - KConfigGroup group( config, sessionGroup ); - int count = group.readEntry( "count", 0 ); - for ( int i = 1; i <= count; i++ ) { - QString n = QString::number(i); - if ( !isWM( group.readEntry( QStringLiteral("program")+n, QString()))) - continue; - QStringList restartCommand = - group.readEntry( QStringLiteral("restartCommand")+n, QStringList() ); - for( QStringList::ConstIterator it = restartCommand.constBegin(); - it != restartCommand.constEnd(); - ++it ) { - if( (*it) == QLatin1String( "-session" ) ) { - ++it; - if( it != restartCommand.constEnd()) { - KConfig cfg( QStringLiteral( "session/" ) + wm + - QLatin1Char( '_' ) + (*it) ); - KConfigGroup group(&cfg, "LegacySession"); - restoreLegacySessionInternal( &group, ' ' ); - } - } - } - } } } void KSMServer::restoreLegacySessionInternal( KConfigGroup* config, char sep ) { int count = config->readEntry( "count",0 ); for ( int i = 1; i <= count; i++ ) { QString n = QString::number(i); - QStringList wmCommand = (sep == ',') ? + QStringList wmCommand = (sep == ',') ? // why is this named "wmCommand"? config->readEntry( QStringLiteral("command")+n, QStringList() ) : KShell::splitArgs( config->readEntry( QStringLiteral("command")+n, QString() ) ); // close enough(?) if( wmCommand.isEmpty()) continue; - if( isWM( wmCommand.first())) - continue; startApplication( wmCommand, config->readEntry( QStringLiteral("clientMachine")+n, QString() ), config->readEntry( QStringLiteral("userId")+n, QString() )); } } static QByteArray getQCStringProperty(WId w, Atom prop) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = nullptr; QByteArray result = ""; status = XGetWindowProperty( QX11Info::display(), w, prop, 0, 10000, false, XA_STRING, &type, &format, &nitems, &extra, &data ); if ( status == Success) { if( data ) result = (char*)data; XFree(data); } return result; } static QStringList getQStringListProperty(WId w, Atom prop) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = nullptr; QStringList result; status = XGetWindowProperty( QX11Info::display(), w, prop, 0, 10000, false, XA_STRING, &type, &format, &nitems, &extra, &data ); if ( status == Success) { if (!data) return result; for (int i=0; i<(int)nitems; i++) { result << QLatin1String( (const char*)data + i ); while(data[i]) i++; } XFree(data); } return result; } QStringList KSMServer::windowWmCommand(WId w) { QStringList ret = getQStringListProperty(w, XA_WM_COMMAND); // hacks here if( ret.count() == 1 ) { QString command = ret.first(); // Mozilla is launched using wrapper scripts, so it's launched using "mozilla", // but the actual binary is "mozilla-bin" or "/mozilla-bin", and that's what // will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though if( command.endsWith(QLatin1String( "mozilla-bin" ))) return QStringList() << QStringLiteral( "mozilla" ); if( command.endsWith(QLatin1String( "firefox-bin" ))) return QStringList() << QStringLiteral( "firefox" ); if( command.endsWith(QLatin1String( "thunderbird-bin" ))) return QStringList() << QStringLiteral( "thunderbird" ); if( command.endsWith(QLatin1String( "sunbird-bin" ))) return QStringList() << QStringLiteral( "sunbird" ); if( command.endsWith(QLatin1String( "seamonkey-bin" ))) return QStringList() << QStringLiteral( "seamonkey" ); } return ret; } QString KSMServer::windowWmClientMachine(WId w) { QByteArray result = getQCStringProperty(w, XA_WM_CLIENT_MACHINE); if (result.isEmpty()) { result = "localhost"; } else { // special name for the local machine (localhost) char hostnamebuf[80]; if (gethostname (hostnamebuf, sizeof hostnamebuf) >= 0) { hostnamebuf[sizeof(hostnamebuf)-1] = 0; if (result == hostnamebuf) result = "localhost"; if(char *dot = strchr(hostnamebuf, '.')) { *dot = '\0'; if(result == hostnamebuf) result = "localhost"; } } } return QLatin1String(result); } WId KSMServer::windowWmClientLeader(WId w) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = nullptr; Window result = w; status = XGetWindowProperty( QX11Info::display(), w, wm_client_leader, 0, 10000, false, XA_WINDOW, &type, &format, &nitems, &extra, &data ); if (status == Success ) { if (data && nitems > 0) result = *((Window*) data); XFree(data); } return result; } /* Returns sessionId for this client, taken either from its window or from the leader window. */ QByteArray KSMServer::windowSessionId(WId w, WId leader) { QByteArray result = getQCStringProperty(w, sm_client_id); if (result.isEmpty() && leader != (WId)None && leader != w) result = getQCStringProperty(leader, sm_client_id); return result; } #endif diff --git a/ksmserver/logout.cpp b/ksmserver/logout.cpp index c09358b56..0b45d2c68 100644 --- a/ksmserver/logout.cpp +++ b/ksmserver/logout.cpp @@ -1,709 +1,622 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include #include // HAVE_LIMITS_H #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIMITS_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "server.h" #include "global.h" #include "client.h" #include "logoutprompt_interface.h" #include "shutdown_interface.h" #include "kwinsession_interface.h" enum KWinSessionState { Normal = 0, Saving = 1, Quitting = 2 }; void KSMServer::logout( int confirm, int sdtype, int sdmode ) { // KDE5: remove me if (sdtype == KWorkSpace::ShutdownTypeLogout) sdtype = KWorkSpace::ShutdownTypeNone; shutdown( (KWorkSpace::ShutdownConfirm)confirm, (KWorkSpace::ShutdownType)sdtype, (KWorkSpace::ShutdownMode)sdmode ); } bool KSMServer::closeSession() { Q_ASSERT(calledFromDBus()); performLogout(); setDelayedReply(true); m_performLogoutCall = message(); return false; } bool KSMServer::canShutdown() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration(); // config may have changed in the KControl module KConfigGroup cg( config, "General"); return cg.readEntry( "offerShutdown", true ) && KDisplayManager().canShutdown(); } bool KSMServer::isShuttingDown() const { return state >= Shutdown; } //this method exists purely for compatibility void KSMServer::shutdown( KWorkSpace::ShutdownConfirm confirm, KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode ) { qCDebug(KSMSERVER) << "Shutdown called with confirm " << confirm << " type " << sdtype << " and mode " << sdmode; if( state >= Shutdown ) // already performing shutdown return; if( state != Idle ) // performing startup { return; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration(); // config may have changed in the KControl module KConfigGroup cg( config, "General"); bool logoutConfirmed = (confirm == KWorkSpace::ShutdownConfirmYes) ? false : (confirm == KWorkSpace::ShutdownConfirmNo) ? true : !cg.readEntry( "confirmLogout", true ); int shutdownType = (sdtype != KWorkSpace::ShutdownTypeDefault ? sdtype : cg.readEntry("shutdownType", (int)KWorkSpace::ShutdownType::ShutdownTypeLogout)); if (!logoutConfirmed) { OrgKdeLogoutPromptInterface logoutPrompt(QStringLiteral("org.kde.LogoutPrompt"), QStringLiteral("/LogoutPrompt"), QDBusConnection::sessionBus()); switch (shutdownType) { case KWorkSpace::ShutdownTypeHalt: logoutPrompt.promptShutDown(); break; case KWorkSpace::ShutdownTypeReboot: logoutPrompt.promptReboot(); break; case KWorkSpace::ShutdownTypeNone: Q_FALLTHROUGH(); default: logoutPrompt.promptLogout(); break; } } else { OrgKdeShutdownInterface shutdownIface(QStringLiteral("org.kde.Shutdown"), QStringLiteral("/Shutdown"), QDBusConnection::sessionBus()); switch (shutdownType) { case KWorkSpace::ShutdownTypeHalt: shutdownIface.logoutAndShutdown(); break; case KWorkSpace::ShutdownTypeReboot: shutdownIface.logoutAndReboot(); break; case KWorkSpace::ShutdownTypeNone: Q_FALLTHROUGH(); default: shutdownIface.logout(); break; } } } void KSMServer::performLogout() { if( state >= Shutdown ) { // already performing shutdown return; } if (state != Idle) { QTimer::singleShot(1000, this, &KSMServer::performLogout); } - auto reply = m_kwinInterface->setState(KWinSessionState::Saving); - // we don't need to block as we wait for kwin to handle it's session 1 - // before messaging the clients + auto setStateReply = m_kwinInterface->setState(KWinSessionState::Saving); state = Shutdown; // shall we save the session on logout? KConfigGroup cg(KSharedConfig::openConfig(), "General"); saveSession = ( cg.readEntry( "loginMode", QStringLiteral( "restorePreviousLogout" ) ) == QLatin1String( "restorePreviousLogout" ) ); qCDebug(KSMSERVER) << "saveSession is " << saveSession; if ( saveSession ) sessionGroup = QStringLiteral( "Session: " ) + QString::fromLocal8Bit( SESSION_PREVIOUS_LOGOUT ); // Set the real desktop background to black so that exit looks // clean regardless of what was on "our" desktop. QPalette palette; palette.setColor( QApplication::desktop()->backgroundRole(), Qt::black ); QApplication::setPalette(palette); - wmPhase1WaitingCount = 0; saveType = saveSession?SmSaveBoth:SmSaveGlobal; #ifndef NO_LEGACY_SESSION_MANAGEMENT performLegacySessionSave(); #endif startProtection(); - foreach( KSMClient* c, clients ) { - c->resetState(); - // Whoever came with the idea of phase 2 got it backwards - // unfortunately. Window manager should be the very first - // one saving session data, not the last one, as possible - // user interaction during session save may alter - // window positions etc. - // Moreover, KWin's focus stealing prevention would lead - // to undesired effects while session saving (dialogs - // wouldn't be activated), so it needs be assured that - // KWin will turn it off temporarily before any other - // user interaction takes place. - // Therefore, make sure the WM finishes its phase 1 - // before others a chance to change anything. - // KWin will check if the session manager is ksmserver, - // and if yes it will save in phase 1 instead of phase 2. - if( isWM( c ) ) - ++wmPhase1WaitingCount; + + // Tell KWin to start saving before we start tearing down clients + // as any "Save changes?" prompt might meddle with the state + if (saveSession) { + setStateReply.waitForFinished(); // do we have to wait for this to finish? + + qCDebug(KSMSERVER) << "Telling KWin we're about to save session" << currentSession(); + + auto saveSessionCall = m_kwinInterface->aboutToSaveSession(currentSession()); + // We need to wait for KWin to save the initial state, e.g. active client and + // current desktop before we signal any clients to quit. They might bring up + // "Save changes?" prompts altering the state. + // KWin doesn't talk to KSMServer directly anymore, so this won't deadlock. + saveSessionCall.waitForFinished(); } - if (wmPhase1WaitingCount > 0) { - foreach( KSMClient* c, clients ) { - if( isWM( c ) ) - SmsSaveYourself( c->connection(), saveType, - true, SmInteractStyleAny, false ); - } - } else { // no WM, simply start them all - foreach( KSMClient* c, clients ) - SmsSaveYourself( c->connection(), saveType, - true, SmInteractStyleAny, false ); + + const auto pendingClients = clients; + + for (KSMClient *c : pendingClients) { + c->resetState(); + + SmsSaveYourself(c->connection(), saveType, true, SmInteractStyleAny, false); } - qCDebug(KSMSERVER) << "clients should be empty, " << clients.isEmpty(); + + qCDebug(KSMSERVER) << "clients should be empty, " << clients.count(); + if ( clients.isEmpty() ) completeShutdownOrCheckpoint(); } void KSMServer::saveCurrentSession() { if ( state != Idle ) return; if ( currentSession().isEmpty() || currentSession() == QString::fromLocal8Bit( SESSION_PREVIOUS_LOGOUT ) ) sessionGroup = QLatin1String("Session: ") + QString::fromLocal8Bit( SESSION_BY_USER ); state = Checkpoint; - wmPhase1WaitingCount = 0; saveType = SmSaveLocal; saveSession = true; #ifndef NO_LEGACY_SESSION_MANAGEMENT performLegacySessionSave(); #endif - foreach( KSMClient* c, clients ) { - c->resetState(); - if( isWM( c ) ) - ++wmPhase1WaitingCount; - } - if (wmPhase1WaitingCount > 0) { - foreach( KSMClient* c, clients ) { - if( isWM( c ) ) - SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false ); - } - } else { - foreach( KSMClient* c, clients ) - SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false ); + + auto aboutToSaveCall = m_kwinInterface->aboutToSaveSession(currentSession()); + aboutToSaveCall.waitForFinished(); + + const auto pendingClients = clients; + for (KSMClient *c : pendingClients) { + SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false ); } if ( clients.isEmpty() ) completeShutdownOrCheckpoint(); } void KSMServer::saveCurrentSessionAs( const QString &session ) { if ( state != Idle ) return; sessionGroup = QStringLiteral( "Session: " ) + session; saveCurrentSession(); } // callbacks void KSMServer::saveYourselfDone( KSMClient* client, bool success ) { if ( state == Idle ) { // State saving when it's not shutdown or checkpoint. Probably // a shutdown was canceled and the client is finished saving // only now. Discard the saved state in order to avoid // the saved data building up. QStringList discard = client->discardCommand(); if( !discard.isEmpty()) executeCommand( discard ); return; } if ( success ) { client->saveYourselfDone = true; completeShutdownOrCheckpoint(); } else { // fake success to make KDE's logout not block with broken // apps. A perfect ksmserver would display a warning box at // the very end. client->saveYourselfDone = true; completeShutdownOrCheckpoint(); } startProtection(); - if( isWM( client ) && !client->wasPhase2 && wmPhase1WaitingCount > 0 ) { - --wmPhase1WaitingCount; - // WM finished its phase1, save the rest - if( wmPhase1WaitingCount == 0 ) { - foreach( KSMClient* c, clients ) - if( !isWM( c )) - SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal, - saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone, - false ); - } - } } void KSMServer::interactRequest( KSMClient* client, int /*dialogType*/ ) { if ( state == Shutdown || state == ClosingSubSession ) client->pendingInteraction = true; else SmsInteract( client->connection() ); handlePendingInteractions(); } void KSMServer::interactDone( KSMClient* client, bool cancelShutdown_ ) { if ( client != clientInteracting ) return; // should not happen clientInteracting = nullptr; if ( cancelShutdown_ ) cancelShutdown( client ); else handlePendingInteractions(); } void KSMServer::phase2Request( KSMClient* client ) { client->waitForPhase2 = true; client->wasPhase2 = true; completeShutdownOrCheckpoint(); - if( isWM( client ) && wmPhase1WaitingCount > 0 ) { - --wmPhase1WaitingCount; - // WM finished its phase1 and requests phase2, save the rest - if( wmPhase1WaitingCount == 0 ) { - foreach( KSMClient* c, clients ) - if( !isWM( c )) - SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal, - saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone, - false ); - } - } } void KSMServer::handlePendingInteractions() { if ( clientInteracting ) return; foreach( KSMClient* c, clients ) { if ( c->pendingInteraction ) { clientInteracting = c; c->pendingInteraction = false; break; } } if ( clientInteracting ) { endProtection(); SmsInteract( clientInteracting->connection() ); } else { startProtection(); } } void KSMServer::cancelShutdown( KSMClient* c ) { clientInteracting = nullptr; qCDebug(KSMSERVER) << state; if ( state == ClosingSubSession ) { clientsToKill.clear(); clientsToSave.clear(); emit subSessionCloseCanceled(); } else { qCDebug(KSMSERVER) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown."; KNotification::event( QStringLiteral( "cancellogout" ), i18n( "Logout canceled by '%1'", c->program()), QPixmap() , nullptr , KNotification::DefaultEvent ); foreach( KSMClient* c, clients ) { SmsShutdownCancelled( c->connection() ); if( c->saveYourselfDone ) { // Discard also saved state. QStringList discard = c->discardCommand(); if( !discard.isEmpty()) executeCommand( discard ); } } } state = Idle; m_kwinInterface->setState(KWinSessionState::Normal); if (m_performLogoutCall.type() == QDBusMessage::MethodCallMessage) { auto reply = m_performLogoutCall.createReply(false); QDBusConnection::sessionBus().send(reply); m_performLogoutCall = QDBusMessage(); } emit logoutCancelled(); } void KSMServer::startProtection() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration(); // config may have changed in the KControl module KConfigGroup cg( config, "General" ); int timeout = cg.readEntry( "clientShutdownTimeoutSecs", 15 ) * 1000; protectionTimer.setSingleShot( true ); protectionTimer.start( timeout ); } void KSMServer::endProtection() { protectionTimer.stop(); } /* Internal protection slot, invoked when clients do not react during shutdown. */ void KSMServer::protectionTimeout() { if ( ( state != Shutdown && state != Checkpoint && state != ClosingSubSession ) || clientInteracting ) return; foreach( KSMClient* c, clients ) { if ( !c->saveYourselfDone && !c->waitForPhase2 ) { qCDebug(KSMSERVER) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")"; c->saveYourselfDone = true; } } completeShutdownOrCheckpoint(); startProtection(); } void KSMServer::completeShutdownOrCheckpoint() { qCDebug(KSMSERVER) << "completeShutdownOrCheckpoint called"; if ( state != Shutdown && state != Checkpoint && state != ClosingSubSession ) return; QList pendingClients; if (state == ClosingSubSession) pendingClients = clientsToSave; else pendingClients = clients; foreach( KSMClient* c, pendingClients ) { if ( !c->saveYourselfDone && !c->waitForPhase2 ) return; // not done yet } // do phase 2 bool waitForPhase2 = false; foreach( KSMClient* c, pendingClients ) { if ( !c->saveYourselfDone && c->waitForPhase2 ) { c->waitForPhase2 = false; SmsSaveYourselfPhase2( c->connection() ); waitForPhase2 = true; } } if ( waitForPhase2 ) return; if ( saveSession ) storeSession(); else discardSession(); qCDebug(KSMSERVER) << "state is " << state; if ( state == Shutdown ) { KNotification *n = KNotification::event(QStringLiteral("exitkde"), QString(), QPixmap(), nullptr, KNotification::DefaultEvent); // Plasma says good bye connect(n, &KNotification::closed, this, &KSMServer::startKilling); state = WaitingForKNotify; // https://bugs.kde.org/show_bug.cgi?id=228005 // if sound is not working for some reason (e.g. no phonon // backends are installed) the closed() signal never happens // and logoutSoundFinished() never gets called. Add this timer to make // sure the shutdown procedure continues even if sound system is broken. QTimer::singleShot(5000, this, [=]{ if (state == WaitingForKNotify) { n->deleteLater(); startKilling(); } }); } else if ( state == Checkpoint ) { foreach( KSMClient* c, clients ) { SmsSaveComplete( c->connection()); } state = Idle; } else { //ClosingSubSession startKillingSubSession(); } } void KSMServer::startKilling() { qCDebug(KSMSERVER) << "Starting killing clients"; if (state == Killing) { // we are already killing return; } // kill all clients state = Killing; m_kwinInterface->setState(KWinSessionState::Quitting); foreach( KSMClient* c, clients ) { - if( isWM( c )) // kill the WM as the last one in order to reduce flicker - continue; qCDebug(KSMSERVER) << "startKilling: client " << c->program() << "(" << c->clientId() << ")"; SmsDie( c->connection() ); } qCDebug(KSMSERVER) << " We killed all clients. We have now clients.count()=" << clients.count() << endl; completeKilling(); QTimer::singleShot( 10000, this, &KSMServer::timeoutQuit ); } void KSMServer::completeKilling() { qCDebug(KSMSERVER) << "KSMServer::completeKilling clients.count()=" << clients.count() << endl; if( state == Killing ) { bool wait = false; foreach( KSMClient* c, clients ) { - if( isWM( c )) - continue; wait = true; // still waiting for clients to go away } if( wait ) return; - killWM(); - } -} - -void KSMServer::killWM() -{ - if( state != Killing ) - return; - - qCDebug(KSMSERVER) << "Starting killing WM"; - state = KillingWM; - bool iswm = false; - foreach( KSMClient* c, clients ) { - if( isWM( c )) { - iswm = true; - qCDebug(KSMSERVER) << "killWM: client " << c->program() << "(" << c->clientId() << ")"; - SmsDie( c->connection() ); - } - } - if( iswm ) { - completeKillingWM(); - QTimer::singleShot( 5000, this, &KSMServer::timeoutWMQuit ); - } - else killingCompleted(); -} - -void KSMServer::completeKillingWM() -{ - qCDebug(KSMSERVER) << "KSMServer::completeKillingWM clients.count()=" << - clients.count() << endl; - if( state == KillingWM ) { - if( clients.isEmpty()) - killingCompleted(); } } // shutdown is fully complete void KSMServer::killingCompleted() { if (m_performLogoutCall.type() == QDBusMessage::MethodCallMessage) { auto reply = m_performLogoutCall.createReply(true); QDBusConnection::sessionBus().send(reply); m_performLogoutCall = QDBusMessage(); } qApp->quit(); } void KSMServer::timeoutQuit() { foreach( KSMClient* c, clients ) { qCWarning(KSMSERVER) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")" ; } - killWM(); -} - -void KSMServer::timeoutWMQuit() -{ - if( state == KillingWM ) { - qCWarning(KSMSERVER) << "SmsDie WM timeout" ; - } killingCompleted(); } void KSMServer::saveSubSession(const QString &name, QStringList saveAndClose, QStringList saveOnly) { if( state != Idle ) { // performing startup qCDebug(KSMSERVER) << "not idle!" << state; return; } qCDebug(KSMSERVER) << name << saveAndClose << saveOnly; state = ClosingSubSession; saveType = SmSaveBoth; //both or local? what oes it mean? saveSession = true; sessionGroup = QStringLiteral( "SubSession: " ) + name; #ifndef NO_LEGACY_SESSION_MANAGEMENT //performLegacySessionSave(); FIXME #endif startProtection(); foreach( KSMClient* c, clients ) { if (saveAndClose.contains(QString::fromLocal8Bit(c->clientId()))) { c->resetState(); SmsSaveYourself( c->connection(), saveType, true, SmInteractStyleAny, false ); clientsToSave << c; clientsToKill << c; } else if (saveOnly.contains(QString::fromLocal8Bit(c->clientId()))) { c->resetState(); SmsSaveYourself( c->connection(), saveType, true, SmInteractStyleAny, false ); clientsToSave << c; } } completeShutdownOrCheckpoint(); } void KSMServer::startKillingSubSession() { qCDebug(KSMSERVER) << "Starting killing clients"; // kill all clients state = KillingSubSession; foreach( KSMClient* c, clientsToKill ) { qCDebug(KSMSERVER) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")"; SmsDie( c->connection() ); } qCDebug(KSMSERVER) << " We killed some clients. We have now clients.count()=" << clients.count() << endl; completeKillingSubSession(); QTimer::singleShot( 10000, this, &KSMServer::signalSubSessionClosed ); } void KSMServer::completeKillingSubSession() { qCDebug(KSMSERVER) << "KSMServer::completeKillingSubSession clients.count()=" << clients.count() << endl; if( state == KillingSubSession ) { - bool wait = false; - foreach( KSMClient* c, clientsToKill ) { - if( isWM( c )) - continue; - wait = true; // still waiting for clients to go away + if (!clientsToKill.isEmpty()) { + return; // still waiting for clients to go away } - if( wait ) - return; signalSubSessionClosed(); } } void KSMServer::signalSubSessionClosed() { if( state != KillingSubSession ) return; clientsToKill.clear(); clientsToSave.clear(); //TODO tell the subSession manager the close request was carried out //so that plasma can close its stuff state = Idle; qCDebug(KSMSERVER) << state; emit subSessionClosed(); } diff --git a/ksmserver/main.cpp b/ksmserver/main.cpp index dcc911268..601625aee 100644 --- a/ksmserver/main.cpp +++ b/ksmserver/main.cpp @@ -1,341 +1,334 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "server.h" #include #include #include #include #include static const char version[] = "0.4"; static const char description[] = I18N_NOOP( "The reliable Plasma session manager that talks the standard X11R6 \nsession management protocol (XSMP)." ); Display* dpy = nullptr; Colormap colormap = 0; Visual *visual = nullptr; extern KSMServer* the_server; void IoErrorHandler ( IceConn iceConn) { the_server->ioError( iceConn ); } bool writeTest(QByteArray path) { path += "/XXXXXX"; int fd = mkstemp(path.data()); if (fd == -1) return false; if (write(fd, "Hello World\n", 12) == -1) { int save_errno = errno; close(fd); unlink(path.data()); errno = save_errno; return false; } close(fd); unlink(path.data()); return true; } void checkComposite() { if( qgetenv( "KDE_SKIP_ARGB_VISUALS" ) == "1" ) return; // thanks to zack rusin and frederik for pointing me in the right direction // for the following bits of X11 code dpy = XOpenDisplay(nullptr); // open default display if (!dpy) { qCCritical(KSMSERVER) << "Cannot connect to the X server"; return; } int screen = DefaultScreen(dpy); int eventBase, errorBase; if (XRenderQueryExtension(dpy, &eventBase, &errorBase)) { int nvi; XVisualInfo templ; templ.screen = screen; templ.depth = 32; templ.c_class = TrueColor; XVisualInfo *xvi = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &templ, &nvi); for (int i = 0; i < nvi; ++i) { XRenderPictFormat *format = XRenderFindVisualFormat(dpy, xvi[i].visual); if (format->type == PictTypeDirect && format->direct.alphaMask) { visual = xvi[i].visual; colormap = XCreateColormap(dpy, RootWindow(dpy, screen), visual, AllocNone); XFree(xvi); return; } } XFree(xvi); } XCloseDisplay( dpy ); dpy = nullptr; } void sanity_check( int argc, char* argv[] ) { QString msg; QByteArray path = qgetenv("HOME"); const QByteArray readOnly = qgetenv("KDE_HOME_READONLY"); if (path.isEmpty()) { msg = i18n("$HOME not set!"); } if (msg.isEmpty() && access(path.data(), W_OK)) { if (errno == ENOENT) msg = i18n("$HOME directory (%1) does not exist.", QFile::decodeName(path)); else if (readOnly.isEmpty()) msg = i18n("No write access to $HOME directory (%1).", QFile::decodeName(path)); } if (msg.isEmpty() && access(path.data(), R_OK)) { if (errno == ENOENT) msg = i18n("$HOME directory (%1) does not exist.", QFile::decodeName(path)); else msg = i18n("No read access to $HOME directory (%1).", QFile::decodeName(path)); } if (msg.isEmpty() && readOnly.isEmpty() && !writeTest(path)) { if (errno == ENOSPC) msg = i18n("$HOME directory (%1) is out of disk space.", QFile::decodeName(path)); else msg = i18n("Writing to the $HOME directory (%2) failed with " "the error '%1'", QString::fromLocal8Bit(strerror(errno)), QFile::decodeName(path)); } if (msg.isEmpty()) { path = getenv("ICEAUTHORITY"); if (path.isEmpty()) { path = qgetenv("HOME"); path += "/.ICEauthority"; } if (access(path.data(), W_OK) && (errno != ENOENT)) msg = i18n("No write access to '%1'.", QFile::decodeName(path)); else if (access(path.data(), R_OK) && (errno != ENOENT)) msg = i18n("No read access to '%1'.", QFile::decodeName(path)); } if (msg.isEmpty()) { path = getenv("KDETMP"); if (path.isEmpty()) path = "/tmp"; if (!writeTest(path)) { if (errno == ENOSPC) msg = i18n("Temp directory (%1) is out of disk space.", QFile::decodeName(path)); else msg = i18n("Writing to the temp directory (%2) failed with\n " "the error '%1'", QString::fromLocal8Bit(strerror(errno)), QFile::decodeName(path)); } } if (msg.isEmpty() && (path != "/tmp")) { path = "/tmp"; if (!writeTest(path)) { if (errno == ENOSPC) msg = i18n("Temp directory (%1) is out of disk space.", QFile::decodeName(path)); else msg = i18n("Writing to the temp directory (%2) failed with\n " "the error '%1'", QString::fromLocal8Bit(strerror(errno)), QFile::decodeName(path)); } } if (msg.isEmpty()) { path += "/.ICE-unix"; if (access(path.data(), W_OK) && (errno != ENOENT)) msg = i18n("No write access to '%1'.", QFile::decodeName(path)); else if (access(path.data(), R_OK) && (errno != ENOENT)) msg = i18n("No read access to '%1'.", QFile::decodeName(path)); } if (!msg.isEmpty()) { const QString msg_pre = i18n("The following installation problem was detected\n" "while trying to start Plasma:") + QStringLiteral("\n\n "); const QString msg_post = i18n("\n\nPlasma is unable to start.\n"); fputs(msg_pre.toUtf8().constData(), stderr); fprintf(stderr, "%s", msg.toUtf8().constData()); fputs(msg_post.toUtf8().constData(), stderr); QApplication a(argc, argv); const QString qmsg = msg_pre + msg + msg_post; KMessageBox::error(nullptr, qmsg, i18n("Plasma Workspace installation problem!")); exit(255); } } int main(int argc, char* argv[]) { sanity_check(argc, argv); putenv((char*)"SESSION_MANAGER="); checkComposite(); // force xcb QPA plugin as ksmserver is very X11 specific const QByteArray origQpaPlatform = qgetenv("QT_QPA_PLATFORM"); qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("xcb")); QQuickWindow::setDefaultAlphaBuffer(true); QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); QApplication *a = new QApplication(argc, argv); // now the QPA platform is set, unset variable again to not launch apps with incorrect environment if (origQpaPlatform.isEmpty()) { qunsetenv("QT_QPA_PLATFORM"); } else { qputenv("QT_QPA_PLATFORM", origQpaPlatform); } QApplication::setApplicationName( QStringLiteral( "ksmserver") ); QApplication::setApplicationVersion( QString::fromLatin1( version ) ); QApplication::setOrganizationDomain( QStringLiteral( "kde.org") ); fcntl(ConnectionNumber(QX11Info::display()), F_SETFD, 1); a->setQuitOnLastWindowClosed(false); // #169486 QCommandLineParser parser; parser.setApplicationDescription(i18n(description)); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption restoreOption(QStringList() << QStringLiteral("r") << QStringLiteral("restore"), i18n("Restores the saved user session if available")); parser.addOption(restoreOption); - QCommandLineOption wmOption(QStringList() << QStringLiteral("w") << QStringLiteral("windowmanager"), - i18n("Starts in case no other window manager is \nparticipating in the session. Default is 'kwin'"), - i18n("wm")); - parser.addOption(wmOption); - QCommandLineOption nolocalOption(QStringLiteral("nolocal"), i18n("Also allow remote connections")); parser.addOption(nolocalOption); QCommandLineOption lockscreenOption(QStringLiteral("lockscreen"), i18n("Starts the session in locked mode")); parser.addOption(lockscreenOption); QCommandLineOption noLockscreenOption(QStringLiteral("no-lockscreen"), i18n("Starts without lock screen support. Only needed if other component provides the lock screen.")); parser.addOption(noLockscreenOption); parser.process(*a); - QString wm = parser.value(wmOption); - bool only_local = !parser.isSet(nolocalOption); #ifndef HAVE__ICETRANSNOLISTEN /* this seems strange, but the default is only_local, so if !only_local * the option --nolocal was given, and we warn (the option --nolocal * does nothing on this platform, as here the default is reversed) */ if (!only_local) { qCWarning(KSMSERVER, "--nolocal is not supported on your platform. Sorry."); } only_local = false; #endif KSMServer::InitFlags flags = KSMServer::InitFlag::None; if (only_local) { flags |= KSMServer::InitFlag::OnlyLocal; } if (parser.isSet(lockscreenOption)) { flags |= KSMServer::InitFlag::ImmediateLockScreen; } if (parser.isSet(noLockscreenOption)) { flags |= KSMServer::InitFlag::NoLockScreen; } - KSMServer *server = new KSMServer( wm, flags); + KSMServer *server = new KSMServer(flags); // for the KDE-already-running check in startkde KSelectionOwner kde_running( "_KDE_RUNNING", 0 ); kde_running.claim( false ); IceSetIOErrorHandler( IoErrorHandler ); KConfigGroup config(KSharedConfig::openConfig(), "General"); QString loginMode = config.readEntry( "loginMode", "restorePreviousLogout" ); if ( parser.isSet( restoreOption )) server->restoreSession( QStringLiteral( SESSION_BY_USER ) ); else if ( loginMode == QLatin1String( "restorePreviousLogout" ) ) server->restoreSession( QStringLiteral( SESSION_PREVIOUS_LOGOUT ) ); else if ( loginMode == QLatin1String( "restoreSavedSession" ) ) server->restoreSession( QStringLiteral( SESSION_BY_USER ) ); else server->startDefaultSession(); KDBusService service(KDBusService::Unique); server->setupShortcuts(); int ret = a->exec(); kde_running.release(); // needs to be done before QApplication destruction delete a; return ret; } diff --git a/ksmserver/org.kde.KWin.Session.xml b/ksmserver/org.kde.KWin.Session.xml index 795e4dbfc..e0f303826 100644 --- a/ksmserver/org.kde.KWin.Session.xml +++ b/ksmserver/org.kde.KWin.Session.xml @@ -1,14 +1,23 @@ - + + - + - - - - + + + + + + + + + + + + diff --git a/ksmserver/server.cpp b/ksmserver/server.cpp index 5fcf90633..0fcb95068 100644 --- a/ksmserver/server.cpp +++ b/ksmserver/server.cpp @@ -1,1249 +1,1158 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "server.h" #include "global.h" #include "client.h" #include "ksmserver_debug.h" #include "ksmserverinterfaceadaptor.h" #include "klocalizedstring.h" #include "kglobalaccel.h" #include #include // HAVE_LIMITS_H #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIMITS_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kscreenlocker_interface.h" #include "kwinsession_interface.h" KSMServer* the_server = nullptr; KSMServer* KSMServer::self() { return the_server; } /*! Utility function to execute a command on the local machine. Used * to restart applications. */ KProcess* KSMServer::startApplication( const QStringList& cmd, const QString& clientMachine, const QString& userId, bool wm ) { QStringList command = cmd; if ( command.isEmpty() ) return nullptr; if ( !userId.isEmpty()) { struct passwd* pw = getpwuid( getuid()); if( pw != nullptr && userId != QString::fromLocal8Bit( pw->pw_name )) { command.prepend( QStringLiteral("--") ); command.prepend( userId ); command.prepend( QStringLiteral("-u") ); command.prepend( QStandardPaths::findExecutable(QStringLiteral("kdesu"))); } } if ( !clientMachine.isEmpty() && clientMachine != QLatin1String("localhost") ) { command.prepend( clientMachine ); command.prepend( xonCommand ); // "xon" by default } // TODO this function actually should not use KProcess at all and use klauncher (kdeinit) instead. // Klauncher should also have support for tracking whether the launched process is still alive // or not, so this should be redone. For now, use KProcess for wm's, as they need to be tracked, // klauncher for the rest where ksmserver doesn't care. if( wm ) { KProcess* process = new KProcess( this ); *process << command; // make it auto-delete connect(process, static_cast(&KProcess::error), process, &KProcess::deleteLater); connect(process, static_cast(&KProcess::finished), process, &KProcess::deleteLater); process->start(); return process; } else { int n = command.count(); org::kde::KLauncher klauncher(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QDBusConnection::sessionBus()); QString app = command[0]; QStringList argList; for ( int i=1; i < n; i++) argList.append( command[i]); klauncher.exec_blind(app, argList ); return nullptr; } } /*! Utility function to execute a command on the local machine. Used * to discard session data */ void KSMServer::executeCommand( const QStringList& command ) { if ( command.isEmpty() ) return; KProcess::execute( command ); } IceAuthDataEntry *authDataEntries = nullptr; static QTemporaryFile *remTempFile = nullptr; static IceListenObj *listenObjs = nullptr; int numTransports = 0; static bool only_local = 0; static Bool HostBasedAuthProc ( char* /*hostname*/) { if (only_local) return true; else return false; } Status KSMRegisterClientProc ( SmsConn /* smsConn */, SmPointer managerData, char * previousId ) { KSMClient* client = (KSMClient*) managerData; client->registerClient( previousId ); return 1; } void KSMInteractRequestProc ( SmsConn /* smsConn */, SmPointer managerData, int dialogType ) { the_server->interactRequest( (KSMClient*) managerData, dialogType ); } void KSMInteractDoneProc ( SmsConn /* smsConn */, SmPointer managerData, Bool cancelShutdown ) { the_server->interactDone( (KSMClient*) managerData, cancelShutdown ); } void KSMSaveYourselfRequestProc ( SmsConn smsConn , SmPointer /* managerData */, int saveType, Bool shutdown, int interactStyle, Bool fast, Bool global ) { if ( shutdown ) { the_server->shutdown( fast ? KWorkSpace::ShutdownConfirmNo : KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeDefault, KWorkSpace::ShutdownModeDefault ); } else if ( !global ) { SmsSaveYourself( smsConn, saveType, false, interactStyle, fast ); SmsSaveComplete( smsConn ); } // else checkpoint only, ksmserver does not yet support this // mode. Will come for KDE 3.1 } void KSMSaveYourselfPhase2RequestProc ( SmsConn /* smsConn */, SmPointer managerData ) { the_server->phase2Request( (KSMClient*) managerData ); } void KSMSaveYourselfDoneProc ( SmsConn /* smsConn */, SmPointer managerData, Bool success ) { the_server->saveYourselfDone( (KSMClient*) managerData, success ); } void KSMCloseConnectionProc ( SmsConn smsConn, SmPointer managerData, int count, char ** reasonMsgs ) { the_server->deleteClient( ( KSMClient* ) managerData ); if ( count ) SmFreeReasons( count, reasonMsgs ); IceConn iceConn = SmsGetIceConnection( smsConn ); SmsCleanUp( smsConn ); IceSetShutdownNegotiation (iceConn, False); IceCloseConnection( iceConn ); } void KSMSetPropertiesProc ( SmsConn /* smsConn */, SmPointer managerData, int numProps, SmProp ** props ) { KSMClient* client = ( KSMClient* ) managerData; for ( int i = 0; i < numProps; i++ ) { SmProp *p = client->property( props[i]->name ); if ( p ) { client->properties.removeAll( p ); SmFreeProperty( p ); } client->properties.append( props[i] ); - if ( !qstrcmp( props[i]->name, SmProgram ) ) - the_server->clientSetProgram( client ); } if ( numProps ) free( props ); } void KSMDeletePropertiesProc ( SmsConn /* smsConn */, SmPointer managerData, int numProps, char ** propNames ) { KSMClient* client = ( KSMClient* ) managerData; for ( int i = 0; i < numProps; i++ ) { SmProp *p = client->property( propNames[i] ); if ( p ) { client->properties.removeAll( p ); SmFreeProperty( p ); } } } void KSMGetPropertiesProc ( SmsConn smsConn, SmPointer managerData ) { KSMClient* client = ( KSMClient* ) managerData; SmProp** props = new SmProp*[client->properties.count()]; int i = 0; foreach( SmProp *prop, client->properties ) props[i++] = prop; SmsReturnProperties( smsConn, i, props ); delete [] props; } class KSMListener : public QSocketNotifier { public: KSMListener( IceListenObj obj ) : QSocketNotifier( IceGetListenConnectionNumber( obj ), QSocketNotifier::Read ) { listenObj = obj; } IceListenObj listenObj; }; class KSMConnection : public QSocketNotifier { public: KSMConnection( IceConn conn ) : QSocketNotifier( IceConnectionNumber( conn ), QSocketNotifier::Read ) { iceConn = conn; } IceConn iceConn; }; /* for printing hex digits */ static void fprintfhex (FILE *fp, unsigned int len, char *cp) { static const char hexchars[] = "0123456789abcdef"; for (; len > 0; len--, cp++) { unsigned char s = *cp; putc(hexchars[s >> 4], fp); putc(hexchars[s & 0x0f], fp); } } /* * We use temporary files which contain commands to add/remove entries from * the .ICEauthority file. */ static void write_iceauth (FILE *addfp, FILE *removefp, IceAuthDataEntry *entry) { fprintf (addfp, "add %s \"\" %s %s ", entry->protocol_name, entry->network_id, entry->auth_name); fprintfhex (addfp, entry->auth_data_length, entry->auth_data); fprintf (addfp, "\n"); fprintf (removefp, "remove protoname=%s protodata=\"\" netid=%s authname=%s\n", entry->protocol_name, entry->network_id, entry->auth_name); } #define MAGIC_COOKIE_LEN 16 Status SetAuthentication_local (int count, IceListenObj *listenObjs) { int i; for (i = 0; i < count; i ++) { char *prot = IceGetListenConnectionString(listenObjs[i]); if (!prot) continue; char *host = strchr(prot, '/'); char *sock = nullptr; if (host) { *host=0; host++; sock = strchr(host, ':'); if (sock) { *sock = 0; sock++; } } qCDebug(KSMSERVER) << "KSMServer: SetAProc_loc: conn " << (unsigned)i << ", prot=" << prot << ", file=" << sock; if (sock && !strcmp(prot, "local")) { chmod(sock, 0700); } IceSetHostBasedAuthProc (listenObjs[i], HostBasedAuthProc); free(prot); } return 1; } Status SetAuthentication (int count, IceListenObj *listenObjs, IceAuthDataEntry **authDataEntries) { QTemporaryFile addTempFile; remTempFile = new QTemporaryFile; if (!addTempFile.open() || !remTempFile->open()) return 0; if ((*authDataEntries = (IceAuthDataEntry *) malloc ( count * 2 * sizeof (IceAuthDataEntry))) == nullptr) return 0; FILE *addAuthFile = fopen(QFile::encodeName(addTempFile.fileName()).constData(), "r+"); FILE *remAuthFile = fopen(QFile::encodeName(remTempFile->fileName()).constData(), "r+"); for (int i = 0; i < numTransports * 2; i += 2) { (*authDataEntries)[i].network_id = IceGetListenConnectionString (listenObjs[i/2]); (*authDataEntries)[i].protocol_name = (char *) "ICE"; (*authDataEntries)[i].auth_name = (char *) "MIT-MAGIC-COOKIE-1"; (*authDataEntries)[i].auth_data = IceGenerateMagicCookie (MAGIC_COOKIE_LEN); (*authDataEntries)[i].auth_data_length = MAGIC_COOKIE_LEN; (*authDataEntries)[i+1].network_id = IceGetListenConnectionString (listenObjs[i/2]); (*authDataEntries)[i+1].protocol_name = (char *) "XSMP"; (*authDataEntries)[i+1].auth_name = (char *) "MIT-MAGIC-COOKIE-1"; (*authDataEntries)[i+1].auth_data = IceGenerateMagicCookie (MAGIC_COOKIE_LEN); (*authDataEntries)[i+1].auth_data_length = MAGIC_COOKIE_LEN; write_iceauth (addAuthFile, remAuthFile, &(*authDataEntries)[i]); write_iceauth (addAuthFile, remAuthFile, &(*authDataEntries)[i+1]); IceSetPaAuthData (2, &(*authDataEntries)[i]); IceSetHostBasedAuthProc (listenObjs[i/2], HostBasedAuthProc); } fclose(addAuthFile); fclose(remAuthFile); QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); if (iceAuth.isEmpty()) { qCWarning(KSMSERVER, "KSMServer: could not find iceauth"); return 0; } KProcess p; p << iceAuth << QStringLiteral("source") << addTempFile.fileName(); p.execute(); return (1); } /* * Free up authentication data. */ void FreeAuthenticationData(int count, IceAuthDataEntry *authDataEntries) { /* Each transport has entries for ICE and XSMP */ if (only_local) return; for (int i = 0; i < count * 2; i++) { free (authDataEntries[i].network_id); free (authDataEntries[i].auth_data); } free (authDataEntries); QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); if (iceAuth.isEmpty()) { qCWarning(KSMSERVER, "KSMServer: could not find iceauth"); return; } if (remTempFile) { KProcess p; p << iceAuth << QStringLiteral("source") << remTempFile->fileName(); p.execute(); } delete remTempFile; remTempFile = nullptr; } static int Xio_ErrorHandler( Display * ) { qCWarning(KSMSERVER, "ksmserver: Fatal IO error: client killed"); // Don't do anything that might require the X connection if (the_server) { KSMServer *server = the_server; the_server = nullptr; server->cleanUp(); // Don't delete server!! } exit(0); // Don't report error, it's not our fault. return 0; // Bogus return value, notreached } void KSMServer::setupXIOErrorHandler() { XSetIOErrorHandler(Xio_ErrorHandler); } static int wake_up_socket = -1; static void sighandler(int sig) { if (sig == SIGHUP) { signal(SIGHUP, sighandler); return; } char ch = 0; (void)::write(wake_up_socket, &ch, 1); } void KSMWatchProc ( IceConn iceConn, IcePointer client_data, Bool opening, IcePointer* watch_data) { KSMServer* ds = ( KSMServer*) client_data; if (opening) { *watch_data = (IcePointer) ds->watchConnection( iceConn ); } else { ds->removeConnection( (KSMConnection*) *watch_data ); } } static Status KSMNewClientProc ( SmsConn conn, SmPointer manager_data, unsigned long* mask_ret, SmsCallbacks* cb, char** failure_reason_ret) { *failure_reason_ret = nullptr; void* client = ((KSMServer*) manager_data )->newClient( conn ); cb->register_client.callback = KSMRegisterClientProc; cb->register_client.manager_data = client; cb->interact_request.callback = KSMInteractRequestProc; cb->interact_request.manager_data = client; cb->interact_done.callback = KSMInteractDoneProc; cb->interact_done.manager_data = client; cb->save_yourself_request.callback = KSMSaveYourselfRequestProc; cb->save_yourself_request.manager_data = client; cb->save_yourself_phase2_request.callback = KSMSaveYourselfPhase2RequestProc; cb->save_yourself_phase2_request.manager_data = client; cb->save_yourself_done.callback = KSMSaveYourselfDoneProc; cb->save_yourself_done.manager_data = client; cb->close_connection.callback = KSMCloseConnectionProc; cb->close_connection.manager_data = client; cb->set_properties.callback = KSMSetPropertiesProc; cb->set_properties.manager_data = client; cb->delete_properties.callback = KSMDeletePropertiesProc; cb->delete_properties.manager_data = client; cb->get_properties.callback = KSMGetPropertiesProc; cb->get_properties.manager_data = client; *mask_ret = SmsRegisterClientProcMask | SmsInteractRequestProcMask | SmsInteractDoneProcMask | SmsSaveYourselfRequestProcMask | SmsSaveYourselfP2RequestProcMask | SmsSaveYourselfDoneProcMask | SmsCloseConnectionProcMask | SmsSetPropertiesProcMask | SmsDeletePropertiesProcMask | SmsGetPropertiesProcMask; return 1; } #ifdef HAVE__ICETRANSNOLISTEN extern "C" int _IceTransNoListen(const char * protocol); #endif -KSMServer::KSMServer( const QString& windowManager, InitFlags flags ) - : wmProcess( nullptr ) - , sessionGroup( QStringLiteral( "" ) ) +KSMServer::KSMServer(InitFlags flags) + : sessionGroup( QStringLiteral( "" ) ) , m_kwinInterface(new OrgKdeKWinSessionInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Session"), QDBusConnection::sessionBus(), this)) , sockets{ -1, -1 } { if (!flags.testFlag(InitFlag::NoLockScreen)) { ScreenLocker::KSldApp::self()->initialize(); if (flags.testFlag(InitFlag::ImmediateLockScreen)) { ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); } } if(::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets) != 0) qFatal("Could not create socket pair, error %d (%s)", errno, strerror(errno)); wake_up_socket = sockets[0]; QSocketNotifier* n = new QSocketNotifier(sockets[1], QSocketNotifier::Read, this); qApp->connect(n, &QSocketNotifier::activated, &QApplication::quit); new KSMServerInterfaceAdaptor( this ); QDBusConnection::sessionBus().registerObject(QStringLiteral("/KSMServer"), this); the_server = this; clean = false; state = Idle; saveSession = false; - wmPhase1WaitingCount = 0; KConfigGroup config(KSharedConfig::openConfig(), "General"); clientInteracting = nullptr; xonCommand = config.readEntry( "xonCommand", "xon" ); - if (windowManager.isEmpty()) { - wm = QStringLiteral(KWIN_BIN); - } else { - wm = windowManager; - } - wmCommands = QStringList({wm}); - only_local = flags.testFlag(InitFlag::OnlyLocal); #ifdef HAVE__ICETRANSNOLISTEN if (only_local) _IceTransNoListen("tcp"); #else only_local = false; #endif char errormsg[256]; if (!SmsInitialize ( (char*) KSMVendorString, (char*) KSMReleaseString, KSMNewClientProc, (SmPointer) this, HostBasedAuthProc, 256, errormsg ) ) { qCWarning(KSMSERVER, "KSMServer: could not register XSM protocol"); } if (!IceListenForConnections (&numTransports, &listenObjs, 256, errormsg)) { qCWarning(KSMSERVER, "KSMServer: Error listening for connections: %s", errormsg); qCWarning(KSMSERVER, "KSMServer: Aborting."); exit(1); } { // publish available transports. QByteArray fName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QDir::separator() + QStringLiteral("KSMserver")); qCDebug(KSMSERVER) << fName; QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); // strip the screen number from the display display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$"))); int i; while( (i = display.indexOf(QLatin1Char(':'))) >= 0) display[i] = '_'; while( (i = display.indexOf(QLatin1Char('/'))) >= 0) display[i] = '_'; fName += '_'+display.toLocal8Bit(); FILE *f; f = ::fopen(fName.data(), "w+"); if (!f) { qCWarning(KSMSERVER, "KSMServer: cannot open %s: %s", fName.data(), strerror(errno)); qCWarning(KSMSERVER, "KSMServer: Aborting."); exit(1); } char* session_manager = IceComposeNetworkIdList(numTransports, listenObjs); fprintf(f, "%s\n%i\n", session_manager, getpid()); fclose(f); setenv( "SESSION_MANAGER", session_manager, true ); // Pass env. var to kdeinit. org::kde::KLauncher klauncher( QStringLiteral( "org.kde.klauncher5" ), QStringLiteral( "/KLauncher" ), QDBusConnection::sessionBus()); klauncher.setLaunchEnv( QStringLiteral( "SESSION_MANAGER" ), QString::fromLocal8Bit( (const char*) session_manager ) ); org::kde::Startup startup(QStringLiteral("org.kde.Startup"), QStringLiteral("/Startup"), QDBusConnection::sessionBus()); startup.updateLaunchEnv( QStringLiteral( "SESSION_MANAGER" ), QString::fromLocal8Bit( (const char*) session_manager ) ); free(session_manager); } if (only_local) { if (!SetAuthentication_local(numTransports, listenObjs)) qFatal("KSMSERVER: authentication setup failed."); } else { if (!SetAuthentication(numTransports, listenObjs, &authDataEntries)) qFatal("KSMSERVER: authentication setup failed."); } IceAddConnectionWatch (KSMWatchProc, (IcePointer) this); KSMListener* con; for ( int i = 0; i < numTransports; i++) { fcntl( IceGetListenConnectionNumber( listenObjs[i] ), F_SETFD, FD_CLOEXEC ); con = new KSMListener( listenObjs[i] ); listener.append( con ); connect(con, &KSMListener::activated, this, &KSMServer::newConnection); } signal(SIGHUP, sighandler); signal(SIGTERM, sighandler); signal(SIGINT, sighandler); signal(SIGPIPE, SIG_IGN); connect(&protectionTimer, &QTimer::timeout, this, &KSMServer::protectionTimeout); connect(&restoreTimer, &QTimer::timeout, this, &KSMServer::tryRestoreNext); connect(qApp, &QApplication::aboutToQuit, this, &KSMServer::cleanUp); setupXIOErrorHandler(); } KSMServer::~KSMServer() { qDeleteAll( listener ); the_server = nullptr; cleanUp(); } void KSMServer::cleanUp() { if (clean) return; clean = true; IceFreeListenObjs (numTransports, listenObjs); wake_up_socket = -1; ::close(sockets[1]); ::close(sockets[0]); sockets[0] = -1; sockets[1] = -1; QByteArray fName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + QStringLiteral("KSMserver")); QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); // strip the screen number from the display display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$"))); int i; while( (i = display.indexOf(QLatin1Char(':'))) >= 0) display[i] = '_'; while( (i = display.indexOf(QLatin1Char('/'))) >= 0) display[i] = '_'; fName += '_'+display.toLocal8Bit(); ::unlink(fName.data()); FreeAuthenticationData(numTransports, authDataEntries); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); } void* KSMServer::watchConnection( IceConn iceConn ) { KSMConnection* conn = new KSMConnection( iceConn ); connect(conn, &KSMConnection::activated, this, &KSMServer::processData); return (void*) conn; } void KSMServer::removeConnection( KSMConnection* conn ) { delete conn; } /*! Called from our IceIoErrorHandler */ void KSMServer::ioError( IceConn /*iceConn*/ ) { } void KSMServer::processData( int /*socket*/ ) { IceConn iceConn = ((KSMConnection*)sender())->iceConn; IceProcessMessagesStatus status = IceProcessMessages( iceConn, nullptr, nullptr ); if ( status == IceProcessMessagesIOError ) { IceSetShutdownNegotiation( iceConn, False ); QList::iterator it = clients.begin(); QList::iterator const itEnd = clients.end(); while ( ( it != itEnd ) && *it && ( SmsGetIceConnection( ( *it )->connection() ) != iceConn ) ) ++it; if ( ( it != itEnd ) && *it ) { SmsConn smsConn = (*it)->connection(); deleteClient( *it ); SmsCleanUp( smsConn ); } (void) IceCloseConnection( iceConn ); } } KSMClient* KSMServer::newClient( SmsConn conn ) { KSMClient* client = new KSMClient( conn ); clients.append( client ); return client; } void KSMServer::deleteClient( KSMClient* client ) { if ( !clients.contains( client ) ) // paranoia return; clients.removeAll( client ); clientsToKill.removeAll( client ); clientsToSave.removeAll( client ); if ( client == clientInteracting ) { clientInteracting = nullptr; handlePendingInteractions(); } delete client; if ( state == Shutdown || state == Checkpoint || state == ClosingSubSession ) completeShutdownOrCheckpoint(); if ( state == Killing ) completeKilling(); else if ( state == KillingSubSession ) completeKillingSubSession(); - if ( state == KillingWM ) - completeKillingWM(); } void KSMServer::newConnection( int /*socket*/ ) { IceAcceptStatus status; IceConn iceConn = IceAcceptConnection( ((KSMListener*)sender())->listenObj, &status); if( iceConn == nullptr ) return; IceSetShutdownNegotiation( iceConn, False ); IceConnectStatus cstatus; while ((cstatus = IceConnectionStatus (iceConn))==IceConnectPending) { (void) IceProcessMessages( iceConn, nullptr, nullptr ); } if (cstatus != IceConnectAccepted) { if (cstatus == IceConnectIOError) qCDebug(KSMSERVER) << "IO error opening ICE Connection!"; else qCDebug(KSMSERVER) << "ICE Connection rejected!"; (void )IceCloseConnection (iceConn); return; } // don't leak the fd fcntl( IceConnectionNumber(iceConn), F_SETFD, FD_CLOEXEC ); } - QString KSMServer::currentSession() { if ( sessionGroup.startsWith( QLatin1String( "Session: " ) ) ) return sessionGroup.mid( 9 ); - return QStringLiteral( "" ); // empty, not null, since used for KConfig::setGroup + return QStringLiteral( "" ); // empty, not null, since used for KConfig::setGroup // TODO does this comment make any sense? } void KSMServer::discardSession() { KConfigGroup config(KSharedConfig::openConfig(), sessionGroup ); int count = config.readEntry( "count", 0 ); foreach ( KSMClient *c, clients ) { QStringList discardCommand = c->discardCommand(); if ( discardCommand.isEmpty()) continue; // check that non of the old clients used the exactly same // discardCommand before we execute it. This used to be the // case up to KDE and Qt < 3.1 int i = 1; while ( i <= count && config.readPathEntry( QStringLiteral("discardCommand") + QString::number(i), QStringList() ) != discardCommand ) i++; if ( i <= count ) executeCommand( discardCommand ); } } void KSMServer::storeSession() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); config->reparseConfiguration(); // config may have changed in the KControl module KConfigGroup generalGroup(config, "General"); excludeApps = generalGroup.readEntry( "excludeApps" ).toLower() .split( QRegularExpression( QStringLiteral("[,:]") ), QString::SkipEmptyParts ); KConfigGroup configSessionGroup(config, sessionGroup); int count = configSessionGroup.readEntry( "count", 0 ); for ( int i = 1; i <= count; i++ ) { QStringList discardCommand = configSessionGroup.readPathEntry( QLatin1String("discardCommand") + QString::number(i), QStringList() ); if ( discardCommand.isEmpty()) continue; // check that non of the new clients uses the exactly same // discardCommand before we execute it. This used to be the // case up to KDE and Qt < 3.1 QList::iterator it = clients.begin(); QList::iterator const itEnd = clients.end(); while ( ( it != itEnd ) && *it && (discardCommand != ( *it )->discardCommand() ) ) ++it; if ( ( it != itEnd ) && *it ) continue; executeCommand( discardCommand ); } config->deleteGroup( sessionGroup ); //### does not work with global config object... KConfigGroup cg( config, sessionGroup); count = 0; - if (state != ClosingSubSession) { - // put the wm first - foreach ( KSMClient *c, clients ) - if ( c->program() == wm ) { - clients.removeAll( c ); - clients.prepend( c ); - break; - } - } + // Tell kwin to save its state + auto reply = m_kwinInterface->finishSaveSession(currentSession()); + reply.waitForFinished(); // boo! foreach ( KSMClient *c, clients ) { int restartHint = c->restartStyleHint(); if (restartHint == SmRestartNever) continue; QString program = c->program(); QStringList restartCommand = c->restartCommand(); if (program.isEmpty() && restartCommand.isEmpty()) continue; if (state == ClosingSubSession && ! clientsToSave.contains(c)) continue; // 'program' might be (mostly) fullpath, or (sometimes) just the name. // 'name' is just the name. QFileInfo info(program); const QString& name = info.fileName(); if ( excludeApps.contains(program.toLower()) || excludeApps.contains(name.toLower()) ) { continue; } count++; QString n = QString::number(count); cg.writeEntry( QStringLiteral("program")+n, program ); cg.writeEntry( QStringLiteral("clientId")+n, c->clientId() ); cg.writeEntry( QStringLiteral("restartCommand")+n, restartCommand ); cg.writePathEntry( QStringLiteral("discardCommand")+n, c->discardCommand() ); cg.writeEntry( QStringLiteral("restartStyleHint")+n, restartHint ); cg.writeEntry( QStringLiteral("userId")+n, c->userId() ); - cg.writeEntry( QStringLiteral("wasWm")+n, isWM( c )); } cg.writeEntry( "count", count ); KConfigGroup cg2( config, "General"); storeLegacySession(config.data()); config->sync(); } QStringList KSMServer::sessionList() { QStringList sessions( QStringLiteral( "default" ) ); KSharedConfig::Ptr config = KSharedConfig::openConfig(); const QStringList groups = config->groupList(); for ( QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it ) if ( (*it).startsWith( QLatin1String( "Session: " ) ) ) sessions << (*it).mid( 9 ); return sessions; } -bool KSMServer::isWM( const KSMClient* client ) const -{ - return isWM( client->program()); -} - -bool KSMServer::isWM( const QString& program ) const -{ - // Strip possible paths, so that even /usr/bin/kwin is recognized as kwin. - QString wmName = wm.mid( wm.lastIndexOf( QDir::separator()) + 1 ); - QString programName = program.mid( program.lastIndexOf( QDir::separator()) + 1 ); - return programName == wmName; -} - bool KSMServer::defaultSession() const { return sessionGroup.isEmpty(); } void KSMServer::setupShortcuts() { if (KAuthorized::authorize( QStringLiteral( "logout" ))) { KActionCollection* actionCollection = new KActionCollection(this); QAction* a; a = actionCollection->addAction(QStringLiteral("Log Out")); a->setText(i18n("Log Out")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::Key_Delete); connect(a, &QAction::triggered, this, &KSMServer::defaultLogout); a = actionCollection->addAction(QStringLiteral("Log Out Without Confirmation")); a->setText(i18n("Log Out Without Confirmation")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_Delete); connect(a, &QAction::triggered, this, &KSMServer::logoutWithoutConfirmation); a = actionCollection->addAction(QStringLiteral("Halt Without Confirmation")); a->setText(i18n("Halt Without Confirmation")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_PageDown); connect(a, &QAction::triggered, this, &KSMServer::haltWithoutConfirmation); a = actionCollection->addAction(QStringLiteral("Reboot Without Confirmation")); a->setText(i18n("Reboot Without Confirmation")); KGlobalAccel::self()->setShortcut(a, QList() << Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_PageUp); connect(a, &QAction::triggered, this, &KSMServer::rebootWithoutConfirmation); } } -/*! Restores the previous session. Ensures the window manager is - running (if specified). +/*! Restores the previous session. */ void KSMServer::restoreSession( const QString &sessionName ) { if( state != Idle ) return; #ifdef KSMSERVER_STARTUP_DEBUG1 t.start(); #endif - state = LaunchingWM; + state = RestoringWMSession; qCDebug(KSMSERVER) << "KSMServer::restoreSession " << sessionName; KSharedConfig::Ptr config = KSharedConfig::openConfig(); sessionGroup = QLatin1String("Session: ") + sessionName; KConfigGroup configSessionGroup( config, sessionGroup); int count = configSessionGroup.readEntry( "count", 0 ); appsToStart = count; - // find all commands to launch the wm in the session - QList wmStartCommands; - if ( !wm.isEmpty() ) { - for ( int i = 1; i <= count; i++ ) { - QString n = QString::number(i); - if ( isWM( configSessionGroup.readEntry( QStringLiteral("program")+n, QString())) ) { - wmStartCommands << configSessionGroup.readEntry( QStringLiteral("restartCommand")+n, QStringList() ); - } + auto reply = m_kwinInterface->loadSession(sessionName); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + if (state == RestoringWMSession) { + state = Idle; } - } - if( wmStartCommands.isEmpty()) // otherwise use the configured default - wmStartCommands << wmCommands; - - launchWM( wmStartCommands ); -} - -void KSMServer::launchWM( const QList< QStringList >& wmStartCommands ) -{ - assert( state == LaunchingWM ); - - if (!(qEnvironmentVariableIsSet("WAYLAND_DISPLAY") || qEnvironmentVariableIsSet("WAYLAND_SOCKET"))) { - // when we have a window manager, we start it first and give - // it some time before launching other processes. Results in a - // visually more appealing startup. - wmProcess = startApplication( wmStartCommands[ 0 ], QString(), QString(), true ); - connect( wmProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(wmProcessChange())); - connect( wmProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(wmProcessChange())); - } - emit windowManagerLoaded(); -} - -void KSMServer::wmProcessChange() -{ - if( state != LaunchingWM ) - { // don't care about the process when not in the wm-launching state anymore - wmProcess = nullptr; - return; - } - if( wmProcess->state() == QProcess::NotRunning ) - { // wm failed to launch for some reason, go with kwin instead - qCWarning(KSMSERVER) << "Window manager" << wm << "failed to launch"; - if( wm == QLatin1String( KWIN_BIN ) ) - return; // uhoh, kwin itself failed - qCDebug(KSMSERVER) << "Launching KWin"; - wm = QStringLiteral( KWIN_BIN ); - wmCommands = ( QStringList() << QStringLiteral( KWIN_BIN ) ); - // launch it - launchWM( QList< QStringList >() << wmCommands ); - return; - } + }); } /*! Starts the default session. - - Currently, that's the window manager only (if specified). */ void KSMServer::startDefaultSession() { if( state != Idle ) return; - state = LaunchingWM; + state = RestoringWMSession; #ifdef KSMSERVER_STARTUP_DEBUG1 t.start(); #endif sessionGroup = QString(); - launchWM( QList< QStringList >() << wmCommands ); } void KSMServer::restoreSession() { Q_ASSERT(calledFromDBus()); if (defaultSession()) { state = KSMServer::Idle; return; } setDelayedReply(true); m_restoreSessionCall = message(); restoreLegacySession(KSharedConfig::openConfig().data()); lastAppStarted = 0; lastIdStarted.clear(); state = KSMServer::Restoring; connect(this, &KSMServer::sessionRestored, this, [this]() { auto reply = m_restoreSessionCall.createReply(); QDBusConnection::sessionBus().send(reply); m_restoreSessionCall = QDBusMessage(); }); tryRestoreNext(); } void KSMServer::restoreSubSession( const QString& name ) { sessionGroup = QStringLiteral( "SubSession: " ) + name; KConfigGroup configSessionGroup( KSharedConfig::openConfig(), sessionGroup); int count = configSessionGroup.readEntry( "count", 0 ); appsToStart = count; lastAppStarted = 0; lastIdStarted.clear(); state = RestoringSubSession; tryRestoreNext(); } -void KSMServer::clientSetProgram( KSMClient* client ) -{ - if( client->program() == wm ) { - emit windowManagerLoaded(); - } -} - void KSMServer::clientRegistered( const char* previousId ) { if ( previousId && lastIdStarted == QString::fromLocal8Bit( previousId ) ) tryRestoreNext(); } void KSMServer::tryRestoreNext() { if( state != Restoring && state != RestoringSubSession ) return; restoreTimer.stop(); KConfigGroup config(KSharedConfig::openConfig(), sessionGroup ); while ( lastAppStarted < appsToStart ) { lastAppStarted++; QString n = QString::number(lastAppStarted); QString clientId = config.readEntry( QLatin1String("clientId")+n, QString() ); bool alreadyStarted = false; foreach ( KSMClient *c, clients ) { if ( QString::fromLocal8Bit( c->clientId() ) == clientId ) { alreadyStarted = true; break; } } if ( alreadyStarted ) continue; QStringList restartCommand = config.readEntry( QLatin1String("restartCommand")+n, QStringList() ); if ( restartCommand.isEmpty() || (config.readEntry( QStringLiteral("restartStyleHint")+n, 0 ) == SmRestartNever)) { continue; } - if ( isWM( config.readEntry( QStringLiteral("program")+n, QString())) ) - continue; // wm already started - if( config.readEntry( QStringLiteral( "wasWm" )+n, false )) - continue; // it was wm before, but not now, don't run it (some have --replace in command :( ) startApplication( restartCommand, config.readEntry( QStringLiteral("clientMachine")+n, QString() ), config.readEntry( QStringLiteral("userId")+n, QString() )); lastIdStarted = clientId; if ( !lastIdStarted.isEmpty() ) { restoreTimer.setSingleShot( true ); restoreTimer.start( 2000 ); return; // we get called again from the clientRegistered handler } } //all done appsToStart = 0; lastIdStarted.clear(); if (state == Restoring) { emit sessionRestored(); } else { //subsession emit subSessionOpened(); } state = Idle; } void KSMServer::startupDone() { state = Idle; } void KSMServer::defaultLogout() { shutdown(KWorkSpace::ShutdownConfirmYes, KWorkSpace::ShutdownTypeDefault, KWorkSpace::ShutdownModeDefault); } void KSMServer::logoutWithoutConfirmation() { shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeNone, KWorkSpace::ShutdownModeDefault); } void KSMServer::haltWithoutConfirmation() { shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeHalt, KWorkSpace::ShutdownModeDefault); } void KSMServer::rebootWithoutConfirmation() { shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeReboot, KWorkSpace::ShutdownModeDefault); } void KSMServer::openSwitchUserDialog() { //this method exists only for compatibility. Users should ideally call this directly OrgKdeScreensaverInterface iface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); iface.SwitchUser(); } diff --git a/ksmserver/server.h b/ksmserver/server.h index 779295535..1b55cf832 100644 --- a/ksmserver/server.h +++ b/ksmserver/server.h @@ -1,266 +1,250 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #ifndef SERVER_H #define SERVER_H #define INT32 QINT32 #include #include #include extern "C" { #include #include #include #include #include } #include // needed to avoid clash with INT8 defined in X11/Xmd.h on solaris #define QT_CLEAN_NAMESPACE 1 #include #include #include #include #include #include #include #include #include #include #define SESSION_PREVIOUS_LOGOUT "saved at previous logout" #define SESSION_BY_USER "saved by user" class KProcess; class KSMListener; class KSMConnection; class KSMClient; class OrgKdeKWinSessionInterface; enum SMType { SM_ERROR, SM_WMCOMMAND, SM_WMSAVEYOURSELF }; struct SMData { SMType type; QStringList wmCommand; QString wmClientMachine; QString wmclass1, wmclass2; }; typedef QMap WindowMap; class KSMServer : public QObject, protected QDBusContext { Q_OBJECT public: enum class InitFlag { None = 0, OnlyLocal = 1 << 0, ImmediateLockScreen = 1 << 1, NoLockScreen = 1 << 2 }; Q_DECLARE_FLAGS(InitFlags, InitFlag) - KSMServer( const QString& windowManager, InitFlags flags ); + KSMServer(InitFlags flags ); ~KSMServer() override; static KSMServer* self(); void* watchConnection( IceConn iceConn ); void removeConnection( KSMConnection* conn ); KSMClient* newClient( SmsConn ); void deleteClient( KSMClient* client ); // callbacks void saveYourselfDone( KSMClient* client, bool success ); void interactRequest( KSMClient* client, int dialogType ); void interactDone( KSMClient* client, bool cancelShutdown ); void phase2Request( KSMClient* client ); // error handling void ioError( IceConn iceConn ); // notification - void clientSetProgram( KSMClient* client ); void clientRegistered( const char* previousId ); // public API void performLogout(); void restoreSession(); void restoreSession( const QString &sessionName ); void startDefaultSession(); void shutdown( KWorkSpace::ShutdownConfirm confirm, KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode ); void setupShortcuts(); Q_SIGNALS: - void windowManagerLoaded(); void logoutCancelled(); public Q_SLOTS: void cleanUp(); private Q_SLOTS: void newConnection( int socket ); void processData( int socket ); void protectionTimeout(); void timeoutQuit(); - void timeoutWMQuit(); - - void wmProcessChange(); void defaultLogout(); void logoutWithoutConfirmation(); void haltWithoutConfirmation(); void rebootWithoutConfirmation(); private: void handlePendingInteractions(); void completeShutdownOrCheckpoint(); void startKilling(); void startKillingSubSession(); void performStandardKilling(); void completeKilling(); void completeKillingSubSession(); - void killWM(); void signalSubSessionClosed(); - void completeKillingWM(); void cancelShutdown( KSMClient* c ); void killingCompleted(); void discardSession(); void storeSession(); void startProtection(); void endProtection(); - void launchWM( const QList< QStringList >& wmStartCommands ); - KProcess* startApplication( const QStringList& command, const QString& clientMachine = QString(), const QString& userId = QString(), bool wm = false ); void executeCommand( const QStringList& command ); - bool isWM( const KSMClient* client ) const; - bool isWM( const QString& program ) const; bool defaultSession() const; // empty session void setupXIOErrorHandler(); void performLegacySessionSave(); void storeLegacySession( KConfig* config ); void restoreLegacySession( KConfig* config ); void restoreLegacySessionInternal( KConfigGroup* config, char sep = ',' ); QStringList windowWmCommand(WId w); QString windowWmClientMachine(WId w); WId windowWmClientLeader(WId w); QByteArray windowSessionId(WId w, WId leader); void tryRestoreNext(); void startupDone(); void runShutdownScripts(); // public dcop interface public Q_SLOTS: //public dcop interface void logout( int, int, int ); bool canShutdown(); bool isShuttingDown() const; QString currentSession(); void saveCurrentSession(); void saveCurrentSessionAs( const QString & ); QStringList sessionList(); void saveSubSession( const QString &name, QStringList saveAndClose, QStringList saveOnly = QStringList() ); void restoreSubSession( const QString &name ); void openSwitchUserDialog(); bool closeSession(); Q_SIGNALS: void subSessionClosed(); void subSessionCloseCanceled(); void subSessionOpened(); void sessionRestored(); private: QList listener; QList clients; enum State { Idle, - LaunchingWM, Restoring, - Shutdown, Checkpoint, Killing, KillingWM, WaitingForKNotify, // shutdown + RestoringWMSession, Restoring, + Shutdown, Checkpoint, Killing, WaitingForKNotify, // shutdown ClosingSubSession, KillingSubSession, RestoringSubSession }; State state; bool saveSession; - int wmPhase1WaitingCount; int saveType; bool clean; KSMClient* clientInteracting; - QString wm; - QStringList wmCommands; - KProcess* wmProcess; QString sessionGroup; - QString sessionName; QTimer protectionTimer; QTimer restoreTimer; QString xonCommand; // sequential startup int appsToStart; int lastAppStarted; QString lastIdStarted; QStringList excludeApps; WindowMap legacyWindows; QDBusMessage m_performLogoutCall; QDBusMessage m_restoreSessionCall; //subSession stuff QList clientsToKill; QList clientsToSave; OrgKdeKWinSessionInterface *m_kwinInterface; int sockets[2]; friend bool readFromPipe(int pipe); }; #endif diff --git a/startkde/config-startplasma.h.cmake b/startkde/config-startplasma.h.cmake index fc8447056..72b201212 100644 --- a/startkde/config-startplasma.h.cmake +++ b/startkde/config-startplasma.h.cmake @@ -1,10 +1,12 @@ #ifndef CONFIG_STARTPLASMA_H #define CONFIG_STARTPLASMA_H #define CMAKE_INSTALL_FULL_BINDIR "@CMAKE_INSTALL_FULL_BINDIR@" #define KDE_INSTALL_FULL_DATAROOTDIR "@KDE_INSTALL_FULL_DATAROOTDIR@" #define CMAKE_INSTALL_FULL_LIBEXECDIR "@CMAKE_INSTALL_FULL_LIBEXECDIR@" #define CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "@CMAKE_INSTALL_FULL_LIBEXECDIR_KF5@" #define KWIN_WAYLAND_BIN_PATH "@KWIN_WAYLAND_BIN_PATH@" +#define KWIN_BIN "${KWIN_BIN}" + #endif diff --git a/startkde/plasma-session/startup.cpp b/startkde/plasma-session/startup.cpp index a6e884c12..52604a386 100644 --- a/startkde/plasma-session/startup.cpp +++ b/startkde/plasma-session/startup.cpp @@ -1,474 +1,500 @@ /***************************************************************** Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak Copyright 2018 David Edmundson relatively small extensions by Oswald Buddenhagen some code taken from the dcopserver (part of the KDE libraries), which is Copyright 1999 Matthias Ettrich Copyright 1999 Preston Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "startup.h" #include "debug.h" #include "kcminit_interface.h" #include "kded_interface.h" #include #include "ksmserver_interface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "startupadaptor.h" #include "../config-startplasma.h" class Phase: public KCompositeJob { Q_OBJECT public: Phase(const AutoStart &autostart, QObject *parent) : KCompositeJob(parent) , m_autostart(autostart) {} bool addSubjob(KJob *job) override { bool rc = KCompositeJob::addSubjob(job); job->start(); return rc; } void slotResult(KJob *job) override { KCompositeJob::slotResult(job); if (!hasSubjobs()) { emitResult(); } } protected: const AutoStart m_autostart; }; class StartupPhase0: public Phase { Q_OBJECT public: StartupPhase0(const AutoStart& autostart, QObject *parent) : Phase(autostart, parent) {} void start() override { qCDebug(PLASMA_SESSION) << "Phase 0"; addSubjob(new AutoStartAppsJob(m_autostart, 0)); addSubjob(new KCMInitJob()); addSubjob(new SleepJob()); } }; class StartupPhase1: public Phase { Q_OBJECT public: StartupPhase1(const AutoStart& autostart, QObject *parent) : Phase(autostart, parent) {} void start() override { qCDebug(PLASMA_SESSION) << "Phase 1"; addSubjob(new AutoStartAppsJob(m_autostart, 1)); } }; class StartupPhase2: public Phase { Q_OBJECT public: StartupPhase2(const AutoStart& autostart, QObject *parent) : Phase(autostart, parent) {} void runUserAutostart(); bool migrateKDE4Autostart(const QString &folder); void start() override { qCDebug(PLASMA_SESSION) << "Phase 2"; addSubjob(new AutoStartAppsJob(m_autostart, 2)); addSubjob(new KDEDInitJob()); runUserAutostart(); } }; SleepJob::SleepJob() { } void SleepJob::start() { auto t = new QTimer(this); connect(t, &QTimer::timeout, this, [this]() {emitResult();}); t->start(100); } // Put the notification in its own thread as it can happen that // PulseAudio will start initializing with this, so let's not // block the main thread with waiting for PulseAudio to start class NotificationThread : public QThread { Q_OBJECT void run() override { // We cannot parent to the thread itself so let's create // a QObject on the stack and parent everythign to it QObject parent; KNotifyConfig notifyConfig(QStringLiteral("plasma_workspace"), QList< QPair >(), QStringLiteral("startkde")); const QString action = notifyConfig.readEntry(QStringLiteral("Action")); if (action.isEmpty() || !action.split(QLatin1Char('|')).contains(QLatin1String("Sound"))) { // no startup sound configured return; } Phonon::AudioOutput *m_audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory, &parent); QString soundFilename = notifyConfig.readEntry(QStringLiteral("Sound")); if (soundFilename.isEmpty()) { qCWarning(PLASMA_SESSION) << "Audio notification requested, but no sound file provided in notifyrc file, aborting audio notification"; return; } QUrl soundURL; const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString &dataLocation: dataLocations) { soundURL = QUrl::fromUserInput(soundFilename, dataLocation + QStringLiteral("/sounds"), QUrl::AssumeLocalFile); if (soundURL.isLocalFile() && QFile::exists(soundURL.toLocalFile())) { break; } else if (!soundURL.isLocalFile() && soundURL.isValid()) { break; } soundURL.clear(); } if (soundURL.isEmpty()) { qCWarning(PLASMA_SESSION) << "Audio notification requested, but sound file from notifyrc file was not found, aborting audio notification"; return; } Phonon::MediaObject *m = new Phonon::MediaObject(&parent); connect(m, &Phonon::MediaObject::finished, this, &NotificationThread::quit); Phonon::createPath(m, m_audioOutput); m->setCurrentSource(soundURL); m->play(); exec(); } }; Startup::Startup(QObject *parent): QObject(parent) { new StartupAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Startup"), QStringLiteral("org.kde.Startup"), this); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Startup")); upAndRunning(QStringLiteral("ksmserver")); const AutoStart autostart; QProcess::execute(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/start_kdeinit_wrapper")); KJob* phase1; QProcessEnvironment kdedProcessEnv; kdedProcessEnv.insert(QStringLiteral("KDED_STARTED_BY_KDEINIT"), QStringLiteral("1")); + KJob *windowManagerJob = nullptr; + + if (qEnvironmentVariable("XDG_SESSION_TYPE") != QLatin1String("wayland")) { + QString windowManager; + if (qEnvironmentVariableIsSet("KDEWM")) { + windowManager = qEnvironmentVariable("KDEWM"); + } + if (windowManager.isEmpty()) { + windowManager = QStringLiteral(KWIN_BIN); + } + + if (windowManager == QLatin1String(KWIN_BIN)) { + windowManagerJob = new StartServiceJob(windowManager, {}, QStringLiteral("org.kde.KWin")); + } else { + windowManagerJob = new StartServiceJob(windowManager, {}, {}); + } + } + const QVector sequence = { new StartProcessJob(QStringLiteral("kcminit_startup"), {}), new StartServiceJob(QStringLiteral("kded5"), {}, QStringLiteral("org.kde.kded5"), kdedProcessEnv), + windowManagerJob, new StartServiceJob(QStringLiteral("ksmserver"), QCoreApplication::instance()->arguments().mid(1), QStringLiteral("org.kde.ksmserver")), new StartupPhase0(autostart, this), phase1 = new StartupPhase1(autostart, this), new RestoreSessionJob(), new StartupPhase2(autostart, this), }; KJob* last = nullptr; for(KJob* job : sequence) { + if (!job) { + continue; + } if (last) { connect(last, &KJob::finished, job, &KJob::start); } last = job; } connect(phase1, &KJob::finished, this, []() { NotificationThread *loginSound = new NotificationThread(); connect(loginSound, &NotificationThread::finished, loginSound, &NotificationThread::deleteLater); loginSound->start();}); connect(sequence.last(), &KJob::finished, this, &Startup::finishStartup); sequence.first()->start(); } void Startup::upAndRunning( const QString& msg ) { QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << msg); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); } void Startup::finishStartup() { qCDebug(PLASMA_SESSION) << "Finished"; upAndRunning(QStringLiteral("ready")); qApp->quit(); } void Startup::updateLaunchEnv(const QString &key, const QString &value) { qputenv(key.toLatin1(), value.toLatin1()); } KCMInitJob::KCMInitJob() : KJob() { } void KCMInitJob::start() { org::kde::KCMInit kcminit(QStringLiteral("org.kde.kcminit"), QStringLiteral("/kcminit"), QDBusConnection::sessionBus()); kcminit.setTimeout(10 * 1000); QDBusPendingReply pending = kcminit.runPhase1(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this]() {emitResult();}); connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater); } KDEDInitJob::KDEDInitJob() { } void KDEDInitJob::start() { qCDebug(PLASMA_SESSION()); org::kde::kded5 kded( QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QDBusConnection::sessionBus()); auto pending = kded.loadSecondPhase(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this]() {emitResult();}); connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater); } RestoreSessionJob::RestoreSessionJob(): KJob() {} void RestoreSessionJob::start() { OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); auto pending = ksmserverIface.restoreSession(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this]() {emitResult();}); connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater); } void StartupPhase2::runUserAutostart() { // Now let's execute the scripts in the KDE-specific autostart-scripts folder. const QString autostartFolder = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QDir::separator() + QStringLiteral("autostart-scripts"); QDir dir(autostartFolder); if (!dir.exists()) { // Create dir in all cases, so that users can find it :-) dir.mkpath(QStringLiteral(".")); if (!migrateKDE4Autostart(autostartFolder)) { return; } } const QStringList entries = dir.entryList(QDir::Files); for (const QString &file : entries) { // Don't execute backup files if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QLatin1String(".bak")) && (file[0] != QLatin1Char('%') || !file.endsWith(QLatin1Char('%'))) && (file[0] != QLatin1Char('#') || !file.endsWith(QLatin1Char('#')))) { const QString fullPath = dir.absolutePath() + QLatin1Char('/') + file; qCInfo(PLASMA_SESSION) << "Starting autostart script " << fullPath; auto p = new KProcess; //deleted in onFinished lambda p->setProgram(fullPath); p->start(); connect(p, static_cast(&QProcess::finished), [p](int exitCode) { qCInfo(PLASMA_SESSION) << "autostart script" << p->program() << "finished with exit code " << exitCode; p->deleteLater(); }); } } } bool StartupPhase2::migrateKDE4Autostart(const QString &autostartFolder) { // Migrate user autostart from kde4 Kdelibs4Migration migration; if (!migration.kdeHomeFound()) { return false; } // KDEHOME/Autostart was the default value for KGlobalSettings::autostart() QString oldAutostart = migration.kdeHome() + QStringLiteral("/Autostart"); // That path could be customized in kdeglobals const QString oldKdeGlobals = migration.locateLocal("config", QStringLiteral("kdeglobals")); if (!oldKdeGlobals.isEmpty()) { oldAutostart = KConfig(oldKdeGlobals).group("Paths").readEntry("Autostart", oldAutostart); } const QDir oldFolder(oldAutostart); qCDebug(PLASMA_SESSION) << "Copying autostart files from" << oldFolder.path(); const QStringList entries = oldFolder.entryList(QDir::Files); for (const QString &file : entries) { const QString src = oldFolder.absolutePath() + QLatin1Char('/') + file; const QString dest = autostartFolder + QLatin1Char('/') + file; QFileInfo info(src); bool success; if (info.isSymLink()) { // This will only work with absolute symlink targets success = QFile::link(info.symLinkTarget(), dest); } else { success = QFile::copy(src, dest); } if (!success) { qCWarning(PLASMA_SESSION) << "Error copying" << src << "to" << dest; } } return true; } AutoStartAppsJob::AutoStartAppsJob(const AutoStart & autostart, int phase) : m_autoStart(autostart) { m_autoStart.setPhase(phase); } void AutoStartAppsJob::start() { qCDebug(PLASMA_SESSION); QTimer::singleShot(0, this, [=]() { do { QString serviceName = m_autoStart.startService(); if (serviceName.isEmpty()) { // Done if (!m_autoStart.phaseDone()) { m_autoStart.setPhaseDone(); } emitResult(); return; } KService service(serviceName); auto arguments = KIO::DesktopExecParser(service, QList()).resultingArguments(); if (arguments.isEmpty()) { qCWarning(PLASMA_SESSION) << "failed to parse" << serviceName << "for autostart"; continue; } qCInfo(PLASMA_SESSION) << "Starting autostart service " << serviceName << arguments; auto program = arguments.takeFirst(); if (!QProcess::startDetached(program, arguments)) qCWarning(PLASMA_SESSION) << "could not start" << serviceName << ":" << program << arguments; } while (true); }); } StartServiceJob::StartServiceJob(const QString &process, const QStringList &args, const QString &serviceId, const QProcessEnvironment &additionalEnv) : KJob() , m_process(new QProcess(this)) , m_serviceId(serviceId) , m_additionalEnv(additionalEnv) { m_process->setProgram(process); m_process->setArguments(args); auto watcher = new QDBusServiceWatcher(serviceId, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &StartServiceJob::emitResult); } void StartServiceJob::start() { auto env = QProcessEnvironment::systemEnvironment(); env.insert(m_additionalEnv); m_process->setProcessEnvironment(env); - if (QDBusConnection::sessionBus().interface()->isServiceRegistered(m_serviceId)) { + if (!m_serviceId.isEmpty() && QDBusConnection::sessionBus().interface()->isServiceRegistered(m_serviceId)) { qCDebug(PLASMA_SESSION) << m_process << "already running"; emitResult(); return; } qCDebug(PLASMA_SESSION) << "Starting " << m_process->program() << m_process->arguments(); if (!m_process->startDetached()) { qCWarning(PLASMA_SESSION) << "error starting process" << m_process->program() << m_process->arguments(); emitResult(); } + + if (m_serviceId.isEmpty()) { + emitResult(); + } } StartProcessJob::StartProcessJob(const QString &process, const QStringList &args, const QProcessEnvironment &additionalEnv) : KJob() , m_process(new QProcess(this)) { m_process->setProgram(process); m_process->setArguments(args); auto env = QProcessEnvironment::systemEnvironment(); env.insert(additionalEnv); m_process->setProcessEnvironment(env); connect(m_process, static_cast(&QProcess::finished), [this](int exitCode) { qCInfo(PLASMA_SESSION) << "process job " << m_process->program() << "finished with exit code " << exitCode; emitResult(); }); } void StartProcessJob::start() { qCDebug(PLASMA_SESSION) << "Starting " << m_process->program() << m_process->arguments(); m_process->start(); } #include "startup.moc" diff --git a/startkde/startplasma.cpp b/startkde/startplasma.cpp index 11b5b5bbb..f3c8ef9d3 100644 --- a/startkde/startplasma.cpp +++ b/startkde/startplasma.cpp @@ -1,429 +1,427 @@ /* This file is part of the KDE project Copyright (C) 2019 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include + #include #include #include #include #include #include #include #include #include #include #include "startplasma.h" QTextStream out(stderr); void messageBox(const QString &text) { out << text; runSync(QStringLiteral("xmessage"), {QStringLiteral("-geometry"), QStringLiteral("500x100"), text}); } QStringList allServices(const QLatin1String& prefix) { QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); const QStringList services = bus->registeredServiceNames(); QMap servicesWithAliases; for (const QString &serviceName : services) { QDBusReply reply = bus->serviceOwner(serviceName); QString owner = reply; if (owner.isEmpty()) owner = serviceName; servicesWithAliases[owner].append(serviceName); } QStringList names; for (auto it = servicesWithAliases.constBegin(); it != servicesWithAliases.constEnd(); ++it) { if (it.value().startsWith(prefix)) names << it.value(); } names.removeDuplicates(); names.sort(); return names; } int runSync(const QString& program, const QStringList &args, const QStringList &env) { QProcess p; if (!env.isEmpty()) p.setEnvironment(QProcess::systemEnvironment() << env); p.setProcessChannelMode(QProcess::ForwardedChannels); p.start(program, args); // qDebug() << "started..." << program << args; p.waitForFinished(-1); if (p.exitCode()) { qWarning() << program << args << "exited with code" << p.exitCode(); } return p.exitCode(); } void sourceFiles(const QStringList &files) { QStringList filteredFiles; std::copy_if(files.begin(), files.end(), std::back_inserter(filteredFiles), [](const QString& i){ return QFileInfo(i).isReadable(); } ); if (filteredFiles.isEmpty()) return; filteredFiles.prepend(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR "/plasma-sourceenv.sh")); QProcess p; p.start(QStringLiteral("/bin/sh"), filteredFiles); p.waitForFinished(-1); const auto fullEnv = p.readAllStandardOutput(); auto envs = fullEnv.split('\0'); for (auto &env: envs) { if (env.startsWith("_=") || env.startsWith("SHLVL")) continue; const int idx = env.indexOf('='); if (Q_UNLIKELY(idx <= 0)) continue; if (qgetenv(env.left(idx)) != env.mid(idx+1)) { // qDebug() << "setting..." << env.left(idx) << env.mid(idx+1) << "was" << qgetenv(env.left(idx)); qputenv(env.left(idx), env.mid(idx+1)); } } } void createConfigDirectory() { const QString configDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); if (!QDir().mkpath(configDir)) out << "Could not create config directory XDG_CONFIG_HOME: " << configDir << '\n'; } void runStartupConfig() { //export LC_* variables set by kcmshell5 formats into environment //so it can be picked up by QLocale and friends. KConfig config(QStringLiteral("plasma-localerc")); KConfigGroup formatsConfig = KConfigGroup(&config, "Formats"); const auto lcValues = { "LANG", "LC_NUMERIC", "LC_TIME", "LC_MONETARY", "LC_MEASUREMENT", "LC_COLLATE", "LC_CTYPE" }; for (auto lc : lcValues) { const QString value = formatsConfig.readEntry(lc, QString()); if (!value.isEmpty()) { qputenv(lc, value.toUtf8()); } } KConfigGroup languageConfig = KConfigGroup(&config, "Translations"); const QString value = languageConfig.readEntry("LANGUAGE", QString()); if (!value.isEmpty()) { qputenv("LANGUAGE", value.toUtf8()); } if (!formatsConfig.hasKey("LANG") && !qEnvironmentVariableIsEmpty("LANG")) { formatsConfig.writeEntry("LANG", qgetenv("LANG")); formatsConfig.sync(); } } void setupCursor(bool wayland) { const KConfig cfg(QStringLiteral("kcminputrc")); const KConfigGroup inputCfg = cfg.group("Mouse"); const auto kcminputrc_mouse_cursorsize = inputCfg.readEntry("cursorSize", QString()); const auto kcminputrc_mouse_cursortheme = inputCfg.readEntry("cursorTheme", QStringLiteral("breeze_cursors")); if (!kcminputrc_mouse_cursortheme.isEmpty() || !kcminputrc_mouse_cursorsize.isEmpty()) { #ifdef XCURSOR_PATH QByteArray path(XCURSOR_PATH); path.replace("$XCURSOR_PATH", qgetenv("XCURSOR_PATH")); qputenv("XCURSOR_PATH", path); #endif } //TODO: consider linking directly const int applyMouseStatus = wayland ? 0 : runSync(QStringLiteral("kapplymousetheme"), { kcminputrc_mouse_cursortheme, kcminputrc_mouse_cursorsize }); if (applyMouseStatus == 10) { qputenv("XCURSOR_THEME", "breeze_cursors"); } else if (!kcminputrc_mouse_cursortheme.isEmpty()) { qputenv("XCURSOR_THEME", kcminputrc_mouse_cursortheme.toUtf8()); } if (!kcminputrc_mouse_cursorsize.isEmpty()) { qputenv("XCURSOR_SIZE", kcminputrc_mouse_cursorsize.toUtf8()); } } // Source scripts found in /plasma-workspace/env/*.sh // (where correspond to the system and user's configuration // directory. // // This is where you can define environment variables that will be available to // all KDE programs, so this is where you can run agents using e.g. eval `ssh-agent` // or eval `gpg-agent --daemon`. // Note: if you do that, you should also put "ssh-agent -k" as a shutdown script // // (see end of this file). // For anything else (that doesn't set env vars, or that needs a window manager), // better use the Autostart folder. void runEnvironmentScripts() { QStringList scripts; const auto locations = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("plasma-workspace/env"), QStandardPaths::LocateDirectory); for (const QString & location : locations) { QDir dir(location); const auto dirScripts = dir.entryInfoList({QStringLiteral("*.sh")}); for (const auto script : dirScripts) { scripts << script.absoluteFilePath(); } } sourceFiles(scripts); } // Mark that full KDE session is running (e.g. Konqueror preloading works only // with full KDE running). The KDE_FULL_SESSION property can be detected by // any X client connected to the same X session, even if not launched // directly from the KDE session but e.g. using "ssh -X", kdesu. $KDE_FULL_SESSION // however guarantees that the application is launched in the same environment // like the KDE session and that e.g. KDE utilities/libraries are available. // KDE_FULL_SESSION property is also only available since KDE 3.5.5. // The matching tests are: // For $KDE_FULL_SESSION: // if test -n "$KDE_FULL_SESSION"; then ... whatever // For KDE_FULL_SESSION property (on X11): // xprop -root | grep "^KDE_FULL_SESSION" >/dev/null 2>/dev/null // if test $? -eq 0; then ... whatever // // Additionally there is $KDE_SESSION_UID with the uid // of the user running the KDE session. It should be rarely needed (e.g. // after sudo to prevent desktop-wide functionality in the new user's kded). // // Since KDE4 there is also KDE_SESSION_VERSION, containing the major version number. // void setupPlasmaEnvironment() { //Manually disable auto scaling because we are scaling above //otherwise apps that manually opt in for high DPI get auto scaled by the developer AND manually scaled by us qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0"); qputenv("KDE_FULL_SESSION", "true"); qputenv("KDE_SESSION_VERSION", "5"); qputenv("KDE_SESSION_UID", QByteArray::number(getuid())); qputenv("XDG_CURRENT_DESKTOP", "KDE"); qputenv("KDE_APPLICATIONS_AS_SCOPE", "1"); } void setupX11() { // Set a left cursor instead of the standard X11 "X" cursor, since I've heard // from some users that they're confused and don't know what to do. This is // especially necessary on slow machines, where starting KDE takes one or two // minutes until anything appears on the screen. // // If the user has overwritten fonts, the cursor font may be different now // so don't move this up. runSync(QStringLiteral("xsetroot"), {QStringLiteral("-cursor_name"), QStringLiteral("left_ptr")}); runSync(QStringLiteral("xprop"), {QStringLiteral("-root"), QStringLiteral("-f"), QStringLiteral("KDE_FULL_SESSION"), QStringLiteral("8t"), QStringLiteral("-set"), QStringLiteral("KDE_FULL_SESSION"), QStringLiteral("true")}); runSync(QStringLiteral("xprop"), {QStringLiteral("-root"), QStringLiteral("-f"), QStringLiteral("KDE_SESSION_VERSION"), QStringLiteral("32c"), QStringLiteral("-set"), QStringLiteral("KDE_SESSION_VERSION"), QStringLiteral("5")}); } void cleanupX11() { runSync(QStringLiteral("xprop"), { QStringLiteral("-root"), QStringLiteral("-remove"), QStringLiteral("KDE_FULL_SESSION") }); runSync(QStringLiteral("xprop"), { QStringLiteral("-root"), QStringLiteral("-remove"), QStringLiteral("KDE_SESSION_VERSION") }); } // TODO: Check if Necessary void cleanupPlasmaEnvironment() { qunsetenv("KDE_FULL_SESSION"); qunsetenv("KDE_SESSION_VERSION"); qunsetenv("KDE_SESSION_UID"); } // kwin_wayland can possibly also start dbus-activated services which need env variables. // In that case, the update in startplasma might be too late. bool syncDBusEnvironment() { int exitCode; // At this point all environment variables are set, let's send it to the DBus session server to update the activation environment if (!QStandardPaths::findExecutable(QStringLiteral("dbus-update-activation-environment")).isEmpty()) { exitCode = runSync(QStringLiteral("dbus-update-activation-environment"), { QStringLiteral("--systemd"), QStringLiteral("--all") }); } else { exitCode = runSync(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR "/ksyncdbusenv"), {}); } return exitCode == 0; } void setupFontDpi() { KConfig cfg(QStringLiteral("kcmfonts")); KConfigGroup fontsCfg(&cfg, "General"); if (!fontsCfg.hasKey("forceFontDPI")) { return; } //TODO port to c++? const QByteArray input = "Xft.dpi: " + QByteArray::number(fontsCfg.readEntry("forceFontDPI", 0)); QProcess p; p.start(QStringLiteral("xrdb"), { QStringLiteral("-quiet"), QStringLiteral("-merge"), QStringLiteral("-nocpp") }); p.setProcessChannelMode(QProcess::ForwardedChannels); p.write(input); p.closeWriteChannel(); p.waitForFinished(-1); } static bool desktopLockedAtStart = false; QProcess* setupKSplash() { const auto dlstr = qgetenv("DESKTOP_LOCKED"); desktopLockedAtStart = dlstr == "true" || dlstr == "1"; qunsetenv("DESKTOP_LOCKED"); // Don't want it in the environment QProcess* p = nullptr; if (!desktopLockedAtStart) { const KConfig cfg(QStringLiteral("ksplashrc")); // the splashscreen and progress indicator KConfigGroup ksplashCfg = cfg.group("KSplash"); if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) { p = new QProcess; p->start(QStringLiteral("ksplashqml"), { ksplashCfg.readEntry("Theme", QStringLiteral("Breeze")) }); } } return p; } - void setupGSLib() // Get Ghostscript to look into user's KDE fonts dir for additional Fontmap { const QByteArray usr_fdir = QFile::encodeName(QDir::home().absoluteFilePath(QStringLiteral(".fonts"))); if (qEnvironmentVariableIsSet("GS_LIB")) { qputenv("GS_LIB", usr_fdir + ':' + qgetenv("GS_LIB")); } else { qputenv("GS_LIB", usr_fdir); } } bool startPlasmaSession(bool wayland) { OrgKdeKSplashInterface iface(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QDBusConnection::sessionBus()); iface.setStage(QStringLiteral("kinit")); // finally, give the session control to the session manager // see kdebase/ksmserver for the description of the rest of the startup sequence // if the KDEWM environment variable has been set, then it will be used as KDE's // window manager instead of kwin. // if KDEWM is not set, ksmserver will ensure kwin is started. // kwrapper5 is used to reduce startup time and memory usage // kwrapper5 does not return useful error codes such as the exit code of ksmserver. // We only check for 255 which means that the ksmserver process could not be // started, any problems thereafter, e.g. ksmserver failing to initialize, // will remain undetected. // If the session should be locked from the start (locked autologin), // lock now and do the rest of the KDE startup underneath the locker. QStringList plasmaSessionOptions; if (wayland) { plasmaSessionOptions << QStringLiteral("--no-lockscreen"); } else { - if (qEnvironmentVariableIsSet("KDEWM")) { - plasmaSessionOptions << QStringLiteral("--windowmanager") << qEnvironmentVariable("KDEWM"); - } if (desktopLockedAtStart) { plasmaSessionOptions << QStringLiteral("--lockscreen"); } } bool rc = true; QEventLoop e; QProcess startPlasmaSession; startPlasmaSession.setProcessChannelMode(QProcess::ForwardedChannels); QDBusServiceWatcher serviceWatcher; serviceWatcher.setConnection(QDBusConnection::sessionBus()); // We want to exit when both ksmserver and plasma-session-shutdown have finished // This also closes if ksmserver crashes unexpectedly, as in those cases plasma-shutdown is not running serviceWatcher.addWatchedService(QStringLiteral("org.kde.ksmserver")); serviceWatcher.addWatchedService(QStringLiteral("org.kde.shutdown")); serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration); QObject::connect(&startPlasmaSession, QOverload::of(&QProcess::finished), [&rc, &e](int exitCode, QProcess::ExitStatus) { if (exitCode == 255) { // Startup error messageBox(QStringLiteral("startkde: Could not start ksmserver. Check your installation.\n")); rc = false; e.quit(); } }); QObject::connect(&serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, [&]() { const QStringList watchedServices = serviceWatcher.watchedServices(); bool plasmaSessionRunning = std::any_of(watchedServices.constBegin(), watchedServices.constEnd(), [](const QString &service) { return QDBusConnection::sessionBus().interface()->isServiceRegistered(service); }); if (!plasmaSessionRunning) { e.quit(); } }); startPlasmaSession.start(QStringLiteral(CMAKE_INSTALL_FULL_BINDIR "/plasma_session"), plasmaSessionOptions); e.exec(); return rc; } void waitForKonqi() { const KConfig cfg(QStringLiteral("startkderc")); const KConfigGroup grp = cfg.group("WaitForDrKonqi"); bool wait_drkonqi = grp.readEntry("Enabled", true); if (wait_drkonqi) { // wait for remaining drkonqi instances with timeout (in seconds) const int wait_drkonqi_timeout = grp.readEntry("Timeout", 900) * 1000; QElapsedTimer wait_drkonqi_counter; wait_drkonqi_counter.start(); QStringList services = allServices(QLatin1String("org.kde.drkonqi-")); while (!services.isEmpty()) { sleep(5); services = allServices(QLatin1String("org.kde.drkonqi-")); if (wait_drkonqi_counter.elapsed() >= wait_drkonqi_timeout) { // ask remaining drkonqis to die in a graceful way for (const auto &service: services) { QDBusInterface iface(service, QStringLiteral("/MainApplication")); iface.call(QStringLiteral("quit")); } break; } } } }