diff --git a/startkde/CMakeLists.txt b/startkde/CMakeLists.txt index 34e0ef4c23d1b47132889d53603eb08c4c718d0c..2b1da5f3d401d843e0264c4e38fd0ad638e3d624 100644 --- a/startkde/CMakeLists.txt +++ b/startkde/CMakeLists.txt @@ -14,42 +14,33 @@ qt_add_dbus_interface( ${CMAKE_SOURCE_DIR}/ksplash/ksplashqml/org.kde.KSplash.xml ksplashinterface ) +ecm_qt_declare_logging_category(startplasma_SRCS HEADER debug.h IDENTIFIER PLASMA_STARTUP CATEGORY_NAME org.kde.startup) - -set(START_PLASMA_COMMON_SRCS startplasma.cpp) - -ecm_qt_declare_logging_category(START_PLASMA_COMMON_SRCS HEADER debug.h IDENTIFIER PLASMA_STARTUP CATEGORY_NAME org.kde.startup) - -add_executable(startplasma-x11 ${START_PLASMA_COMMON_SRCS} startplasma-x11.cpp kcheckrunning/kcheckrunning.cpp ${startplasma_SRCS}) -add_executable(startplasma-wayland ${START_PLASMA_COMMON_SRCS} startplasma-wayland.cpp ${startplasma_SRCS}) -add_executable(startplasma-waylandsession ${START_PLASMA_COMMON_SRCS} startplasma-waylandsession.cpp ${startplasma_SRCS}) - -target_link_libraries(startplasma-x11 PRIVATE +add_library(startplasma OBJECT startplasma.cpp ${startplasma_SRCS}) +target_link_libraries(startplasma PUBLIC Qt::Core Qt::DBus KF5::ConfigCore KF5::Notifications + ${PHONON_LIBRARIES} PW::KWorkspace +) + +add_executable(startplasma-x11 ${START_PLASMA_COMMON_SRCS} startplasma-x11.cpp kcheckrunning/kcheckrunning.cpp) +add_executable(startplasma-wayland ${START_PLASMA_COMMON_SRCS} startplasma-wayland.cpp) +add_executable(startplasma-waylandsession ${START_PLASMA_COMMON_SRCS} startplasma-waylandsession.cpp) + +target_link_libraries(startplasma-x11 PRIVATE + startplasma X11::X11 # for kcheckrunning - ${PHONON_LIBRARIES} ) target_link_libraries(startplasma-wayland PRIVATE - Qt::Core - Qt::DBus - KF5::ConfigCore - KF5::Notifications - PW::KWorkspace - ${PHONON_LIBRARIES} + startplasma ) target_link_libraries(startplasma-waylandsession PRIVATE - Qt::Core - Qt::DBus - KF5::ConfigCore - KF5::Notifications - PW::KWorkspace - ${PHONON_LIBRARIES} + startplasma ) add_subdirectory(plasma-session) add_subdirectory(plasma-shutdown) diff --git a/startkde/plasma-session/CMakeLists.txt b/startkde/plasma-session/CMakeLists.txt index f93868b9dc83444617f34618849a5294bebc114d..3bfb198723667c56901e74246dc54e1dfe3e33dd 100644 --- a/startkde/plasma-session/CMakeLists.txt +++ b/startkde/plasma-session/CMakeLists.txt @@ -4,6 +4,8 @@ set(plasma_session_SRCS main.cpp autostart.cpp startup.cpp + sessiontrack.cpp + signalhandler.cpp ) ecm_qt_declare_logging_category(plasma_session_SRCS HEADER debug.h IDENTIFIER PLASMA_SESSION CATEGORY_NAME org.kde.plasma.session) @@ -20,14 +22,10 @@ qt_add_dbus_interface( plasma_session_SRCS ../../ksmserver/org.kde.KSMServerInte add_executable(plasma_session ${plasma_session_SRCS}) +target_include_directories(plasma_session PRIVATE ${CMAKE_SOURCE_DIR}/startkde ${CMAKE_BINARY_DIR}/startkde) target_link_libraries(plasma_session - Qt::Core - Qt::DBus - KF5::ConfigCore - KF5::Service - KF5::CoreAddons + startplasma KF5::KIOCore - PW::KWorkspace PlasmaAutostart ) diff --git a/startkde/plasma-session/sessiontrack.cpp b/startkde/plasma-session/sessiontrack.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3d2c821c54d68056d0b714d6cfa5743a2a273c74 --- /dev/null +++ b/startkde/plasma-session/sessiontrack.cpp @@ -0,0 +1,47 @@ +/* + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.0-only +*/ + +#include "sessiontrack.h" +#include "signalhandler.h" +#include +#include +#include +#include + +SessionTrack::SessionTrack(const QVector &processes) + : m_processes(processes) +{ + SignalHandler::self()->addSignal(SIGTERM); + connect(SignalHandler::self(), &SignalHandler::signalReceived, QCoreApplication::instance(), [](int signal) { + if (signal == SIGTERM) { + QCoreApplication::instance()->exit(0); + } + }); + + for (auto process : std::as_const(m_processes)) { + connect(process, QOverload::of(&QProcess::finished), this, [this] { + m_processes.removeAll(static_cast(sender())); + }); + } + + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &SessionTrack::deleteLater); +} + +SessionTrack::~SessionTrack() +{ + disconnect(this, nullptr, QCoreApplication::instance(), nullptr); + + for (auto process : std::as_const(m_processes)) { + process->terminate(); + } + for (auto process : std::as_const(m_processes)) { + if (process->state() == QProcess::Running && !process->waitForFinished(500)) { + process->kill(); + } else { + delete process; + } + } +} diff --git a/startkde/plasma-session/sessiontrack.h b/startkde/plasma-session/sessiontrack.h new file mode 100644 index 0000000000000000000000000000000000000000..0983c177076ed62ca61083b27d771b1d39c78413 --- /dev/null +++ b/startkde/plasma-session/sessiontrack.h @@ -0,0 +1,22 @@ +/* + SPDX-FileCopyrightText: 2018 David Edmundson + + SPDX-License-Identifier: LGPL-2.0-only +*/ + +#pragma once + +#include +#include + +class SessionTrack : public QObject +{ + Q_OBJECT +public: + SessionTrack(const QVector &processes); + ~SessionTrack() override; + +private: + QVector m_processes; + QEventLoopLocker m_lock; +}; diff --git a/startkde/plasma-session/signalhandler.cpp b/startkde/plasma-session/signalhandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bc7eb09a485a7d768c1db756d4861353f47c1a46 --- /dev/null +++ b/startkde/plasma-session/signalhandler.cpp @@ -0,0 +1,60 @@ +/* + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.0-only +*/ + +#include "signalhandler.h" +#include +#include +#include +#include + +int SignalHandler::signalFd[2]; + +SignalHandler::SignalHandler() +{ + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) { + qWarning() << "Couldn't create a socketpair"; + return; + } + + m_handler = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); + connect(m_handler, &QSocketNotifier::activated, this, &SignalHandler::handleSignal); +} + +SignalHandler::~SignalHandler() +{ + for (int sig : std::as_const(m_signalsRegistered)) { + signal(sig, nullptr); + } + close(signalFd[0]); + close(signalFd[1]); +} + +void SignalHandler::addSignal(int signalToTrack) +{ + m_signalsRegistered.insert(signalToTrack); + signal(signalToTrack, signalHandler); +} + +void SignalHandler::signalHandler(int signal) +{ + ::write(signalFd[0], &signal, sizeof(signal)); +} + +void SignalHandler::handleSignal() +{ + m_handler->setEnabled(false); + int signal; + ::read(signalFd[1], &signal, sizeof(signal)); + m_handler->setEnabled(true); + + Q_EMIT signalReceived(signal); +} + +SignalHandler *SignalHandler::self() +{ + static SignalHandler s_self; + return &s_self; +} diff --git a/startkde/plasma-session/signalhandler.h b/startkde/plasma-session/signalhandler.h new file mode 100644 index 0000000000000000000000000000000000000000..c0f8428b47bee1b78c733761e8bcac352810da11 --- /dev/null +++ b/startkde/plasma-session/signalhandler.h @@ -0,0 +1,38 @@ +/* + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.0-only +*/ + +#pragma once + +#include +#include +#include + +/** + * Class to be able to receive ANSI C signals and forward them onto the Qt eventloop + * + * It's a singleton as it relies on static data getting defined. + */ +class SignalHandler : public QObject +{ + Q_OBJECT +public: + ~SignalHandler() override; + void addSignal(int signal); + + static SignalHandler *self(); + +Q_SIGNALS: + void signalReceived(int signal); + +private: + SignalHandler(); + void handleSignal(); + static void signalHandler(int signal); + + QSet m_signalsRegistered; + static int signalFd[2]; + QSocketNotifier *m_handler = nullptr; +}; diff --git a/startkde/plasma-session/startup.cpp b/startkde/plasma-session/startup.cpp index 270744053e3c0488db516e9c53f8d991d03ffc76..fa7231abed699799a1588e67d552b0e5dee80db7 100644 --- a/startkde/plasma-session/startup.cpp +++ b/startkde/plasma-session/startup.cpp @@ -16,6 +16,8 @@ #include "debug.h" +#include + #include "kcminit_interface.h" #include "kded_interface.h" #include "ksmserver_interface.h" @@ -36,9 +38,11 @@ #include #include +#include "sessiontrack.h" #include "startupadaptor.h" #include "../config-startplasma.h" +#include "startplasma.h" class Phase : public KCompositeJob { @@ -136,6 +140,8 @@ void SleepJob::start() Startup::Startup(QObject *parent) : QObject(parent) { + Q_ASSERT(!s_self); + s_self = this; new StartupAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Startup"), QStringLiteral("org.kde.Startup"), this); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Startup")); @@ -163,6 +169,8 @@ Startup::Startup(QObject *parent) } KJob *phase1 = nullptr; + m_lock.reset(new QEventLoopLocker); + const QVector sequence = { new StartProcessJob(QStringLiteral("kcminit_startup"), {}), new StartServiceJob(QStringLiteral("kded5"), {}, QStringLiteral("org.kde.kded5"), {}), @@ -204,6 +212,10 @@ void Startup::finishStartup() { qCDebug(PLASMA_SESSION) << "Finished"; upAndRunning(QStringLiteral("ready")); + + playStartupSound(this); + new SessionTrack(m_processes); + deleteLater(); } void Startup::updateLaunchEnv(const QString &key, const QString &value) @@ -211,6 +223,26 @@ void Startup::updateLaunchEnv(const QString &key, const QString &value) qputenv(key.toLatin1(), value.toLatin1()); } +bool Startup::startDetached(const QString &program, const QStringList &args) +{ + QProcess *p = new QProcess(); + p->setProgram(program); + p->setArguments(args); + return startDetached(p); +} + +bool Startup::startDetached(QProcess *process) +{ + process->start(); + const bool ret = process->waitForStarted(); + if (ret) { + m_processes << process; + } + return ret; +} + +Startup *Startup::s_self = nullptr; + KCMInitJob::KCMInitJob() : KJob() { @@ -336,7 +368,7 @@ void AutoStartAppsJob::start() } qCInfo(PLASMA_SESSION) << "Starting autostart service " << serviceName << arguments; auto program = arguments.takeFirst(); - if (!QProcess::startDetached(program, arguments)) + if (!Startup::self()->startDetached(program, arguments)) qCWarning(PLASMA_SESSION) << "could not start" << serviceName << ":" << program << arguments; } while (true); }); @@ -344,7 +376,7 @@ void AutoStartAppsJob::start() StartServiceJob::StartServiceJob(const QString &process, const QStringList &args, const QString &serviceId, const QProcessEnvironment &additionalEnv) : KJob() - , m_process(new QProcess(this)) + , m_process(new QProcess) , m_serviceId(serviceId) , m_additionalEnv(additionalEnv) { @@ -367,7 +399,7 @@ void StartServiceJob::start() return; } qCDebug(PLASMA_SESSION) << "Starting " << m_process->program() << m_process->arguments(); - if (!m_process->startDetached()) { + if (!Startup::self()->startDetached(m_process)) { qCWarning(PLASMA_SESSION) << "error starting process" << m_process->program() << m_process->arguments(); emitResult(); } diff --git a/startkde/plasma-session/startup.h b/startkde/plasma-session/startup.h index a5e1476b09215c7aec3fd9fda6f7082d5ff86081..da3ab8421b2f231e854724f4c4befba79245289a 100644 --- a/startkde/plasma-session/startup.h +++ b/startkde/plasma-session/startup.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include @@ -21,6 +22,16 @@ public: Startup(QObject *parent); void upAndRunning(const QString &msg); void finishStartup(); + + static Startup *self() + { + Q_ASSERT(s_self); + return s_self; + } + + bool startDetached(const QString &program, const QStringList &args); + bool startDetached(QProcess *process); + 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 @@ -28,6 +39,10 @@ public Q_SLOTS: private: void autoStart(int phase); + + QVector m_processes; + QScopedPointer m_lock; + static Startup *s_self; }; class SleepJob : public KJob diff --git a/startkde/startplasma-wayland.cpp b/startkde/startplasma-wayland.cpp index 788ff3089a9092513232210bb47d9c01b6dbcdf6..e09b290d2287ed648b647acc81dcbedc59bfbe67 100644 --- a/startkde/startplasma-wayland.cpp +++ b/startkde/startplasma-wayland.cpp @@ -9,6 +9,7 @@ #include #include #include +#include int main(int argc, char **argv) { @@ -16,6 +17,7 @@ int main(int argc, char **argv) createConfigDirectory(); setupCursor(true); + signal(SIGTERM, sigtermHandler); { KConfig fonts(QStringLiteral("kcmfonts")); diff --git a/startkde/startplasma-waylandsession.cpp b/startkde/startplasma-waylandsession.cpp index 7f916e40936a5fc9d7da04ddb51771502eb241bb..5ddd7ef60420e3d5cb5234010b9982a2c9d2291a 100644 --- a/startkde/startplasma-waylandsession.cpp +++ b/startkde/startplasma-waylandsession.cpp @@ -5,10 +5,12 @@ */ #include "startplasma.h" +#include int main(int argc, char **argv) { QCoreApplication app(argc, argv); + signal(SIGTERM, sigtermHandler); QScopedPointer ksplash; @@ -16,7 +18,7 @@ int main(int argc, char **argv) ksplash.reset(setupKSplash()); } - out << "startplasma-waylandsession: Starting up..."; + out << "startplasma-waylandsession: Starting up...\n"; if (!syncDBusEnvironment()) { out << "Could not sync environment to dbus.\n"; diff --git a/startkde/startplasma.cpp b/startkde/startplasma.cpp index 6e6141b48b4c14c69e1d363fcb5351e9a1989469..5beddd43c57ee709e5fd243e490c47021b6487d9 100644 --- a/startkde/startplasma.cpp +++ b/startkde/startplasma.cpp @@ -36,6 +36,14 @@ QTextStream out(stderr); +void sigtermHandler(int signalNumber) +{ + Q_UNUSED(signalNumber) + if (QCoreApplication::instance()) { + QCoreApplication::instance()->exit(-1); + } +} + void messageBox(const QString &text) { out << text; @@ -66,6 +74,24 @@ QStringList allServices(const QLatin1String &prefix) return names; } +void gentleTermination(QProcess *p) +{ + if (p->state() != QProcess::Running) { + return; + } + + p->close(); + p->terminate(); + + // Wait longer for a session than a greeter + if (!p->waitForFinished(5000)) { + p->kill(); + if (!p->waitForFinished(5000)) { + qWarning() << "Could not fully finish the process" << p->program(); + } + } +} + int runSync(const QString &program, const QStringList &args, const QStringList &env) { QProcess p; @@ -73,6 +99,10 @@ int runSync(const QString &program, const QStringList &args, const QStringList & p.setEnvironment(QProcess::systemEnvironment() << env); p.setProcessChannelMode(QProcess::ForwardedChannels); p.start(program, args); + + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &p, [&p] { + gentleTermination(&p); + }); // qCDebug(PLASMA_STARTUP) << "started..." << program << args; p.waitForFinished(-1); if (p.exitCode()) { @@ -512,9 +542,10 @@ bool startPlasmaSession(bool wayland) // Create .desktop files for the scripts in .config/autostart-scripts migrateUserScriptsAutostart(); + QScopedPointer startPlasmaSession; if (!useSystemdBoot()) { + startPlasmaSession.reset(new QProcess); qCDebug(PLASMA_STARTUP) << "Using classic boot"; - QProcess startPlasmaSession; QStringList plasmaSessionOptions; if (wayland) { @@ -525,19 +556,19 @@ bool startPlasmaSession(bool wayland) } } - startPlasmaSession.setProcessChannelMode(QProcess::ForwardedChannels); - 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(); - } - }); - - startPlasmaSession.start(QStringLiteral(CMAKE_INSTALL_FULL_BINDIR "/plasma_session"), plasmaSessionOptions); - // plasma-session starts everything else up then quits - rc = startPlasmaSession.waitForFinished(120 * 1000); + startPlasmaSession->setProcessChannelMode(QProcess::ForwardedChannels); + QObject::connect(startPlasmaSession.data(), + QOverload::of(&QProcess::finished), + &e, + [&rc](int exitCode, QProcess::ExitStatus) { + if (exitCode == 255) { + // Startup error + messageBox(QStringLiteral("startkde: Could not start plasma_session. Check your installation.\n")); + rc = false; + } + }); + + startPlasmaSession->start(QStringLiteral(CMAKE_INSTALL_FULL_BINDIR "/plasma_session"), plasmaSessionOptions); } else { qCDebug(PLASMA_STARTUP) << "Using systemd boot"; const QString platform = wayland ? QStringLiteral("wayland") : QStringLiteral("x11"); @@ -552,10 +583,12 @@ bool startPlasmaSession(bool wayland) qWarning() << "Could not start systemd managed Plasma session:" << reply.error().name() << reply.error().message(); messageBox(QStringLiteral("startkde: Could not start Plasma session.\n")); rc = false; + } else { + playStartupSound(&e); } } if (rc) { - playStartupSound(e); + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &e, &QEventLoop::quit); e.exec(); } return rc; @@ -631,7 +664,7 @@ static void migrateUserScriptsAutostart() QDBusConnection::sessionBus().call(message); } -static void playStartupSound(QObject &parent) +void playStartupSound(QObject *parent) { KNotifyConfig notifyConfig(QStringLiteral("plasma_workspace"), QList>(), QStringLiteral("startkde")); const QString action = notifyConfig.readEntry(QStringLiteral("Action")); @@ -639,7 +672,7 @@ static void playStartupSound(QObject &parent) // no startup sound configured return; } - Phonon::AudioOutput *audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory, &parent); + Phonon::AudioOutput *audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory, parent); QString soundFilename = notifyConfig.readEntry(QStringLiteral("Sound")); if (soundFilename.isEmpty()) { @@ -665,8 +698,10 @@ static void playStartupSound(QObject &parent) return; } - Phonon::MediaObject *mediaObject = new Phonon::MediaObject(&parent); + Phonon::MediaObject *mediaObject = new Phonon::MediaObject(parent); Phonon::createPath(mediaObject, audioOutput); + QObject::connect(mediaObject, &Phonon::MediaObject::finished, audioOutput, &QObject::deleteLater); + QObject::connect(mediaObject, &Phonon::MediaObject::finished, mediaObject, &QObject::deleteLater); mediaObject->setCurrentSource(soundURL); mediaObject->play(); diff --git a/startkde/startplasma.h b/startkde/startplasma.h index dfac0f761cb0c44ea1615fd35e2aa7b3c8068b33..230c0c8b199ef642abb2d0fa5bc6d3ccd7acf06a 100644 --- a/startkde/startplasma.h +++ b/startkde/startplasma.h @@ -13,6 +13,7 @@ extern QTextStream out; +void sigtermHandler(int signalNumber); QStringList allServices(const QLatin1String &prefix); int runSync(const QString &program, const QStringList &args, const QStringList &env = {}); void sourceFiles(const QStringList &files); @@ -39,13 +40,16 @@ static void resetSystemdFailedUnits(); static bool hasSystemdService(const QString &serviceName); static bool useSystemdBoot(); static void migrateUserScriptsAutostart(); -static void playStartupSound(QObject &parent); +void playStartupSound(QObject *parent); + +void gentleTermination(QProcess *process); struct KillBeforeDeleter { static inline void cleanup(QProcess *pointer) { - if (pointer) - pointer->kill(); + if (pointer) { + gentleTermination(pointer); + } delete pointer; } }; diff --git a/startkde/waitforname/main.cpp b/startkde/waitforname/main.cpp index 1f116515fb874576e1a36618f9f85e16c50b66ac..180fb0803463ee9ce9ae6bee1e684ac8697de0a6 100644 --- a/startkde/waitforname/main.cpp +++ b/startkde/waitforname/main.cpp @@ -5,10 +5,19 @@ */ #include "waiter.h" +#include +void sigtermHandler(int signalNumber) +{ + Q_UNUSED(signalNumber) + if (QCoreApplication::instance()) { + QCoreApplication::instance()->exit(-1); + } +} int main(int argc, char **argv) { Waiter app(argc, argv); + signal(SIGTERM, sigtermHandler); if (!app.waitForService()) { return 0;