File session-seatd.patch of Package kwin
From e107373edc770886d99e7daf3bd95f03ffa8e1e1 Mon Sep 17 00:00:00 2001 From: Yukari Chiba <[email protected]> Date: Wed, 20 Aug 2025 14:35:10 +0800 Subject: [PATCH] core/session: add seatd/libseat support --- CMakeLists.txt | 4 + src/CMakeLists.txt | 11 ++ src/config-kwin.h.cmake | 1 + src/core/session.cpp | 6 ++ src/core/session.h | 3 + src/core/session_seatd.cpp | 210 +++++++++++++++++++++++++++++++++++++ src/core/session_seatd.h | 56 ++++++++++ 7 files changed, 291 insertions(+) create mode 100644 src/core/session_seatd.cpp create mode 100644 src/core/session_seatd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 92025ccb2e0..8f22d32d867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -379,6 +379,9 @@ add_feature_info(libdisplayinfo libdisplayinfo_FOUND "EDID and DisplayID library pkg_check_modules(PipeWire IMPORTED_TARGET libpipewire-0.3>=1.0.9) add_feature_info(PipeWire PipeWire_FOUND "Required for Wayland screencasting") +pkg_check_modules(libseat IMPORTED_TARGET libseat) +add_feature_info(libseat libseat_FOUND "Required for seatd session backend") + if (KWIN_BUILD_NOTIFICATIONS) find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS Notifications) endif() @@ -401,6 +404,7 @@ ecm_find_qmlmodule(org.kde.plasma.components 2.0) cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "PlasmaActivities_FOUND" OFF) cmake_dependent_option(KWIN_BUILD_EIS "Enable building KWin with libeis support" ON "Libeis-1.0_FOUND" OFF) +cmake_dependent_option(KWIN_BUILD_SEATD "Enable building of KWin with libseat support" ON "libseat_FOUND" OFF) include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/src/wayland diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e33c02bddd1..32e24858da9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -262,6 +262,13 @@ if (TARGET K::KGlobalAccelD) target_link_libraries(kwin PRIVATE K::KGlobalAccelD) endif() +if (KWIN_BUILD_SEATD) + target_sources(kwin PRIVATE + core/session_seatd.cpp + ) + target_link_libraries(kwin PRIVATE PkgConfig::libseat) +endif() + if (KWIN_BUILD_X11) target_sources(kwin PRIVATE @@ -436,6 +443,10 @@ if (KWIN_BUILD_X11) install(FILES atoms.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kwin COMPONENT Devel) endif() +if (KWIN_BUILD_SEATD) + install(FILES core/session_seatd.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kwin/core COMPONENT Devel) +endif() + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h diff --git a/src/config-kwin.h.cmake b/src/config-kwin.h.cmake index 5e05ffa255e..e595a6708c3 100644 --- a/src/config-kwin.h.cmake +++ b/src/config-kwin.h.cmake @@ -11,6 +11,7 @@ #cmakedefine01 KWIN_BUILD_ACTIVITIES #cmakedefine01 KWIN_BUILD_GLOBALSHORTCUTS #cmakedefine01 KWIN_BUILD_X11 +#cmakedefine01 KWIN_BUILD_SEATD constexpr QLatin1String KWIN_CONFIG("kwinrc"); constexpr QLatin1String KWIN_VERSION_STRING("${PROJECT_VERSION}"); constexpr QLatin1String XCB_VERSION_STRING("${XCB_VERSION}"); diff --git a/src/core/session.cpp b/src/core/session.cpp index bfb32745523..dccec2b2e0a 100644 --- a/src/core/session.cpp +++ b/src/core/session.cpp @@ -8,6 +8,9 @@ #include "session_consolekit.h" #include "session_logind.h" #include "session_noop.h" +#if KWIN_BUILD_SEATD +#include "session_seatd.h" +#endif namespace KWin { @@ -19,6 +22,9 @@ static const struct } s_availableSessions[] = { {Session::Type::Logind, &LogindSession::create}, {Session::Type::ConsoleKit, &ConsoleKitSession::create}, +#if KWIN_BUILD_SEATD + {Session::Type::Seatd, &SeatdSession::create}, +#endif {Session::Type::Noop, &NoopSession::create}, }; diff --git a/src/core/session.h b/src/core/session.h index 3c5ea48da09..354a022a8c1 100644 --- a/src/core/session.h +++ b/src/core/session.h @@ -37,6 +37,9 @@ public: Noop, ConsoleKit, Logind, +#if KWIN_BUILD_SEATD + Seatd, +#endif }; /** diff --git a/src/core/session_seatd.cpp b/src/core/session_seatd.cpp new file mode 100644 index 00000000000..c654c2518d8 --- /dev/null +++ b/src/core/session_seatd.cpp @@ -0,0 +1,210 @@ + +/* + SPDX-FileCopyrightText: 2025 Yukari Chiba <[email protected]> + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "session_seatd.h" +#include "utils/common.h" // For qCWarning, etc. + +extern "C" { +#include <libseat.h> +} + +#include <QSocketNotifier> +#include <QString> +#include <cerrno> +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +namespace KWin +{ + +// Static libseat callbacks that forward to member functions +void SeatdSession::on_enable_seat(struct libseat *seat, void *userdata) +{ + Q_UNUSED(seat); + if (auto session = static_cast<SeatdSession *>(userdata)) { + session->handleEnableSeat(); + } +} + +void SeatdSession::on_disable_seat(struct libseat *seat, void *userdata) +{ + if (auto session = static_cast<SeatdSession *>(userdata)) { + session->handleDisableSeat(); + // As per libseat docs, we must acknowledge the event + libseat_disable_seat(seat); + } +} + +std::unique_ptr<SeatdSession> SeatdSession::create() +{ + // Create the session object first to get a valid 'this' pointer for userdata + auto session = new SeatdSession(nullptr); // Temporarily pass nullptr for seat + + static const struct libseat_seat_listener listener = { + .enable_seat = &SeatdSession::on_enable_seat, + .disable_seat = &SeatdSession::on_disable_seat, + }; + + struct libseat *seat = libseat_open_seat(&listener, session); + if (!seat) { + qCWarning(KWIN_CORE) << "Failed to open libseat seat:" << strerror(errno); + delete session; // Clean up the created object + return nullptr; + } + + // Now assign the created seat to the session object + session->m_seat = seat; + + if (session->initialize()) { + return std::unique_ptr<SeatdSession>{session}; + } + + // Initialization failed, destructor will call libseat_close_seat + delete session; + return nullptr; +} + +SeatdSession::SeatdSession(struct libseat *seat) + : m_seat(seat) +{ +} + +bool SeatdSession::initialize() +{ + const int fd = libseat_get_fd(m_seat); + if (fd < 0) { + qCWarning(KWIN_CORE) << "Failed to get libseat fd:" << strerror(errno); + return false; + } + + m_notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, this, &SeatdSession::handleSeatdEvents); + + // Initial dispatch to get session state, blocking until we get the first events. + // This ensures we know if we are active or not at startup. + if (libseat_dispatch(m_seat, -1) == -1) { + qCWarning(KWIN_CORE) << "Initial libseat_dispatch failed:" << strerror(errno); + return false; + } + + return true; +} + +SeatdSession::~SeatdSession() +{ + if (m_seat) { + libseat_close_seat(m_seat); + } +} + +bool SeatdSession::isActive() const +{ + return m_isActive; +} + +Session::Capabilities SeatdSession::capabilities() const +{ + // libseat supports switching sessions (VTs) + return Capability::SwitchTerminal; +} + +QString SeatdSession::seat() const +{ + const char *name = libseat_seat_name(m_seat); + return name ? QString::fromUtf8(name) : QStringLiteral("seat0"); +} + +uint SeatdSession::terminal() const +{ + // libseat does not provide a way to get the current terminal number. + return 0; +} + +int SeatdSession::openRestricted(const QString &fileName) +{ + if (!m_isActive) { + errno = EAGAIN; + qCWarning(KWIN_CORE) << "Cannot open restricted device when session is not active."; + return -1; + } + + struct stat st; + if (stat(fileName.toUtf8(), &st) < 0) { + return -1; + } + + int fd = -1; + const int device_id = libseat_open_device(m_seat, fileName.toUtf8().constData(), &fd); + + if (device_id < 0) { + qCWarning(KWIN_CORE) << "libseat_open_device for" << fileName << "failed:" << strerror(errno); + // FIXME: can not reopen device after re-enable client, fallback to open() + return open(fileName.toLocal8Bit(), O_RDWR | O_CLOEXEC); + } + + // Set CLOEXEC flag to prevent leaking fd to child processes + const int flags = fcntl(fd, F_GETFD); + if (flags != -1) { + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + } else { + qCWarning(KWIN_CORE) << "fcntl(F_GETFD) failed for new device fd:" << strerror(errno); + } + + return fd; +} + +void SeatdSession::closeRestricted(int fileDescriptor) +{ + ::close(fileDescriptor); +} + +void SeatdSession::switchTo(uint terminal) +{ + if (libseat_switch_session(m_seat, terminal) != 0) { + qCWarning(KWIN_CORE) << "libseat_switch_session failed:" << strerror(errno); + } +} + +FileDescriptor SeatdSession::delaySleep(const QString &reason) +{ + // libseat does not have an API for sleep inhibition + Q_UNUSED(reason); + return FileDescriptor{}; +} + +void SeatdSession::handleSeatdEvents() +{ + // Non-blocking dispatch with timeout 0. + if (libseat_dispatch(m_seat, 0) == -1) { + if (errno != EAGAIN) { + qCWarning(KWIN_CORE) << "libseat_dispatch failed:" << strerror(errno); + } + } +} + +void SeatdSession::handleEnableSeat() +{ + if (!m_isActive) { + qCDebug(KWIN_CORE) << "Seatd session enabled."; + m_isActive = true; + Q_EMIT activeChanged(true); + } +} + +void SeatdSession::handleDisableSeat() +{ + if (m_isActive) { + qCDebug(KWIN_CORE) << "Seatd session disabled."; + m_isActive = false; + Q_EMIT activeChanged(false); + } +} + +} // namespace KWin + +#include "moc_session_seatd.cpp" diff --git a/src/core/session_seatd.h b/src/core/session_seatd.h new file mode 100644 index 00000000000..efe30d1cee7 --- /dev/null +++ b/src/core/session_seatd.h @@ -0,0 +1,56 @@ +/* + SPDX-FileCopyrightText: 2025 Yukari Chiba <[email protected]> + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "session.h" +#include <QMap> + +// Forward declarations to avoid including headers here +struct libseat; +class QSocketNotifier; + +namespace KWin +{ + +class SeatdSession : public Session +{ + Q_OBJECT + +public: + static std::unique_ptr<SeatdSession> create(); + ~SeatdSession() override; + + // Session interface + bool isActive() const override; + Capabilities capabilities() const override; + QString seat() const override; + uint terminal() const override; + int openRestricted(const QString &fileName) override; + void closeRestricted(int fileDescriptor) override; + void switchTo(uint terminal) override; + FileDescriptor delaySleep(const QString &reason) override; + +private Q_SLOTS: + void handleSeatdEvents(); + +private: + explicit SeatdSession(struct libseat *seat); + bool initialize(); + + // libseat callbacks + void handleEnableSeat(); + void handleDisableSeat(); + + static void on_enable_seat(struct libseat *seat, void *userdata); + static void on_disable_seat(struct libseat *seat, void *userdata); + + struct libseat *m_seat = nullptr; + QSocketNotifier *m_notifier = nullptr; + bool m_isActive = false; +}; + +} // namespace KWin -- GitLab