Commit 256b6e17 authored by David Edmundson's avatar David Edmundson
Browse files

Systemd Startup

Summary:
This brings numerous advantages such as:

- easier admin configuration with drop-ins, overrides and multiple
hooks throughout (and only having to learn one tool)

- session cleanup on exit, avoiding that occasional part where shutdown
hangs

 - startup that actually knows when things are up

 - race free autostart and DBus activation at once

 - logs that rotate are split by service and usable

 - resource management through slices and cgroups (the part I want)

Over the past 2 years I've been trying to tidy up and encapsulate the
relevant parts of startup into the binary plasma-session so that we can
just runtime swap out that one part and supporting both paths will be
easy.

Support is toggleable via cmake flag, as it seems like it should be a
distro decision, especially as we will require a specific systemd with
the xdg-generator.

KDED/kwin/other services are attached to the relevant repo.

Task T11914

Differential Revision: https://phabricator.kde.org/D28305

squash
parent ef8f85f6
......@@ -33,6 +33,8 @@ include(ECMQueryQmake)
include(ECMInstallIcons)
include(KDEPackageAppTemplates)
include(KDEClangFormat)
include(ECMConfiguredInstall)
include(ECMGenerateDBusServiceFile)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
Plasma DocTools Runner Notifications NotifyConfig Su NewStuff Wallet
......@@ -182,6 +184,8 @@ add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
# locate qdbus in the Qt path because not every distro makes a symlink at /usr/bin/qdbus
query_qmake(QtBinariesDir QT_INSTALL_BINS)
option(PLASMA_SYSTEMD_BOOT "Use systemd units for startup of plasma (WIP)" FALSE)
add_subdirectory(doc)
add_subdirectory(libkworkspace)
add_subdirectory(libdbusmenuqt)
......
......@@ -47,3 +47,4 @@ target_link_libraries(gmenudbusmenuproxy
install(TARGETS gmenudbusmenuproxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES gmenudbusmenuproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
ecm_install_configured_files(INPUT plasma-gmenudbusmenuproxy.service.in @ONLY DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
......@@ -40,3 +40,4 @@ Type=Application
X-KDE-StartupNotify=false
OnlyShowIn=KDE;
X-KDE-autostart-phase=1
X-systemd-skip=true
[Unit]
Description=Proxies GTK DBus menus to a Plasma readable format
[Service]
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/gmenudbusmenuproxy
Restart=on-failure
Type=simple
Slice=background.slice
TimeoutSec=5sec
......@@ -5,11 +5,10 @@ set(krunner_SRCS
set(krunner_dbusAppXML dbus/org.kde.krunner.App.xml)
qt5_add_dbus_adaptor(krunner_SRCS ${krunner_dbusAppXML} view.h View)
configure_file(dbus/org.kde.krunner.service.in
${CMAKE_CURRENT_BINARY_DIR}/org.kde.krunner.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.krunner.service
DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
ecm_generate_dbus_service_file(NAME org.kde.krunner
EXECUTABLE ${KDE_INSTALL_FULL_BINDIR}/krunner
SYSTEMD_SERVICE plasma-krunner.service
DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
add_executable(krunner ${krunner_SRCS})
......@@ -41,4 +40,7 @@ configure_package_config_file(KRunnerAppDBusInterfaceConfig.cmake.in
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KRunnerAppDBusInterfaceConfig.cmake
DESTINATION ${CMAKECONFIG_INSTALL_DIR})
ecm_install_configured_files(INPUT plasma-krunner.service.in @ONLY DESTINATION
${SYSTEMD_USER_UNIT_INSTALL_DIR})
add_subdirectory(update)
[D-BUS Service]
Name=org.kde.krunner
Exec=@KDE_INSTALL_FULL_BINDIR@/krunner
[Unit]
Description=KRunner
[Service]
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/krunner
Type=dbus
BusName=org.kde.krunner
TimeoutSec=5sec
Slice=background.slice
Restart=no #as we're dbus activated anyway
......@@ -71,3 +71,5 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KSMServerDBusInterfaceConfig.cmake
########### install files ###############
install( FILES org.kde.KSMServerInterface.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR})
ecm_install_configured_files(INPUT plasma-ksmserver.service.in DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
[Unit]
Description=KDE Session Management Server
Wants=plasma-kcminit.service
[Service]
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/ksmserver
# This magic minus sign means don't fail if exit code is non-zero...
ExecStartPost=-@QtBinariesDir@/qdbus org.kde.KSplash /KSplash org.kde.KSplash.setStage ksmserver
BusName=org.kde.ksmserver
Slice=session.slice
[Install]
WantedBy=plasma-core.target
......@@ -2,4 +2,14 @@
source @CMAKE_INSTALL_FULL_LIBEXECDIR@/plasma-dev-prefix.sh
# This is a bit of a hack done because systemd starts in pam, and we only set our dev paths after all that is complete
# This copies everything into a transient runtime directory that systemd reads and reloads the units
if [ ! -z "$XDG_RUNTIME_DIR" ]
mkdir -p "$XDG_RUNTIME_DIR/systemd/user.control"
command cp -r @KDE_INSTALL_FULL_SYSTEMDUSERUNITDIR@/* $XDG_RUNTIME_DIR/systemd/user.control
systemctl --user daemon-reload
endif
startplasma$@
......@@ -100,6 +100,8 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop DESTINATIO
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
install( FILES dbus/org.kde.PlasmaShell.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} )
ecm_install_configured_files(INPUT plasma-plasmashell.service.in @ONLY DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
install(FILES
scripting/plasma-layouttemplate.desktop
DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR})
......
......@@ -60,6 +60,7 @@ OnlyShowIn=KDE;
X-KDE-autostart-phase=0
Icon=plasmashell
NoDisplay=true
X-systemd-skip=true
X-KDE-Wayland-Interfaces=org_kde_plasma_window_management,org_kde_kwin_keystate,zkde_screencast_unstable_v1
X-KDE-DBUS-Restricted-Interfaces=org.kde.kwin.Screenshot
[Unit]
Description=KDE Plasma Workspace
Wants=plasma-ksmserver.service plasma-kcminit.service
[Service]
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/plasmashell --no-respawn
Restart=on-failure
Type=dbus
BusName=org.kde.plasmashell
Slice=session.slice
TimeoutSec=40sec
[Install]
WantedBy=plasma-core.target
add_subdirectory(kcminit)
add_subdirectory(waitforname)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_subdirectory(systemd)
endif()
add_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII)
add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT)
add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
......@@ -11,18 +15,22 @@ qt5_add_dbus_interface(
ksplashinterface
)
add_executable(startplasma-x11 startplasma.cpp startplasma-x11.cpp kcheckrunning/kcheckrunning.cpp ${startplasma_SRCS})
add_executable(startplasma-wayland startplasma.cpp startplasma-wayland.cpp ${startplasma_SRCS})
add_executable(startplasma-waylandsession startplasma.cpp startplasma-waylandsession.cpp ${startplasma_SRCS})
add_executable(kde-systemd-start-condition kde-systemd-start-condition.cpp)
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_include_directories(startplasma-x11 PRIVATE ${X11_X11_INCLUDE_PATH})
target_link_libraries(startplasma-x11 PRIVATE Qt5::Core Qt5::DBus KF5::ConfigCore PW::KWorkspace
${X11_X11_LIB} # for kcheckrunning
)
target_link_libraries(startplasma-wayland PRIVATE Qt5::Core Qt5::DBus KF5::ConfigCore PW::KWorkspace)
target_link_libraries(startplasma-waylandsession PRIVATE Qt5::Core Qt5::DBus KF5::ConfigCore PW::KWorkspace)
target_link_libraries(kde-systemd-start-condition PUBLIC KF5::ConfigCore KF5::Service)
add_subdirectory(plasma-session)
add_subdirectory(plasma-shutdown)
......@@ -39,6 +47,3 @@ install(TARGETS startplasma-wayland ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(TARGETS startplasma-waylandsession DESTINATION ${KDE_INSTALL_LIBEXECDIR})
install(PROGRAMS plasma-sourceenv.sh DESTINATION ${KDE_INSTALL_LIBEXECDIR})
install(PROGRAMS plasma-dbus-run-session-if-needed DESTINATION ${KDE_INSTALL_LIBEXECDIR})
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
install(TARGETS kde-systemd-start-condition DESTINATION ${KDE_INSTALL_BINDIR})
endif ()
......@@ -32,6 +32,9 @@ set(kcminit_startup_KDEINIT_SRCS main.cpp)
qt5_add_dbus_interface(kcminit_startup_KDEINIT_SRCS ${klauncher_xml} klauncher_iface)
kf5_add_kdeinit_executable( kcminit_startup ${kcminit_startup_KDEINIT_SRCS})
ecm_install_configured_files(INPUT plasma-kcminit-phase1.service.in plasma-kcminit.service.in
DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
target_link_libraries(kdeinit_kcminit_startup Qt5::Core Qt5::Gui Qt5::DBus KF5::CoreAddons KF5::Service KF5::I18n PW::KWorkspace)
if (XCB_XCB_FOUND)
target_link_libraries(kdeinit_kcminit_startup XCB::XCB)
......
[Unit]
Description=KDE Configuration Module Initialization (Phase 1)
Requires=kcminit.service
After=plasma-kcminit.service plasma-kded.service
[Service]
Type=oneshot
ExecStart=@QtBinariesDir@/qdbus org.kde.kcminit /kcminit org.kde.KCMInit.runPhase1
Slice=session.slice
[Unit]
Description=KDE Config Module Initialization
[Service]
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/kcminit_startup
Restart=no
Type=forking
Slice=session.slice
[Install]
Alias=plasma-workspace.service
......@@ -30,6 +30,7 @@
#include <KConfig>
#include <KConfigGroup>
#include <KSharedConfig>
#include <unistd.h>
......@@ -37,6 +38,9 @@
#include "startplasma.h"
#include "../config-workspace.h"
#include "debug.h"
QTextStream out(stderr);
void messageBox(const QString &text)
......@@ -76,7 +80,7 @@ int runSync(const QString& program, const QStringList &args, const QStringList &
p.setEnvironment(QProcess::systemEnvironment() << env);
p.setProcessChannelMode(QProcess::ForwardedChannels);
p.start(program, args);
// qDebug() << "started..." << program << args;
// qCDebug(PLASMA_STARTUP) << "started..." << program << args;
p.waitForFinished(-1);
if (p.exitCode()) {
qWarning() << program << args << "exited with code" << p.exitCode();
......@@ -110,7 +114,7 @@ void sourceFiles(const QStringList &files)
continue;
if (qgetenv(env.left(idx)) != env.mid(idx+1)) {
// qDebug() << "setting..." << env.left(idx) << env.mid(idx+1) << "was" << qgetenv(env.left(idx));
// qCDebug(PLASMA_STARTUP) << "setting..." << env.left(idx) << env.mid(idx+1) << "was" << qgetenv(env.left(idx));
qputenv(env.left(idx), env.mid(idx+1));
}
}
......@@ -331,6 +335,45 @@ QProcess* setupKSplash()
return p;
}
bool hasSystemdService(const QString &serviceName)
{
auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
QStringLiteral("/org/freedesktop/systemd1"),
QStringLiteral("org.freedesktop.systemd1.Manager"),
QStringLiteral("ListUnitsByNames"));
msg << QStringList({serviceName});
auto reply = QDBusConnection::sessionBus().call(msg);
if (reply.type() == QDBusMessage::ErrorMessage) {
return false;
}
// if we have a service returned then it must have found it
return !reply.arguments().isEmpty();
}
bool useSystemdBoot()
{
auto config = KSharedConfig::openConfig(QStringLiteral("startkderc"), KConfig::NoGlobals);
const QString configValue = config->group(QStringLiteral("General")).readEntry("systemdBoot", QStringLiteral("false")).toLower();
if (configValue == QLatin1String("false")) {
return false;
}
if (!hasSystemdService(QStringLiteral("plasma-workspace@ANY.target"))) {
qWarning() << "Systemd boot requested, but plasma services were not found";
return false;
}
if (configValue == QLatin1String("force")) {
return true;
}
// xdg-desktop.target is shipped with an upcoming systemd and shows we have a generator
// for creating units out of autostart files
// only enable if that also exists, unless we're forced above
return hasSystemdService(QStringLiteral("xdg-desktop.target"));
}
bool startPlasmaSession(bool wayland)
{
OrgKdeKSplashInterface iface(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QDBusConnection::sessionBus());
......@@ -348,22 +391,9 @@ bool startPlasmaSession(bool wayland)
// 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 (desktopLockedAtStart) {
plasmaSessionOptions << QStringLiteral("--lockscreen");
}
}
bool rc = true;
QEventLoop e;
QProcess startPlasmaSession;
startPlasmaSession.setProcessChannelMode(QProcess::ForwardedChannels);
QDBusServiceWatcher serviceWatcher;
serviceWatcher.setConnection(QDBusConnection::sessionBus());
......@@ -373,15 +403,6 @@ bool startPlasmaSession(bool wayland)
serviceWatcher.addWatchedService(QStringLiteral("org.kde.Shutdown"));
serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
QObject::connect(&startPlasmaSession, QOverload<int, QProcess::ExitStatus>::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) {
......@@ -392,8 +413,49 @@ bool startPlasmaSession(bool wayland)
}
});
startPlasmaSession.start(QStringLiteral(CMAKE_INSTALL_FULL_BINDIR "/plasma_session"), plasmaSessionOptions);
e.exec();
if (!useSystemdBoot()) {
qCDebug(PLASMA_STARTUP) << "Using classic boot";
QProcess startPlasmaSession;
QStringList plasmaSessionOptions;
if (wayland) {
plasmaSessionOptions << QStringLiteral("--no-lockscreen");
} else {
if (desktopLockedAtStart) {
plasmaSessionOptions << QStringLiteral("--lockscreen");
}
}
startPlasmaSession.setProcessChannelMode(QProcess::ForwardedChannels);
QObject::connect(&startPlasmaSession, QOverload<int, QProcess::ExitStatus>::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);
rc = startPlasmaSession.waitForStarted();
} else {
qCDebug(PLASMA_STARTUP) << "Using systemd boot";
const QString platform = wayland ? QStringLiteral("wayland") : QStringLiteral("x11");
auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
QStringLiteral("/org/freedesktop/systemd1"),
QStringLiteral("org.freedesktop.systemd1.Manager"),
QStringLiteral("StartUnit"));
msg << QStringLiteral("plasma-workspace@%1.target").arg(platform) << QStringLiteral("fail");
auto reply = QDBusConnection::sessionBus().call(msg);
if (reply.type() == QDBusMessage::ErrorMessage) {
messageBox(QStringLiteral("startkde: Could not start Plasma session.\n"));
rc = false;
}
}
if (rc) {
e.exec();
}
return rc;
}
......
......@@ -48,6 +48,9 @@ bool startPlasmaSession(bool wayland);
void waitForKonqi();
static bool hasSystemdService(const QString &serviceName);
static bool useSystemdBoot();
struct KillBeforeDeleter
{
static inline void cleanup(QProcess *pointer)
......
ecm_install_configured_files(INPUT plasma-ksplash-ready.service.in @ONLY
DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
install(FILES plasma-core@.target DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
install(FILES plasma-workspace@.target DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
add_executable(kde-systemd-start-condition kde-systemd-start-condition.cpp)
target_link_libraries(kde-systemd-start-condition PUBLIC KF5::ConfigCore KF5::Service)
install(TARGETS kde-systemd-start-condition DESTINATION ${KDE_INSTALL_BINDIR})
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment