diff --git a/ksmserver/README b/ksmserver/README index 698c932d6..bb419b933 100644 --- a/ksmserver/README +++ b/ksmserver/README @@ -1,162 +1,161 @@ KDE session manager (ksmserver) -------------------------------- Matthias Ettrich Lubos Lunak ksmserver is KDE's session management server. It talks the standard X11R6 session management protocol (XSMP). Thus, in theory, it should be compatible with all session managment compliant X11R6 applications. Unfortunately, there aren't that many of them. To be precise, I have never seen a single commercial application that supports it and even within the official X11R6 distribution, 'xclock' is the only exception. Nevertheless we've chosen to support XSMP despites the complexity of the protocol in order to provide KDE users more interoperability with applications that were not explicitely written with KDE in mind. XSMP, as an official X standard, promised to be more likely to be supported by third parties than even a superior KDE-specific protocol. Let's see whether we were right and more applications will actually talk XSMP. At least all KDE applications do now. Here's a short overview on how session management works. Contents -------- Starting the server KDE startup sequence Establishing the connection Authorization Requesting a shutdown Starting the server ------------------- The server is usually started from the 'startkde' script. It supports the following options: -r, --restore Restores the previous session if available -w, --windowmanager Starts 'wm' in case no other window manager is participating in the session. Default is 'kwin' The default 'startkde' launches 'ksmserver --restore'. The 'windowmanager' option is useful for users that prefer a window manager other than kwin. Since the window manager has to participate in the session (it has to remember window positions and states), it is usually restarted when the session is restored. To be *really* sure that this happens, even if the wm might have crashed during the previous session, ksmserver ensures that. The option specifies, which windowmanager shall be launched for sure. But again: if the stored session contains a window manager, the restored one will be used, not the specified one. As a special feature, ksmserver always starts the specified window manager first, which results in a much nicer startup sequence (less flashy). KDE startup sequence -------------------- Ksmserver controls the second part of the KDE startup sequence, after it gets control from the startkde script, which controls the first part of the startup sequence. All code related to startup should be in startup.cpp and going down in that source file should follow the startup order (but note that this is just a documentation which may get outdated, so in case of doubts the source wins ;) ). The startkde scripts already launches kdeinit, which in turns launches KDE daemons like dbus daemon, klauncher and kded. Kded loads autoloaded kded modules, i.e. those that have X-KDE-Kded-autoload=true in .desktop files. The exact way autoloading works is controlled by X-KDE-Kded-phase=, which may be 0, 1 or 2 (the default). Kded phase 0 means the module is always loaded by kded, even outside of KDE session. It should used only by kded modules which must be always running. Kded phase 1 modules are loaded right after kded startup, but only during KDE startup, i.e. it is for modules that are always needed by the KDE session. Phase 2 modules will be loaded later. [More information about kded modules could be found in kdelibs/kded/HOWTO] Startkde also launches kcminit, which performs initialization done by kcontrol -modules. There are three kcminit phases, 0, 1 and 2, controlled +modules. There are two kcminit phases, 0, 1, controlled by X-KDE-Init-Phase= in the .desktop file, which defaults to 1. Phase 0 kcminit modules should be only those that really need to be run early in the startup process (and those should probably actually use kstartupconfig in startkde to be done even before kdeinit and daemons). After executing phase 0 modules kcminit returns and waits. When ksmserver is launched, the first thing it does is launching the window manager, as the WM is necessary before any windows are possibly shown. When the WM is ready, ksmserver tells klauncher to perform autostart phase 0 ($KDEHOME/share/autostart). There are 3 autostart phases, 0, 1 and 2, defined by X-KDE-autostart-phase, defaulting to 2. Phase 0 is reserved only for the actual visible base components of KDE, i.e. Plasma, in order to make the startup appear visually faster. Plasma uses D-Bus calls suspendStartup() and resumeStartup() to make ksmserver stay waiting for autostart phase 0 until Plasma is ready (note: suspendStartup() needs to be called before the application registers with ksmserver, i.e. before KApplication ctor is called). Next step is telling the waiting kcminit to perform phase 1 - all kcminit modules that should be executed before KDE startup is considered done. After that ksmserver tells klauncher to perform autostart phase 1, i.e. launching normal components of KDE that should be available right after KDE startup, and after this session restore is performed, i.e. launching all applications that were running during last session saving (usually logout). By this time KDE session is considered to be more or less ready and ksmserver does the knotify startkde event (i.e. plays the login sound). It also tells klauncher to perform autostart phase 2, kded to load all -remaining autoload (i.e. kded phase 2) modules, kcminit to execute -kcminit phase 2 (kcontrol modules that do initialization that can wait, -like launching daemons) and it itself executes the user Autostart folder. +remaining autoload (i.e. kded phase 2) modules, and it itself executes +the user Autostart folder. Technical note: There's a reason why kded modules and items in autostart default to the latest phase. Before you explicitly use a different phase, read and understand what's above. You should also consider whether something really needs to be launched during KDE startup and can't be loaded on-demand when really needed. Abusing the phases will result in public spanking for making KDE startup slower. Establishing the connection --------------------------- As required by the XSMP specification, the session management server propagates its network address in the SESSION_MANAGER environment variable. Probably not the best way to do it, but it's the way it is. All KDE (and plain Qt) applications simply read this variable and try to establish a connection to an XSMP server at this address. If the variable is undefined, nothing happens. This means, if you want to start a program that should not participate in the session, simply undefine SESSION_MANAGER in your terminal window and launch the application. If you want to see an application desparately trying to connect to something, try setting it to some bogus value. In addition, ksmserver propagates both its network address and its process id in ~/.kde/socket-$HOSTNAME/KSMserver-$DISPLAY. A utility function KWorkSpace::propagateSessionManager() reads this setting and sets SESSION_MANAGER accordingly, so that child processes can pick it up. The function is called by clients that are started outside the session (i.e. before ksmserver is started), but want to launch other processes that should participate in the session. Authorization ------------- XSMP is built on top of the Inter Client Exchange (ICE) protocol, which comes standard as a part of X11R6 and later. Authorization is done using 'iceauth', with MIT-MAGIC-COOKIE as used by X. In order to be able to access the session management server, you need to be able to read ~/.ICEauthority. For security reasons, we do not provide any host-based authorization. Requesting a shutdown --------------------- If an application wants to request a shutdown (i.e. a logout), it does this via an SmcRequestSaveYourself message to the server. In KDE, a utility function KWorkSpace::requestShutDown() does exactly this. It's for example called by KDE's panel or by the context menu of the desktop. diff --git a/startkde/kcminit/main.cpp b/startkde/kcminit/main.cpp index 5b0912b25..54261eadc 100644 --- a/startkde/kcminit/main.cpp +++ b/startkde/kcminit/main.cpp @@ -1,252 +1,251 @@ /* Copyright (c) 1999 Matthias Hoelzer-Kluepfel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "main.h" #include "klauncher_iface.h" #ifdef XCB_FOUND #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int ready[ 2 ]; static bool startup = false; static void sendReady() { if( ready[ 1 ] == -1 ) return; char c = 0; write( ready[ 1 ], &c, 1 ); close( ready[ 1 ] ); ready[ 1 ] = -1; } static void waitForReady() { char c = 1; close( ready[ 1 ] ); read( ready[ 0 ], &c, 1 ); close( ready[ 0 ] ); } bool KCMInit::runModule(const QString &libName, KService::Ptr service) { QString KCMINIT_PREFIX=QStringLiteral("kcminit_"); const QVariant tmp = service->property(QStringLiteral("X-KDE-Init-Symbol"), QVariant::String); QString kcminit; if( tmp.isValid() ) { kcminit = tmp.toString(); if( !kcminit.startsWith( KCMINIT_PREFIX ) ) kcminit = KCMINIT_PREFIX + kcminit; } else kcminit = KCMINIT_PREFIX + libName; QString path = KPluginLoader::findPlugin(libName); if (path.isEmpty()) { path = KPluginLoader::findPlugin(QStringLiteral("kcms/") + libName); } if (path.isEmpty()) { qWarning() << "Module" << libName << "was not found"; return false; } // get the kcminit_ function QFunctionPointer init = QLibrary::resolve(path, kcminit.toUtf8().constData()); if (!init) { qWarning() << "Module" << libName << "does not actually have a kcminit function"; return false; } // initialize the module qDebug() << "Initializing " << libName << ": " << kcminit; init(); return true; } void KCMInit::runModules( int phase ) { QString KCMINIT_PREFIX=QStringLiteral("kcminit_"); for (const KService::Ptr & service : qAsConst(m_list)) { const QVariant tmp = service->property(QStringLiteral("X-KDE-Init-Library"), QVariant::String); QString library; if( tmp.isValid() ) { library = tmp.toString(); if( !library.startsWith( KCMINIT_PREFIX ) ) library = KCMINIT_PREFIX + library; } else { library = service->library(); } if (library.isEmpty()) { qWarning() << Q_FUNC_INFO << "library is empty, skipping"; continue; // Skip } // see ksmserver's README for the description of the phases const QVariant vphase = service->property(QStringLiteral("X-KDE-Init-Phase"), QVariant::Int ); int libphase = 1; if( vphase.isValid() ) libphase = vphase.toInt(); + if (libphase > 1) { + libphase = 1; + } + if( phase != -1 && libphase != phase ) continue; // try to load the library if (!m_alreadyInitialized.contains(library)) { runModule(library, service); m_alreadyInitialized.insert(library); } } } KCMInit::KCMInit( const QCommandLineParser& args ) { QString arg; if (args.positionalArguments().size() == 1) { arg = args.positionalArguments().first(); } if (args.isSet(QStringLiteral("list"))) { m_list = KServiceTypeTrader::self()->query( QStringLiteral("KCModuleInit") ); for (const KService::Ptr &service : qAsConst(m_list)) { if (service->library().isEmpty()) continue; // Skip printf("%s\n", QFile::encodeName(service->desktopEntryName()).data()); } return; } if (!arg.isEmpty()) { QString module = arg; if (!module.endsWith(QLatin1String(".desktop"))) module += QLatin1String(".desktop"); KService::Ptr serv = KService::serviceByStorageId( module ); if ( !serv || serv->library().isEmpty() ) { qCritical() << i18n("Module %1 not found", module); return; } else { m_list.append(serv); } } else { // locate the desktop files m_list = KServiceTypeTrader::self()->query( QStringLiteral("KCModuleInit") ); } if( startup ) { runModules( 0 ); // Tell KSplash that KCMInit has started QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("kcminit")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); sendReady(); QTimer::singleShot( 300 * 1000, qApp, &QCoreApplication::quit); // just in case QDBusConnection::sessionBus().registerObject(QStringLiteral("/kcminit"), this, QDBusConnection::ExportScriptableContents); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kcminit")); - qApp->exec(); // wait for runPhase1() and runPhase2() + qApp->exec(); // wait for runPhase1() } else runModules( -1 ); // all phases } KCMInit::~KCMInit() { sendReady(); } void KCMInit::runPhase1() { runModules( 1 ); -} - -void KCMInit::runPhase2() -{ - runModules( 2 ); - qApp->exit( 0 ); + qApp->exit(0); } extern "C" Q_DECL_EXPORT int kdemain(int argc, char *argv[]) { // kdeinit waits for kcminit to finish, but during KDE startup // only important kcm's are started very early in the login process, // the rest is delayed, so fork and make parent return after the initial phase pipe( ready ); if( fork() != 0 ) { waitForReady(); return 0; } close( ready[ 0 ] ); startup = ( strcmp( argv[ 0 ], "kcminit_startup" ) == 0 ); // started from startkde? KWorkSpace::detectPlatform(argc, argv); QGuiApplication::setDesktopSettingsAware(false); QGuiApplication app(argc, argv); //gui is needed for several modules KLocalizedString::setApplicationDomain("kcminit"); KAboutData about(QStringLiteral("kcminit"), i18n("KCMInit"), QString(), i18n("KCMInit - runs startup initialization for Control Modules."), KAboutLicense::GPL); KAboutData::setApplicationData(about); QCommandLineParser parser; about.setupCommandLine(&parser); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("list"), i18n("List modules that are run at startup"))); parser.addPositionalArgument(QStringLiteral("module"), i18n("Configuration module to run")); parser.process(app); about.processCommandLine(&parser); KCMInit kcminit( parser ); return 0; } diff --git a/startkde/kcminit/main.h b/startkde/kcminit/main.h index 4e341f62d..ffb4da840 100644 --- a/startkde/kcminit/main.h +++ b/startkde/kcminit/main.h @@ -1,46 +1,45 @@ /* Copyright (c) 1999 Matthias Hoelzer-Kluepfel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MAIN_H #define MAIN_H #include #include #include class KCMInit : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KCMInit") public Q_SLOTS: //dbus Q_SCRIPTABLE void runPhase1(); - Q_SCRIPTABLE void runPhase2(); public: explicit KCMInit( const QCommandLineParser& args ); ~KCMInit() override; private: bool runModule(const QString &libName, KService::Ptr service); void runModules( int phase ); KService::List m_list; QSet m_alreadyInitialized; }; #endif // MAIN_H diff --git a/startkde/plasma-session/startup.cpp b/startkde/plasma-session/startup.cpp index 232e37d04..7059a6d5a 100644 --- a/startkde/plasma-session/startup.cpp +++ b/startkde/plasma-session/startup.cpp @@ -1,432 +1,426 @@ /***************************************************************** 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" 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(1)); + 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()); - addSubjob(new KCMInitJob(2)); 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")); const AutoStart autostart; auto phase0 = new StartupPhase0(autostart, this); auto phase1 = new StartupPhase1(autostart, this); auto phase2 = new StartupPhase2(autostart, this); auto restoreSession = new RestoreSessionJob(); // this includes starting kwin (currently) // forward our arguments into ksmserver to match startplasma expectations QStringList arguments = qApp->arguments(); arguments.removeFirst(); auto ksmserverJob = new StartServiceJob(QStringLiteral("ksmserver"), arguments, QStringLiteral("org.kde.ksmserver")); connect(ksmserverJob, &KJob::finished, phase0, &KJob::start); connect(phase0, &KJob::finished, phase1, &KJob::start); connect(phase1, &KJob::finished, restoreSession, &KJob::start); connect(restoreSession, &KJob::finished, phase2, &KJob::start); upAndRunning(QStringLiteral("ksmserver")); connect(phase1, &KJob::finished, this, []() { NotificationThread *loginSound = new NotificationThread(); connect(loginSound, &NotificationThread::finished, loginSound, &NotificationThread::deleteLater); loginSound->start();}); connect(phase2, &KJob::finished, this, &Startup::finishStartup); ksmserverJob->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")); } void Startup::updateLaunchEnv(const QString &key, const QString &value) { qputenv(key.toLatin1(), value.toLatin1()); } -KCMInitJob::KCMInitJob(int phase) - :m_phase(phase) +KCMInitJob::KCMInitJob() + : KJob() { } void KCMInitJob::start() { org::kde::KCMInit kcminit(QStringLiteral("org.kde.kcminit"), QStringLiteral("/kcminit"), QDBusConnection::sessionBus()); kcminit.setTimeout(10 * 1000); - QDBusPendingReply pending; - if (m_phase == 1) { - pending = kcminit.runPhase1(); - } else { - pending = kcminit.runPhase2(); - } + 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): KJob(), m_process(process), m_args(args) { auto watcher = new QDBusServiceWatcher(serviceId, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &StartServiceJob::emitResult); } void StartServiceJob::start() { QProcess::startDetached(m_process, m_args); } #include "startup.moc" diff --git a/startkde/plasma-session/startup.h b/startkde/plasma-session/startup.h index bac744648..33e5e23af 100644 --- a/startkde/plasma-session/startup.h +++ b/startkde/plasma-session/startup.h @@ -1,105 +1,103 @@ /***************************************************************** ksmserver - the KDE session management server Copyright 2018 David Edmundson 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. ******************************************************************/ #pragma once #include #include #include "autostart.h" class Startup : public QObject { Q_OBJECT public: Startup(QObject *parent); void upAndRunning( const QString& msg ); void finishStartup(); public Q_SLOTS: // alternatively we could drop this and have a rule that we /always/ launch everything through klauncher // need resolution from frameworks discussion on kdeinit void updateLaunchEnv(const QString &key, const QString &value); private: void autoStart(int phase); }; class SleepJob: public KJob { Q_OBJECT public: SleepJob(); void start() override; }; class KCMInitJob: public KJob { Q_OBJECT public: - KCMInitJob(int phase); + KCMInitJob(); void start() override; -private: - int m_phase; }; class KDEDInitJob: public KJob { Q_OBJECT public: KDEDInitJob(); void start() override; }; class AutoStartAppsJob: public KJob { Q_OBJECT public: AutoStartAppsJob(const AutoStart &autoStart, int phase); void start() override; private: AutoStart m_autoStart; }; /** * Launches a process, and waits for the service to appear on the session bus */ class StartServiceJob: public KJob { Q_OBJECT public: StartServiceJob(const QString &process, const QStringList &args, const QString &serviceId); void start() override; private: const QString m_process; const QStringList m_args; }; class RestoreSessionJob: public KJob { Q_OBJECT public: RestoreSessionJob(); void start() override; private: };