diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a783d01a0..b910028aec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,6 +355,9 @@ set_package_properties(QAccessibilityClient6 PROPERTIES ) set(HAVE_ACCESSIBILITY ${QAccessibilityClient6_FOUND}) +pkg_check_modules(libsystemd IMPORTED_TARGET libsystemd) +add_feature_info(libsystemd libsystemd_FOUND "Required for setting up the service watchdog") + option(KWIN_BUILD_GLOBALSHORTCUTS "Enable building of KWin with global shortcuts support" ON) if(KWIN_BUILD_GLOBALSHORTCUTS) find_package(KGlobalAccelD REQUIRED) diff --git a/plasma-kwin_wayland.service.in b/plasma-kwin_wayland.service.in index 6db09b26ba..d4a4cf6216 100644 --- a/plasma-kwin_wayland.service.in +++ b/plasma-kwin_wayland.service.in @@ -3,6 +3,10 @@ Description=KDE Window Manager PartOf=graphical-session.target [Service] +Type=notify-reload ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/kwin_wayland_wrapper --xwayland BusName=org.kde.KWinWrapper Slice=session.slice +WatchdogSec=5s +NotifyAccess=all +WatchdogSignal=SIGHUP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e0db4fe9a..e5f22b3143 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -355,6 +355,11 @@ if (KWIN_BUILD_TABBOX) add_subdirectory(tabbox/switchers) endif() +if(TARGET PkgConfig::libsystemd) + target_sources(kwin PRIVATE watchdog.cpp) + target_link_libraries(kwin PRIVATE PkgConfig::libsystemd) +endif() + qt_generate_dbus_interface(virtualkeyboard_dbus.h org.kde.kwin.VirtualKeyboard.xml OPTIONS -A) qt_generate_dbus_interface(tabletmodemanager.h org.kde.KWin.TabletModeManager.xml OPTIONS -A) diff --git a/src/helpers/wayland_wrapper/kwin_wrapper.cpp b/src/helpers/wayland_wrapper/kwin_wrapper.cpp index 9f08261faf..e2634b2ba9 100644 --- a/src/helpers/wayland_wrapper/kwin_wrapper.cpp +++ b/src/helpers/wayland_wrapper/kwin_wrapper.cpp @@ -42,7 +42,10 @@ class KWinWrapper : public QObject public: KWinWrapper(QObject *parent); ~KWinWrapper(); + void run(); + void restart(); + void terminate(); private: wl_socket *m_socket; @@ -82,13 +85,7 @@ KWinWrapper::KWinWrapper(QObject *parent) KWinWrapper::~KWinWrapper() { wl_socket_destroy(m_socket); - if (m_kwinProcess) { - disconnect(m_kwinProcess, nullptr, this, nullptr); - m_kwinProcess->terminate(); - m_kwinProcess->waitForFinished(); - m_kwinProcess->kill(); - m_kwinProcess->waitForFinished(); - } + terminate(); } void KWinWrapper::run() @@ -147,7 +144,6 @@ void KWinWrapper::run() env.insert("XAUTHORITY", m_xauthorityFile.fileName()); } } - auto envSyncJob = new KUpdateLaunchEnvironmentJob(env); connect(envSyncJob, &KUpdateLaunchEnvironmentJob::finished, this, []() { // The service name is merely there to indicate to the world that we're up and ready with all envs exported @@ -155,21 +151,42 @@ void KWinWrapper::run() }); } +void KWinWrapper::terminate() +{ + if (m_kwinProcess) { + disconnect(m_kwinProcess, nullptr, this, nullptr); + m_kwinProcess->terminate(); + m_kwinProcess->waitForFinished(); + m_kwinProcess->kill(); + m_kwinProcess->waitForFinished(); + } +} + +void KWinWrapper::restart() +{ + terminate(); + m_kwinProcess->start(); +} + int main(int argc, char **argv) { QCoreApplication app(argc, argv); app.setQuitLockEnabled(false); // don't exit when the first KJob finishes KSignalHandler::self()->watchSignal(SIGTERM); - QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &app, [&app](int signal) { + KSignalHandler::self()->watchSignal(SIGHUP); + + KWinWrapper wrapper(&app); + wrapper.run(); + + QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &app, [&app, &wrapper](int signal) { if (signal == SIGTERM) { app.quit(); + } else if (signal == SIGHUP) { // The systemd service will issue SIGHUP when it's locked up so that we can restarted + wrapper.restart(); } }); - KWinWrapper wrapper(&app); - wrapper.run(); - return app.exec(); } diff --git a/src/watchdog.cpp b/src/watchdog.cpp new file mode 100644 index 0000000000..e9a6b92947 --- /dev/null +++ b/src/watchdog.cpp @@ -0,0 +1,62 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2024 Aleix Pol i Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "utils/common.h" +#include +#include +#include + +class Watchdog : public QObject +{ + Q_OBJECT +public: + Watchdog(QObject *parent) + : QObject(parent) + { + bool ok; + const std::chrono::microseconds watchdogIntervalInUs((qgetenv("WATCHDOG_USEC").toUInt(&ok) * 3) / 4); + if (!ok) { + qCInfo(KWIN_CORE) << "Watchdog: disabled, not running on a systemd environment or watchdog is not set up. No WATCHDOG_USEC."; + deleteLater(); + return; + } + m_onBehalf = qgetenv("WATCHDOG_PID").toUInt(&ok); + if (!ok) { + qCInfo(KWIN_CORE) << "Watchdog: disabled, not running on a systemd environment or watchdog is not set up. No WATCHDOG_PID."; + deleteLater(); + return; + } + qunsetenv("WATCHDOG_USEC"); + qunsetenv("WATCHDOG_PID"); + auto t = new QTimer(this); + t->setInterval(std::chrono::duration_cast(watchdogIntervalInUs)); + t->setSingleShot(false); + qCInfo(KWIN_CORE) << "Watchdog: enabled. Interval:" << watchdogIntervalInUs << t->intervalAsDuration(); + + sd_pid_notify(m_onBehalf, 0, "READY=1"); // If service Type=notify the service is only considered ready once we send this + qCInfo(KWIN_CORE) << "Watchdog: Notified as ready"; + + connect(t, &QTimer::timeout, this, [this] { + sd_pid_notify(m_onBehalf, 0, "WATCHDOG=1"); + }); + t->start(); + } + +private: + pid_t m_onBehalf = 0; +}; + +static void setupWatchdog() +{ + new Watchdog(QCoreApplication::instance()); +} + +Q_COREAPP_STARTUP_FUNCTION(setupWatchdog) + +#include "watchdog.moc"