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