diff --git a/src/common/SignalHandler.cpp b/src/common/SignalHandler.cpp index b2590f2..4bbd289 100644 --- a/src/common/SignalHandler.cpp +++ b/src/common/SignalHandler.cpp @@ -32,7 +32,6 @@ namespace DDM { int sigintFd[2]; int sigtermFd[2]; - int sigcustomFd[2]; SignalHandler::SignalHandler(QObject *parent) : QObject(parent) { initialize(); @@ -45,9 +44,6 @@ namespace DDM { snterm = new QSocketNotifier(sigtermFd[1], QSocketNotifier::Read, this); connect(snterm, &QSocketNotifier::activated, this, &SignalHandler::handleSigterm); - - sncustom = new QSocketNotifier(sigcustomFd[1], QSocketNotifier::Read, this); - connect(sncustom, &QSocketNotifier::activated, this, &SignalHandler::handleSigCustom); } SignalHandler::~SignalHandler() { @@ -55,17 +51,12 @@ namespace DDM { sa_default.sa_handler = SIG_DFL; sigaction(SIGINT, &sa_default, NULL); sigaction(SIGTERM, &sa_default, NULL); - for (int signal : customSignals) - sigaction(signal, &sa_default, NULL); snint->setEnabled(false); snterm->setEnabled(false); - sncustom->setEnabled(false); ::close(sigintFd[0]); ::close(sigintFd[1]); ::close(sigtermFd[0]); ::close(sigtermFd[1]); - ::close(sigcustomFd[0]); - ::close(sigcustomFd[1]); } void SignalHandler::initialize() { @@ -95,31 +86,6 @@ namespace DDM { return; } - if (::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sigcustomFd)) - qCritical() << "Failed to create socket pair for custom signals handling."; - } - - void SignalHandler::addCustomSignal(int signal) - { - struct sigaction sigcustom = { }; - sigcustom.sa_handler = SignalHandler::customSignalHandler; - sigemptyset(&sigcustom.sa_mask); - sigcustom.sa_flags = SA_RESTART; - - if (sigaction(signal, &sigcustom, 0) < 0) { - qCritical() << "Failed to set up " << strsignal(signal) << " handler."; - return; - } - - sigset_t set; - sigemptyset(&set); - sigaddset(&set, signal); - if (sigprocmask(SIG_UNBLOCK, &set, nullptr) < 0) { - qCritical() << "Failed to unblock " << strsignal(signal) << " handler."; - return; - } - - customSignals.append(signal); } void SignalHandler::intSignalHandler(int) { @@ -138,13 +104,6 @@ namespace DDM { } } - void SignalHandler::customSignalHandler(int signal) { - if (::write(sigcustomFd[0], &signal, sizeof(signal)) == -1) { - qCritical() << "Error writing to the " << strsignal(signal) << " handler"; - return; - } - } - void SignalHandler::handleSigint() { // disable notifier snint->setEnabled(false); @@ -189,27 +148,4 @@ namespace DDM { snterm->setEnabled(true); } - void SignalHandler::handleSigCustom() { - // disable notifier - sncustom->setEnabled(false); - - // read from socket - int signal; - if (::read(sigcustomFd[1], &signal, sizeof(signal)) == -1) { - // something went wrong! - qCritical() << "Error reading from the socket"; - return; - } - - // log event - qWarning() << "Signal received: " << strsignal(signal); - - // emit signal - emit customSignalReceived(signal); - - // enable notifier - sncustom->setEnabled(true); - } - - } diff --git a/src/common/SignalHandler.h b/src/common/SignalHandler.h index 0c24910..9bb6e18 100644 --- a/src/common/SignalHandler.h +++ b/src/common/SignalHandler.h @@ -21,7 +21,6 @@ #define DDM_SIGNALHANDLER_H #include -#include class QSocketNotifier; @@ -33,30 +32,22 @@ namespace DDM { SignalHandler(QObject *parent = 0); ~SignalHandler(); - void addCustomSignal(int signal); - signals: void sighupReceived(); void sigintReceived(); void sigtermReceived(); - void customSignalReceived(int signal); private slots: void handleSigint(); void handleSigterm(); - void handleSigCustom(); private: static void initialize(); static void intSignalHandler(int unused); static void termSignalHandler(int unused); - static void customSignalHandler(int unused); QSocketNotifier *snint { nullptr }; QSocketNotifier *snterm { nullptr }; - QSocketNotifier *sncustom { nullptr }; - - QList customSignals{}; }; } #endif // DDM_SIGNALHANDLER_H diff --git a/src/daemon/Auth.cpp b/src/daemon/Auth.cpp index 9aa6654..c55e24e 100644 --- a/src/daemon/Auth.cpp +++ b/src/daemon/Auth.cpp @@ -6,10 +6,12 @@ #include "UserSession.h" #include "DaemonApp.h" +#include "DdeSeatdControl.h" #include "Login1Manager.h" #include "Login1Session.h" #include "SignalHandler.h" -#include "VirtualTerminal.h" +#include "TtyUtils.h" +#include "TreelandConnector.h" #include #include @@ -229,8 +231,13 @@ namespace DDM { return -1; } - // Here is most safe place to jump VT - VirtualTerminal::jumpToVt(tty, false, false); + // Here is most safe place to request the VT switch before opening the session. + if (!daemonApp->seatdControl()->requestSwitchVt(tty)) { + qWarning() << "[Auth] Failed to switch to VT" << tty << ":" << strerror(errno); + close(pipefd[0]); + close(pipefd[1]); + return -1; + } sessionLeaderPid = fork(); switch (sessionLeaderPid) { @@ -379,7 +386,7 @@ namespace DDM { CHECK_RET_OPEN // Set PAM_TTY - QString vtPath = VirtualTerminal::path(tty); + QString vtPath = TtyUtils::path(tty); d->ret = pam_set_item(d->handle, PAM_TTY, qPrintable(vtPath)); CHECK_RET_OPEN diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 5347f96..1a84cf2 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -20,6 +20,7 @@ configure_file(config.h.in config.h IMMEDIATE @ONLY) set(DAEMON_SOURCES Auth.cpp DaemonApp.cpp + DdeSeatdControl.cpp Display.cpp DisplayManager.cpp PowerManager.cpp @@ -27,7 +28,6 @@ set(DAEMON_SOURCES SocketServer.cpp TreelandConnector.cpp UserSession.cpp - VirtualTerminal.cpp XorgDisplayServer.cpp TreelandDisplayServer.cpp ${TREELAND_DDM_SOURCE} diff --git a/src/daemon/DaemonApp.cpp b/src/daemon/DaemonApp.cpp index 1efb015..f80fdb8 100644 --- a/src/daemon/DaemonApp.cpp +++ b/src/daemon/DaemonApp.cpp @@ -22,6 +22,7 @@ #include "Configuration.h" #include "Constants.h" +#include "DdeSeatdControl.h" #include "DisplayManager.h" #include "PowerManager.h" #include "SeatManager.h" @@ -85,7 +86,12 @@ namespace DDM { connect(m_signalHandler, &SignalHandler::sigintReceived, this, &DaemonApp::quit); connect(m_signalHandler, &SignalHandler::sigtermReceived, this, &DaemonApp::quit); - m_treelandConnector = new TreelandConnector(); + m_seatdControl = new DdeSeatdControl(this); + m_treelandConnector = new TreelandConnector(this); + connect(m_seatdControl, &DdeSeatdControl::vtChanged, + m_seatManager, &SeatManager::handleVtChanged); + if (!m_seatdControl->connectEventSocket()) + qWarning() << "Failed to connect dde-seatd event socket during startup"; // log message qDebug() << "Starting..."; diff --git a/src/daemon/DaemonApp.h b/src/daemon/DaemonApp.h index 12c824b..8cf155f 100644 --- a/src/daemon/DaemonApp.h +++ b/src/daemon/DaemonApp.h @@ -31,6 +31,7 @@ namespace DDM { class PowerManager; class SeatManager; class SignalHandler; + class DdeSeatdControl; class TreelandConnector; class DaemonApp : public QCoreApplication { @@ -46,6 +47,7 @@ namespace DDM { inline PowerManager *powerManager() const { return m_powerManager; }; inline SeatManager *seatManager() const { return m_seatManager; }; inline SignalHandler *signalHandler() const { return m_signalHandler; }; + inline DdeSeatdControl *seatdControl() const { return m_seatdControl; }; inline TreelandConnector *treelandConnector() const { return m_treelandConnector; }; void backToNormal(); @@ -62,6 +64,7 @@ namespace DDM { PowerManager *m_powerManager { nullptr }; SeatManager *m_seatManager { nullptr }; SignalHandler *m_signalHandler { nullptr }; + DdeSeatdControl *m_seatdControl { nullptr }; TreelandConnector *m_treelandConnector { nullptr }; }; } diff --git a/src/daemon/DdeSeatdControl.cpp b/src/daemon/DdeSeatdControl.cpp new file mode 100644 index 0000000..70c6130 --- /dev/null +++ b/src/daemon/DdeSeatdControl.cpp @@ -0,0 +1,437 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DdeSeatdControl.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DDM { + +static constexpr auto controlSocketPath = "/run/dde-seatd-control.sock"; +static constexpr uint16_t maxControlPayloadSize = 4096; +static constexpr size_t maxControlStringLength = 64; + +enum ControlOpcode : uint16_t { + ControlCreateGroupVt = 1, + ControlDestroyGroupVt = 2, + ControlGetActiveVt = 3, + ControlFindFreeVt = 4, + ControlSwitchVt = 5, + ControlGroupVtCreated = 100, + ControlVtChanged = 101, + ControlOk = 102, + ControlActiveVt = 103, + ControlFreeVt = 104, + ControlError = 255, +}; + +struct ControlHeader { + uint16_t opcode; + uint16_t size; +}; + +struct ControlCreateGroupVtRequest { + int32_t ownerPid; + int32_t vt; + char user[maxControlStringLength]; + char session[maxControlStringLength]; +}; + +struct ControlDestroyGroupVtRequest { + int32_t vt; +}; + +struct ControlSwitchVtRequest { + int32_t vt; +}; + +struct ControlGroupVtCreatedEvent { + int32_t ownerPid; + int32_t vt; +}; + +struct ControlVtStateEvent { + int32_t vt; +}; + +struct ControlVtChangeEvent { + int32_t oldVt; + int32_t newVt; +}; + +struct ControlErrorMessage { + int32_t error; +}; + +static int connectUnixSocket(const char *path) { + int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd == -1) + return -1; + + sockaddr_un addr {}; + addr.sun_family = AF_UNIX; + const size_t pathLength = strlen(path); + if (pathLength >= sizeof(addr.sun_path)) { + close(fd); + errno = ENAMETOOLONG; + return -1; + } + memcpy(addr.sun_path, path, pathLength + 1); + const auto size = static_cast(offsetof(sockaddr_un, sun_path) + pathLength + 1); + if (::connect(fd, reinterpret_cast(&addr), size) == -1) { + close(fd); + return -1; + } + return fd; +} + +static ssize_t recvAll(int fd, void *buffer, size_t size) { + auto *data = static_cast(buffer); + size_t received = 0; + while (received < size) { + const auto ret = recv(fd, data + received, size - received, 0); + if (ret == -1) { + if (errno == EINTR) + continue; + return -1; + } + if (ret == 0) { + errno = ECONNRESET; + return -1; + } + received += static_cast(ret); + } + return static_cast(received); +} + +static bool sendAll(int fd, const void *buffer, size_t size) { + const auto *data = static_cast(buffer); + size_t sent = 0; + while (sent < size) { + const auto ret = send(fd, data + sent, size - sent, MSG_NOSIGNAL); + if (ret == -1) { + if (errno == EINTR) + continue; + return false; + } + if (ret == 0) { + errno = EPIPE; + return false; + } + sent += static_cast(ret); + } + return true; +} + +static bool waitForReadable(int fd, int timeoutMs) { + pollfd pollFd { + .fd = fd, + .events = POLLIN, + .revents = 0, + }; + int pollResult = -1; + do { + pollResult = poll(&pollFd, 1, timeoutMs); + } while (pollResult == -1 && errno == EINTR); + if (pollResult == 0) { + errno = ETIMEDOUT; + return false; + } + if (pollResult < 0) + return false; + if ((pollFd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0 || (pollFd.revents & POLLIN) == 0) { + errno = ECONNRESET; + return false; + } + return true; +} + +static bool readControlResponse(int fd, uint16_t expectedOpcode, void *payload, uint16_t payloadSize) { + if (!waitForReadable(fd, 3000)) + return false; + + ControlHeader responseHeader {}; + if (recvAll(fd, &responseHeader, sizeof(responseHeader)) != static_cast(sizeof(responseHeader))) + return false; + + if (responseHeader.opcode == ControlError && responseHeader.size == sizeof(ControlErrorMessage)) { + ControlErrorMessage error {}; + if (recvAll(fd, &error, sizeof(error)) != static_cast(sizeof(error))) + return false; + errno = error.error; + return false; + } + + if (responseHeader.opcode != expectedOpcode || responseHeader.size != payloadSize) { + errno = EPROTO; + return false; + } + + if (payloadSize == 0) + return true; + + return recvAll(fd, payload, payloadSize) == static_cast(payloadSize); +} + +static bool sendControlRequest(int fd, uint16_t opcode, const void *payload, uint16_t payloadSize) { + ControlHeader header { + .opcode = opcode, + .size = payloadSize, + }; + if (!sendAll(fd, &header, sizeof(header))) + return false; + if (payloadSize == 0) + return true; + return sendAll(fd, payload, payloadSize); +} + +template +static void copyFixedString(char (&target)[N], const QString &source, const char *fieldName) { + const auto bytes = source.toUtf8(); + memset(target, 0, N); + if (bytes.size() >= static_cast(N)) { + qWarning() << "Truncating dde-seatd control" << fieldName << "from" + << bytes.size() << "to" << (N - 1) << "bytes"; + } + const auto length = std::min(static_cast(bytes.size()), N - 1); + memcpy(target, bytes.constData(), length); +} + +DdeSeatdControl::DdeSeatdControl(QObject *parent) + : QObject(parent) { +} + +DdeSeatdControl::~DdeSeatdControl() { + disconnectEventSocket(); +} + +bool DdeSeatdControl::connectEventSocket() { + if (m_eventSocket && m_eventSocket->state() == QLocalSocket::ConnectedState) + return true; + + disconnectEventSocket(); + + m_eventSocket = new QLocalSocket(this); + connect(m_eventSocket, &QLocalSocket::readyRead, this, &DdeSeatdControl::handleEventSocket); + connect(m_eventSocket, &QLocalSocket::disconnected, this, &DdeSeatdControl::disconnectEventSocket); + connect(m_eventSocket, &QLocalSocket::errorOccurred, this, &DdeSeatdControl::handleEventSocketError); + + m_eventSocket->connectToServer(QString::fromLatin1(controlSocketPath)); + if (!m_eventSocket->waitForConnected(3000)) { + qWarning() << "Failed to connect dde-seatd control event socket" + << controlSocketPath << ":" << m_eventSocket->errorString(); + disconnectEventSocket(); + return false; + } + + m_activeVt = queryActiveVt(); + if (m_activeVt <= 0) { + qWarning("Failed to initialize cached active VT from dde-seatd: %s", strerror(errno)); + disconnectEventSocket(); + return false; + } + + qDebug("Connected dde-seatd control event socket"); + return true; +} + +void DdeSeatdControl::disconnectEventSocket() { + if (m_eventSocket) { + m_eventSocket->blockSignals(true); + if (m_eventSocket->state() != QLocalSocket::UnconnectedState) + m_eventSocket->disconnectFromServer(); + m_eventSocket->deleteLater(); + m_eventSocket = nullptr; + } + m_eventBuffer.clear(); + m_activeVt = -1; + m_pendingVt = -1; +} + +int DdeSeatdControl::queryActiveVt() { + const int fd = connectUnixSocket(controlSocketPath); + if (fd == -1) + return -1; + + if (!sendControlRequest(fd, ControlGetActiveVt, nullptr, 0)) { + close(fd); + return -1; + } + + ControlVtStateEvent event {}; + const bool ok = readControlResponse(fd, ControlActiveVt, &event, sizeof(event)); + close(fd); + return ok ? event.vt : -1; +} + +int DdeSeatdControl::activeVt() { + if (m_activeVt > 0) + return m_activeVt; + + m_activeVt = queryActiveVt(); + if (m_activeVt == m_pendingVt) + m_pendingVt = -1; + return m_activeVt; +} + +int DdeSeatdControl::findAvailableVt() { + const int fd = connectUnixSocket(controlSocketPath); + if (fd == -1) + return -1; + + if (!sendControlRequest(fd, ControlFindFreeVt, nullptr, 0)) { + close(fd); + return -1; + } + + ControlVtStateEvent event {}; + const bool ok = readControlResponse(fd, ControlFreeVt, &event, sizeof(event)); + close(fd); + return ok ? event.vt : -1; +} + +bool DdeSeatdControl::requestSwitchVt(int vt) { + const int fd = connectUnixSocket(controlSocketPath); + if (fd == -1) + return false; + + ControlSwitchVtRequest request { + .vt = vt, + }; + if (!sendControlRequest(fd, ControlSwitchVt, &request, sizeof(request))) { + close(fd); + return false; + } + + const bool ok = readControlResponse(fd, ControlOk, nullptr, 0); + close(fd); + if (ok) + m_pendingVt = m_activeVt == vt ? -1 : vt; + return ok; +} + +int DdeSeatdControl::createGroupVt(int ownerPid, const QString &user, const QString &sessionId) { + if (ownerPid <= 0) { + errno = EINVAL; + return -1; + } + + const int fd = connectUnixSocket(controlSocketPath); + if (fd == -1) + return -1; + + ControlCreateGroupVtRequest request {}; + request.ownerPid = ownerPid; + request.vt = 0; + copyFixedString(request.user, user, "user"); + copyFixedString(request.session, sessionId, "session"); + + if (!sendControlRequest(fd, ControlCreateGroupVt, &request, sizeof(request))) { + close(fd); + return -1; + } + + ControlGroupVtCreatedEvent event {}; + const bool ok = readControlResponse(fd, ControlGroupVtCreated, &event, sizeof(event)); + close(fd); + if (!ok) + return -1; + if (event.vt <= 0) { + errno = EINVAL; + return -1; + } + + if (!connectEventSocket()) { + destroyGroupVt(event.vt); + return -1; + } + return event.vt; +} + +bool DdeSeatdControl::sendDestroyGroupVt(int vt) { + const int fd = connectUnixSocket(controlSocketPath); + if (fd == -1) + return false; + + ControlDestroyGroupVtRequest request { + .vt = vt, + }; + const bool ok = sendControlRequest(fd, ControlDestroyGroupVt, &request, sizeof(request)); + close(fd); + return ok; +} + +void DdeSeatdControl::destroyGroupVt(int vt) { + if (vt > 0 && !sendDestroyGroupVt(vt)) + qWarning("Failed to destroy grouped VT %d: %s", vt, strerror(errno)); +} + +void DdeSeatdControl::handleEventSocket() { + if (!m_eventSocket) + return; + + const QByteArray chunk = m_eventSocket->readAll(); + if (chunk.isEmpty()) + return; + + m_eventBuffer.append(chunk); + while (m_eventBuffer.size() >= static_cast(sizeof(ControlHeader))) { + ControlHeader header {}; + memcpy(&header, m_eventBuffer.constData(), sizeof(header)); + if (header.size > maxControlPayloadSize) { + qWarning("Invalid dde-seatd control message size %u", header.size); + disconnectEventSocket(); + return; + } + + const int messageSize = static_cast(sizeof(ControlHeader) + header.size); + if (m_eventBuffer.size() < messageSize) + return; + + const auto *payload = m_eventBuffer.constData() + sizeof(ControlHeader); + switch (header.opcode) { + case ControlVtChanged: + if (header.size != sizeof(ControlVtChangeEvent)) { + qWarning("Invalid dde-seatd control payload size %u for opcode %u", + header.size, header.opcode); + break; + } + { + ControlVtChangeEvent event {}; + memcpy(&event, payload, sizeof(event)); + m_activeVt = event.newVt; + if (m_pendingVt == event.newVt) + m_pendingVt = -1; + Q_EMIT vtChanged(event.oldVt, event.newVt); + } + break; + default: + qWarning("Ignoring unknown dde-seatd control opcode %u with payload size %u", + header.opcode, header.size); + break; + } + m_eventBuffer.remove(0, messageSize); + } +} + +void DdeSeatdControl::handleEventSocketError(QLocalSocket::LocalSocketError error) { + if (!m_eventSocket) + return; + + qWarning() << "dde-seatd control event socket error:" << error + << m_eventSocket->errorString(); + disconnectEventSocket(); +} + +} diff --git a/src/daemon/DdeSeatdControl.h b/src/daemon/DdeSeatdControl.h new file mode 100644 index 0000000..bd80589 --- /dev/null +++ b/src/daemon/DdeSeatdControl.h @@ -0,0 +1,45 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef DDM_DDESEATDCONTROL_H +#define DDM_DDESEATDCONTROL_H + +#include +#include +#include +#include + +namespace DDM { +class DdeSeatdControl : public QObject { + Q_OBJECT +public: + explicit DdeSeatdControl(QObject *parent = nullptr); + ~DdeSeatdControl(); + + bool connectEventSocket(); + void disconnectEventSocket(); + + int activeVt(); + int findAvailableVt(); + bool requestSwitchVt(int vt); + + int createGroupVt(int ownerPid, const QString &user, const QString &sessionId); + void destroyGroupVt(int vt); + +Q_SIGNALS: + void vtChanged(int oldVt, int newVt); + +private: + void handleEventSocket(); + void handleEventSocketError(QLocalSocket::LocalSocketError error); + int queryActiveVt(); + bool sendDestroyGroupVt(int vt); + + QLocalSocket *m_eventSocket { nullptr }; + QByteArray m_eventBuffer; + int m_activeVt { -1 }; + int m_pendingVt { -1 }; +}; +} + +#endif diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index 35de7e7..0bc69e0 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -25,6 +25,7 @@ #include "Auth.h" #include "Configuration.h" #include "DaemonApp.h" +#include "DdeSeatdControl.h" #include "DisplayManager.h" #include "Messages.h" #include "SeatManager.h" @@ -36,23 +37,20 @@ #include "config.h" #include "Login1Manager.h" -#include "VirtualTerminal.h" #include #include #include #include #include -#include -#include -#include #include #include #include -#include #include #include +#include +#include #define STRINGIFY(x) #x @@ -90,18 +88,18 @@ namespace DDM { if (!isTtyInUse(QStringLiteral("tty" STRINGIFY(DDM_INITIAL_VT)))) { return DDM_INITIAL_VT; } - const auto vt = VirtualTerminal::currentVt(); + const auto vt = daemonApp->seatdControl()->activeVt(); if (vt > 0 && !isTtyInUse(QStringLiteral("tty%1").arg(vt))) { return vt; } - return VirtualTerminal::setUpNewVt(); + return daemonApp->seatdControl()->findAvailableVt(); } static bool isVtReservedByDdm(int vt) { - for (Display *display : daemonApp->seatManager()->displays) { + for (Display *display : std::as_const(daemonApp->seatManager()->displays)) { if (display->terminalId == vt) return true; - for (Auth *auth : display->auths) { + for (Auth *auth : std::as_const(display->auths)) { if (auth->tty == vt) return true; } @@ -117,7 +115,7 @@ namespace DDM { return vt; } - return VirtualTerminal::setUpNewVt(); + return daemonApp->seatdControl()->findAvailableVt(); } Display::Display(SeatManager *parent, QString name) @@ -181,7 +179,10 @@ namespace DDM { if (m_started) return true; - VirtualTerminal::jumpToVt(terminalId, false); + if (!daemonApp->seatdControl()->requestSwitchVt(terminalId)) { + qCritical() << "Failed to switch to greeter VT" << terminalId; + return false; + } if (!m_treeland->start()) return false; @@ -329,7 +330,8 @@ namespace DDM { QByteArray cookie; if (session.isSingleMode()) { auth->type = Treeland; - auth->tty = daemonApp->treelandConnector()->createGroupVtForTreeland(user, sessionId); + const int ownerPid = daemonApp->treelandConnector()->mainPid(); + auth->tty = daemonApp->seatdControl()->createGroupVt(ownerPid, user, sessionId); if (auth->tty <= 0) { qCritical() << "Failed to allocate grouped VT for Treeland user session"; auths.removeAll(auth); @@ -344,6 +346,13 @@ namespace DDM { auth->tty = fetchAvailableUserVt(); } + if (auth->tty <= 0) { + qCritical() << "Failed to allocate VT for user session"; + auths.removeAll(auth); + delete auth; + return; + } + qInfo() << "Authentication succeeded for user" << user << ", opening session" << session.fileName() << ", command:" << session.exec() << ", VT:" << auth->tty; @@ -401,7 +410,7 @@ namespace DDM { if (xdgSessionId <= 0) { qCritical() << "Failed to open logind session for user" << user; if (auth->type == Treeland) - daemonApp->treelandConnector()->destroyGroupVt(auth->tty); + daemonApp->seatdControl()->destroyGroupVt(auth->tty); auths.removeAll(auth); delete auth; return; @@ -412,7 +421,7 @@ namespace DDM { auths.removeAll(auth); daemonApp->displayManager()->RemoveSession(auth->sessionId); if (auth->type == Treeland) - daemonApp->treelandConnector()->destroyGroupVt(auth->tty); + daemonApp->seatdControl()->destroyGroupVt(auth->tty); delete auth; }); daemonApp->displayManager()->AddSession(sessionId, name, user, auth->tty); @@ -482,8 +491,8 @@ namespace DDM { manager.UnlockSession(QString::number(auth->xdgSessionId)); if (auth->type == Treeland) activateSession(user, auth->xdgSessionId); - else - VirtualTerminal::jumpToVt(auth->tty, false, false); + else if (!daemonApp->seatdControl()->requestSwitchVt(auth->tty)) + qWarning() << "Failed to switch to session VT" << auth->tty << "for user" << user; qInfo() << "Successfully identified user" << user; return; } diff --git a/src/daemon/SeatManager.cpp b/src/daemon/SeatManager.cpp index f414230..7f90d15 100644 --- a/src/daemon/SeatManager.cpp +++ b/src/daemon/SeatManager.cpp @@ -21,8 +21,11 @@ #include "SeatManager.h" #include "Configuration.h" +#include "Auth.h" #include "DaemonApp.h" #include "Display.h" +#include "DisplayManager.h" +#include "TreelandConnector.h" #include #include @@ -32,8 +35,54 @@ #include "LogindDBusTypes.h" #include +#include namespace DDM { + static constexpr auto greeterUserName = "dde"; + + static bool isVtRunningTreeland(int vtnr) { + for (Display *display : std::as_const(daemonApp->seatManager()->displays)) { + if (display->terminalId == vtnr) + return true; + for (Auth *auth : std::as_const(display->auths)) + if (auth->tty == vtnr && auth->type == Display::Treeland) + return true; + } + return false; + } + + static bool isTreelandGreeterVt(int vtnr) { + for (Display *display : std::as_const(daemonApp->seatManager()->displays)) { + if (display->terminalId == vtnr) + return true; + } + return false; + } + + static QString findTreelandUserByVt(int vtnr) { + if (vtnr <= 0) + return {}; + + auto user = daemonApp->displayManager()->findUserByVt(vtnr); + if (!user.isEmpty()) + return user; + + for (Display *display : std::as_const(daemonApp->seatManager()->displays)) { + for (Auth *auth : std::as_const(display->auths)) { + if (auth->tty == vtnr && auth->type == Display::Treeland) + return auth->user; + } + } + + if (isVtRunningTreeland(vtnr)) + user = daemonApp->displayManager()->LastActivatedUser(); + if (!user.isEmpty()) + return user; + + if (isTreelandGreeterVt(vtnr)) + return QString::fromLatin1(greeterUserName); + return {}; + } class LogindSeat : public QObject { Q_OBJECT @@ -112,7 +161,7 @@ namespace DDM { connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher, reply, this]() { watcher->deleteLater(); const auto seats = reply.value(); - for (const NamedSeatPath &seat : seats) { + for (const NamedSeatPath &seat : std::as_const(seats)) { logindSeatAdded(seat.name, seat.path); } }); @@ -195,6 +244,37 @@ namespace DDM { createSeat(name); } + void SeatManager::handleVtChanged(int oldVt, int newVt) { + const bool oldIsTreelandVt = isVtRunningTreeland(oldVt); + const bool oldIsGreeterVt = isTreelandGreeterVt(oldVt); + const auto oldUser = findTreelandUserByVt(oldVt); + const bool newIsTreelandVt = isVtRunningTreeland(newVt); + const bool newIsGreeterVt = isTreelandGreeterVt(newVt); + const auto newUser = findTreelandUserByVt(newVt); + + qInfo("dde-seatd VT change event: oldVt=%d oldIsTreelandVt=%d oldIsGreeterVt=%d oldUser=%s newVt=%d newIsTreelandVt=%d newIsGreeterVt=%d newUser=%s connected=%d", + oldVt, + oldIsTreelandVt, + oldIsGreeterVt, + qPrintable(oldUser), + newVt, + newIsTreelandVt, + newIsGreeterVt, + qPrintable(newUser), + daemonApp->treelandConnector()->isConnected()); + + if (!newIsTreelandVt) { + qInfo("External VT %d became active; leaving seat transition to dde-seatd/libseat", newVt); + return; + } + + if (newIsGreeterVt || newUser == QString::fromLatin1(greeterUserName)) { + daemonApp->treelandConnector()->switchToGreeter(); + } else { + daemonApp->treelandConnector()->switchToUser(newUser); + } + } + void SeatManager::logindSeatAdded(const QString& name, const QDBusObjectPath& objectPath) { auto logindSeat = new LogindSeat(name, objectPath); diff --git a/src/daemon/SeatManager.h b/src/daemon/SeatManager.h index 99f2253..b9f62cf 100644 --- a/src/daemon/SeatManager.h +++ b/src/daemon/SeatManager.h @@ -51,6 +51,9 @@ namespace DDM { void logindSeatRemoved(const QString &name, const QDBusObjectPath &objectPath); void displayStopped(); + public Q_SLOTS: + void handleVtChanged(int oldVt, int newVt); + private: void startDisplay(Display *display, int tryNr = 1); }; diff --git a/src/daemon/TreelandConnector.cpp b/src/daemon/TreelandConnector.cpp index 6aa4cf0..49b9ba1 100644 --- a/src/daemon/TreelandConnector.cpp +++ b/src/daemon/TreelandConnector.cpp @@ -2,233 +2,92 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "TreelandConnector.h" -#include "Auth.h" -#include "DaemonApp.h" -#include "Display.h" -#include "DisplayManager.h" -#include "SeatManager.h" + #include "treeland-ddm-v1.h" -#include #include #include #include #include -#include -#include #include #include #include #include -#include -#include -#include -#include #include -#include #include -#include #include -#include -#include -#include #include namespace DDM { -static constexpr auto controlSocketPath = "/run/dde-seatd-control.sock"; static constexpr auto systemdService = "org.freedesktop.systemd1"; static constexpr auto systemdPath = "/org/freedesktop/systemd1"; static constexpr auto systemdManagerInterface = "org.freedesktop.systemd1.Manager"; static constexpr auto systemdPropertiesInterface = "org.freedesktop.DBus.Properties"; static constexpr auto systemdServiceInterface = "org.freedesktop.systemd1.Service"; static constexpr auto treelandUnit = "treeland.service"; -static constexpr uint16_t maxControlPayloadSize = 4096; - -enum ControlOpcode : uint16_t { - ControlCreateGroupVt = 1, - ControlDestroyGroupVt = 2, - ControlGroupVtCreated = 100, - ControlVtChanged = 101, - ControlError = 255, -}; - -struct ControlHeader { - uint16_t opcode; - uint16_t size; -}; - -struct ControlCreateGroupVtRequest { - int32_t ownerPid; - int32_t vt; - char user[64]; - char session[64]; -}; - -struct ControlDestroyGroupVtRequest { - int32_t vt; -}; - -struct ControlGroupVtCreatedEvent { - int32_t ownerPid; - int32_t vt; -}; - -struct ControlVtChangeEvent { - int32_t oldVt; - int32_t newVt; -}; - -struct ControlErrorMessage { - int32_t error; -}; - -static int connectUnixSocket(const char *path) { - int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (fd == -1) - return -1; - - sockaddr_un addr {}; - addr.sun_family = AF_UNIX; - const size_t pathLength = strlen(path); - if (pathLength >= sizeof(addr.sun_path)) { - close(fd); - errno = ENAMETOOLONG; - return -1; - } - memcpy(addr.sun_path, path, pathLength + 1); - const auto size = static_cast(offsetof(sockaddr_un, sun_path) + pathLength + 1); - if (::connect(fd, reinterpret_cast(&addr), size) == -1) { - close(fd); - return -1; - } - return fd; -} -static ssize_t recvAll(int fd, void *buffer, size_t size) { - auto data = static_cast(buffer); - size_t received = 0; - while (received < size) { - const auto ret = recv(fd, data + received, size - received, 0); - if (ret == -1) { - if (errno == EINTR) - continue; - return -1; - } - if (ret == 0) { - errno = ECONNRESET; - return -1; - } - received += static_cast(ret); - } - return static_cast(received); +TreelandConnector::TreelandConnector(QObject *parent) + : QObject(parent) { } -static bool sendAll(int fd, const void *buffer, size_t size) { - const auto *data = static_cast(buffer); - size_t sent = 0; - while (sent < size) { - const auto ret = send(fd, data + sent, size - sent, MSG_NOSIGNAL); - if (ret == -1) { - if (errno == EINTR) - continue; - return false; - } - if (ret == 0) { - errno = EPIPE; - return false; - } - sent += static_cast(ret); - } - return true; +TreelandConnector::~TreelandConnector() { + disconnect(); } -template -static void copyFixedString(char (&target)[N], const QString &source) { - const auto bytes = source.toUtf8(); - const auto length = std::min(static_cast(bytes.size()), N - 1); - memcpy(target, bytes.constData(), static_cast(length)); - target[length] = '\0'; +bool TreelandConnector::isConnected() { + return m_ddm; } -static bool isVtRunningTreeland(int vtnr) { - for (Display *display : daemonApp->seatManager()->displays) { - if (display->terminalId == vtnr) - return true; - for (Auth *auth : display->auths) - if (auth->tty == vtnr && auth->type == Display::Treeland) - return true; +int TreelandConnector::mainPid() { + QDBusInterface systemd(systemdService, + systemdPath, + systemdManagerInterface, + QDBusConnection::systemBus()); + const auto unitReply = systemd.call(QStringLiteral("GetUnit"), QString::fromLatin1(treelandUnit)); + if (unitReply.type() == QDBusMessage::ErrorMessage) { + qWarning() << "Failed to get" << treelandUnit << "unit:" << unitReply.errorMessage(); + return -1; } - return false; -} -static bool isTreelandGreeterVt(int vtnr) { - for (Display *display : daemonApp->seatManager()->displays) { - if (display->terminalId == vtnr) - return true; - } - return false; -} - -static QString findTreelandUserByVt(int vtnr) { - if (vtnr <= 0) - return {}; - - auto user = daemonApp->displayManager()->findUserByVt(vtnr); - if (!user.isEmpty()) - return user; - - for (Display *display : daemonApp->seatManager()->displays) { - for (Auth *auth : display->auths) { - if (auth->tty == vtnr && auth->type == Display::Treeland) - return auth->user; - } + const auto unitPath = qvariant_cast(unitReply.arguments().value(0)).path(); + QDBusInterface properties(systemdService, + unitPath, + systemdPropertiesInterface, + QDBusConnection::systemBus()); + const auto reply = properties.call(QStringLiteral("Get"), + QString::fromLatin1(systemdServiceInterface), + QStringLiteral("MainPID")); + if (reply.type() == QDBusMessage::ErrorMessage) { + qWarning() << "Failed to get Treeland MainPID:" << reply.errorMessage(); + return -1; } - if (isVtRunningTreeland(vtnr)) - user = daemonApp->displayManager()->LastActivatedUser(); - if (!user.isEmpty()) - return user; - - for (Display *display : daemonApp->seatManager()->displays) { - if (display->terminalId == vtnr) - return QStringLiteral("dde"); + const auto variant = qvariant_cast(reply.arguments().value(0)).variant(); + bool ok = false; + const auto pid = variant.toULongLong(&ok); + if (!ok || pid == 0 || pid > static_cast(std::numeric_limits::max())) { + qWarning() << "Invalid Treeland MainPID from systemd:" << variant; + return -1; } - return {}; -} - -// TreelandConnector - -TreelandConnector::TreelandConnector() : QObject(nullptr) { -} - -TreelandConnector::~TreelandConnector() { - disconnectControlSocket(); - delete m_notifier; - if (m_display) - wl_display_disconnect(m_display); -} - -bool TreelandConnector::isConnected() { - return m_ddm; + return static_cast(pid); } void TreelandConnector::setPrivateObject(struct treeland_ddm_v1 *ddm) { m_ddm = ddm; } -void TreelandConnector::setSignalHandler() { -} - -// Event implementation - -static void switchToVt([[maybe_unused]] void *data, [[maybe_unused]] struct treeland_ddm_v1 *ddm, int32_t vtnr) { +static void switchToVt([[maybe_unused]] void *data, + [[maybe_unused]] struct treeland_ddm_v1 *ddm, + int32_t vtnr) { qWarning("Ignoring deprecated treeland switch_to_vt request for VT %d; wlroots/libseat handles VT switching directly", vtnr); } -static void acquireVt([[maybe_unused]] void *data, [[maybe_unused]] struct treeland_ddm_v1 *ddm, [[maybe_unused]] int32_t vtnr) { +static void acquireVt([[maybe_unused]] void *data, + [[maybe_unused]] struct treeland_ddm_v1 *ddm, + [[maybe_unused]] int32_t vtnr) { } const struct treeland_ddm_v1_listener treelandDDMListener { @@ -236,9 +95,7 @@ const struct treeland_ddm_v1_listener treelandDDMListener { .acquire_vt = acquireVt, }; -// wayland object binding - -void registerGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { +static void registerGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, "treeland_ddm_v1") == 0) { auto connector = static_cast(data); auto ddm = static_cast( @@ -250,10 +107,9 @@ void registerGlobal(void *data, struct wl_registry *registry, uint32_t name, con } } -void removeGlobal([[maybe_unused]] void *data, [[maybe_unused]] struct wl_registry *registry, [[maybe_unused]] uint32_t name) { - // Do not deregister the global object (set m_ddm to null) here, - // as wlroots will send global_remove event when session deactivated, - // which is not what we want. The connection will be preserved after that. +static void removeGlobal([[maybe_unused]] void *data, + [[maybe_unused]] struct wl_registry *registry, + [[maybe_unused]] uint32_t name) { } const struct wl_registry_listener registryListener { @@ -261,12 +117,8 @@ const struct wl_registry_listener registryListener { .global_remove = removeGlobal, }; -void TreelandConnector::connect(QString socketPath) { +void TreelandConnector::connect(const QString &socketPath) { disconnect(); - if (!connectControlSocket()) { - qCritical("Cannot connect Treeland without dde-seatd control socket"); - return; - } m_display = wl_display_connect(qPrintable(socketPath)); if (m_display == nullptr) { @@ -279,9 +131,10 @@ void TreelandConnector::connect(QString socketPath) { wl_display_roundtrip(m_display); - while (wl_display_dispatch_pending(m_display) > 0); + while (wl_display_dispatch_pending(m_display) > 0) { + } wl_display_flush(m_display); - m_notifier = new QSocketNotifier(wl_display_get_fd(m_display), QSocketNotifier::Read); + m_notifier = new QSocketNotifier(wl_display_get_fd(m_display), QSocketNotifier::Read, this); QObject::connect(m_notifier, &QSocketNotifier::activated, this, [this] { if (wl_display_dispatch(m_display) == -1 || wl_display_flush(m_display) == -1) { if (errno != EAGAIN) { @@ -293,272 +146,18 @@ void TreelandConnector::connect(QString socketPath) { } void TreelandConnector::disconnect() { - disconnectControlSocket(); + if (m_notifier) { + m_notifier->setEnabled(false); + delete m_notifier; + m_notifier = nullptr; + } if (m_display) { - if (m_notifier) - m_notifier->setEnabled(false); wl_display_disconnect(m_display); - if (m_notifier) { - m_notifier->deleteLater(); - m_notifier = nullptr; - } m_display = nullptr; } m_ddm = nullptr; } -bool TreelandConnector::connectControlSocket() { - if (m_controlSocket && m_controlSocket->state() == QLocalSocket::ConnectedState) - return true; - - disconnectControlSocket(); - - m_controlSocket = new QLocalSocket(this); - QObject::connect(m_controlSocket, &QLocalSocket::readyRead, this, &TreelandConnector::handleControlSocket); - QObject::connect(m_controlSocket, &QLocalSocket::disconnected, this, &TreelandConnector::disconnectControlSocket); - - m_controlSocket->connectToServer(QString::fromLatin1(controlSocketPath)); - if (!m_controlSocket->waitForConnected(3000)) { - qWarning() << "Failed to connect dde-seatd control socket" - << controlSocketPath << ":" << m_controlSocket->errorString(); - disconnectControlSocket(); - return false; - } - qDebug("Connected dde-seatd control event socket via QLocalSocket"); - return true; -} - -void TreelandConnector::disconnectControlSocket() { - if (m_controlSocket) { - if (m_controlSocket->state() == QLocalSocket::ConnectedState - || m_controlSocket->state() == QLocalSocket::ClosingState) { - qDebug("dde-seatd control socket disconnected"); - } - m_controlSocket->blockSignals(true); - if (m_controlSocket->state() != QLocalSocket::UnconnectedState) - m_controlSocket->disconnectFromServer(); - m_controlSocket->deleteLater(); - m_controlSocket = nullptr; - } - m_controlBuffer.clear(); -} - -int TreelandConnector::treelandMainPid() const { - QDBusInterface systemd(systemdService, - systemdPath, - systemdManagerInterface, - QDBusConnection::systemBus()); - const auto unitReply = systemd.call(QStringLiteral("GetUnit"), QString::fromLatin1(treelandUnit)); - if (unitReply.type() == QDBusMessage::ErrorMessage) { - qWarning() << "Failed to get" << treelandUnit << "unit:" << unitReply.errorMessage(); - return -1; - } - - const auto unitPath = qvariant_cast(unitReply.arguments().value(0)).path(); - QDBusInterface properties(systemdService, - unitPath, - systemdPropertiesInterface, - QDBusConnection::systemBus()); - const auto reply = properties.call(QStringLiteral("Get"), - QString::fromLatin1(systemdServiceInterface), - QStringLiteral("MainPID")); - if (reply.type() == QDBusMessage::ErrorMessage) { - qWarning() << "Failed to get Treeland MainPID:" << reply.errorMessage(); - return -1; - } - - const auto variant = qvariant_cast(reply.arguments().value(0)).variant(); - bool ok = false; - const auto pid = variant.toULongLong(&ok); - if (!ok || pid == 0 || pid > static_cast(std::numeric_limits::max())) { - qWarning() << "Invalid Treeland MainPID from systemd:" << variant; - return -1; - } - return static_cast(pid); -} - -int TreelandConnector::createGroupVtForTreeland(const QString &user, const QString &sessionId) { - const int ownerPid = treelandMainPid(); - if (ownerPid <= 0) - return -1; - - const int fd = connectUnixSocket(controlSocketPath); - if (fd == -1) { - qWarning("Failed to connect dde-seatd control socket %s: %s", - controlSocketPath, strerror(errno)); - return -1; - } - - ControlHeader header { - .opcode = ControlCreateGroupVt, - .size = sizeof(ControlCreateGroupVtRequest), - }; - ControlCreateGroupVtRequest request {}; - request.ownerPid = ownerPid; - request.vt = 0; - copyFixedString(request.user, user); - copyFixedString(request.session, sessionId); - - struct { - ControlHeader header; - ControlCreateGroupVtRequest request; - } message { - .header = header, - .request = request, - }; - - if (!sendAll(fd, &message, sizeof(message))) { - qWarning("Failed to send create-group-vt request: %s", strerror(errno)); - close(fd); - return -1; - } - - pollfd pollFd { - .fd = fd, - .events = POLLIN, - .revents = 0, - }; - int pollResult = -1; - do { - pollResult = poll(&pollFd, 1, 3000); - } while (pollResult == -1 && errno == EINTR); - if (pollResult == -1) { - qWarning("Failed waiting for dde-seatd create-group-vt response: %s", strerror(errno)); - close(fd); - return -1; - } - if (pollResult == 0) { - qWarning("Timed out waiting for dde-seatd create-group-vt response"); - close(fd); - return -1; - } - if ((pollFd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0 || (pollFd.revents & POLLIN) == 0) { - qWarning("Invalid dde-seatd create-group-vt poll events: 0x%x", pollFd.revents); - close(fd); - errno = ECONNRESET; - return -1; - } - - ControlHeader responseHeader {}; - if (recvAll(fd, &responseHeader, sizeof(responseHeader)) != static_cast(sizeof(responseHeader))) { - qWarning("Failed to read create-group-vt response header: %s", strerror(errno)); - close(fd); - return -1; - } - - int vt = -1; - if (responseHeader.opcode == ControlGroupVtCreated && - responseHeader.size == sizeof(ControlGroupVtCreatedEvent)) { - ControlGroupVtCreatedEvent event {}; - if (recvAll(fd, &event, sizeof(event)) == static_cast(sizeof(event))) - vt = event.vt; - } else if (responseHeader.opcode == ControlError && - responseHeader.size == sizeof(ControlErrorMessage)) { - ControlErrorMessage error {}; - if (recvAll(fd, &error, sizeof(error)) == static_cast(sizeof(error))) - qWarning("dde-seatd create-group-vt failed: %s", strerror(error.error)); - } else { - qWarning("Unexpected dde-seatd create-group-vt response opcode %u size %u", - responseHeader.opcode, responseHeader.size); - } - - close(fd); - if (vt > 0 && !connectControlSocket()) { - qWarning("Failed to reconnect dde-seatd control event socket, rolling back grouped VT %d", vt); - destroyGroupVt(vt); - return -1; - } - return vt; -} - -bool TreelandConnector::sendDestroyGroupVt(int vt) { - const int fd = connectUnixSocket(controlSocketPath); - if (fd == -1) - return false; - - ControlHeader header { - .opcode = ControlDestroyGroupVt, - .size = sizeof(ControlDestroyGroupVtRequest), - }; - ControlDestroyGroupVtRequest request { - .vt = vt, - }; - struct { - ControlHeader header; - ControlDestroyGroupVtRequest request; - } message { - .header = header, - .request = request, - }; - const bool ok = sendAll(fd, &message, sizeof(message)); - close(fd); - return ok; -} - -void TreelandConnector::destroyGroupVt(int vt) { - if (vt > 0 && !sendDestroyGroupVt(vt)) - qWarning("Failed to destroy grouped VT %d: %s", vt, strerror(errno)); -} - -void TreelandConnector::handleControlSocket() { - if (!m_controlSocket) - return; - - const QByteArray chunk = m_controlSocket->readAll(); - if (chunk.isEmpty()) - return; - - m_controlBuffer.append(chunk); - while (m_controlBuffer.size() >= static_cast(sizeof(ControlHeader))) { - ControlHeader header {}; - memcpy(&header, m_controlBuffer.constData(), sizeof(header)); - if (header.size > maxControlPayloadSize) { - qWarning("Invalid dde-seatd control message size %u", header.size); - disconnectControlSocket(); - return; - } - const int messageSize = static_cast(sizeof(ControlHeader) + header.size); - if (m_controlBuffer.size() < messageSize) - return; - - const auto payload = m_controlBuffer.constData() + sizeof(ControlHeader); - if (header.opcode == ControlVtChanged && header.size == sizeof(ControlVtChangeEvent)) { - ControlVtChangeEvent event {}; - memcpy(&event, payload, sizeof(event)); - const bool oldIsTreelandVt = isVtRunningTreeland(event.oldVt); - const bool oldIsGreeterVt = isTreelandGreeterVt(event.oldVt); - const auto oldUser = findTreelandUserByVt(event.oldVt); - const bool newIsTreelandVt = isVtRunningTreeland(event.newVt); - const bool newIsGreeterVt = isTreelandGreeterVt(event.newVt); - const auto newUser = findTreelandUserByVt(event.newVt); - qInfo("dde-seatd VT change event: oldVt=%d oldIsTreelandVt=%d oldIsGreeterVt=%d oldUser=%s newVt=%d newIsTreelandVt=%d newIsGreeterVt=%d newUser=%s connected=%d", - event.oldVt, - oldIsTreelandVt, - oldIsGreeterVt, - qPrintable(oldUser), - event.newVt, - newIsTreelandVt, - newIsGreeterVt, - qPrintable(newUser), - isConnected()); - if (newIsTreelandVt) { - if (newIsGreeterVt || newUser == QStringLiteral("dde")) { - switchToGreeter(); - } else { - switchToUser(newUser); - } - } else { - qInfo("External VT %d became active; leaving seat transition to dde-seatd/libseat", event.newVt); - } - } else { - qDebug("Ignored dde-seatd control message opcode=%u size=%u", header.opcode, header.size); - } - m_controlBuffer.remove(0, messageSize); - } -} - -// Request wrapper - void TreelandConnector::switchToGreeter() { if (isConnected()) { qDebug("Calling treeland switch_to_greeter"); @@ -569,7 +168,7 @@ void TreelandConnector::switchToGreeter() { } } -void TreelandConnector::switchToUser(const QString username) { +void TreelandConnector::switchToUser(const QString &username) { if (isConnected()) { qDebug("Calling treeland switch_to_user: user=%s", qPrintable(username)); treeland_ddm_v1_switch_to_user(m_ddm, qPrintable(username)); diff --git a/src/daemon/TreelandConnector.h b/src/daemon/TreelandConnector.h index 512ce7c..7261e1c 100644 --- a/src/daemon/TreelandConnector.h +++ b/src/daemon/TreelandConnector.h @@ -2,9 +2,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include #include -#include +#include struct wl_display; struct treeland_ddm_v1; @@ -13,29 +12,19 @@ namespace DDM { class TreelandConnector : public QObject { Q_OBJECT public: - TreelandConnector(); + explicit TreelandConnector(QObject *parent = nullptr); ~TreelandConnector(); bool isConnected(); + int mainPid(); void setPrivateObject(struct treeland_ddm_v1 *ddm); - void setSignalHandler(); - void connect(const QString socketPath); + void connect(const QString &socketPath); void disconnect(); - int createGroupVtForTreeland(const QString &user, const QString &sessionId); - void destroyGroupVt(int vt); - void switchToGreeter(); - void switchToUser(const QString username); -private: - bool connectControlSocket(); - void disconnectControlSocket(); - void handleControlSocket(); - int treelandMainPid() const; - bool sendDestroyGroupVt(int vt); + void switchToUser(const QString &username); +private: struct wl_display *m_display { nullptr }; QSocketNotifier *m_notifier { nullptr }; - QLocalSocket *m_controlSocket { nullptr }; struct treeland_ddm_v1 *m_ddm { nullptr }; - QByteArray m_controlBuffer; }; } diff --git a/src/daemon/TtyUtils.h b/src/daemon/TtyUtils.h new file mode 100644 index 0000000..0b8053c --- /dev/null +++ b/src/daemon/TtyUtils.h @@ -0,0 +1,21 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef DDM_TTYUTILS_H +#define DDM_TTYUTILS_H + +#include + +namespace DDM::TtyUtils { + inline QString path(int tty) { +#ifdef __FreeBSD__ + const char c = tty <= 10 ? static_cast('0' + (tty - 1)) + : static_cast('a' + (tty - 11)); + return QStringLiteral("/dev/ttyv%1").arg(c); +#else + return QStringLiteral("/dev/tty%1").arg(tty); +#endif + } +} + +#endif diff --git a/src/daemon/UserSession.cpp b/src/daemon/UserSession.cpp index 63bf372..8b27230 100644 --- a/src/daemon/UserSession.cpp +++ b/src/daemon/UserSession.cpp @@ -25,9 +25,9 @@ #include "Auth.h" #include "Configuration.h" +#include "TtyUtils.h" #include "TreelandConnector.h" #include "UserSession.h" -#include "VirtualTerminal.h" #include "XAuth.h" #include @@ -43,6 +43,7 @@ #include #include #include +#include namespace DDM { UserSession::UserSession(Auth *parent) @@ -135,7 +136,7 @@ namespace DDM { // the session as stdin so that it stays open without races if (auth->type != Display::X11) { // open VT and get the fd - QString ttyString = VirtualTerminal::path(auth->tty); + QString ttyString = TtyUtils::path(auth->tty); int vtFd = ::open(qPrintable(ttyString), O_RDWR | O_NOCTTY); // when this is true we'll take control of the tty @@ -173,7 +174,8 @@ namespace DDM { } // enter Linux namespaces - for (const QString &ns: mainConfig.Namespaces.get()) { + const auto namespaces = mainConfig.Namespaces.get(); + for (const QString &ns : std::as_const(namespaces)) { qInfo() << "Entering namespace" << ns; int fd = ::open(qPrintable(ns), O_RDONLY); if (fd < 0) { diff --git a/src/daemon/VirtualTerminal.cpp b/src/daemon/VirtualTerminal.cpp deleted file mode 100644 index 11e6135..0000000 --- a/src/daemon/VirtualTerminal.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2023-2026 UnionTech Software Technology Co., Ltd. - * Copyright (c) 2015 Pier Luigi Fiorini - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - ***************************************************************************/ - -#include -#include - -#include "VirtualTerminal.h" - -#include "DaemonApp.h" -#include "SignalHandler.h" - -#include -#include -#include -#include -#include -#ifdef __FreeBSD__ -#include -#else -#include -#include -#endif -#include -#include -#include - -#define RELEASE_DISPLAY_SIGNAL (SIGRTMAX) -#define ACQUIRE_DISPLAY_SIGNAL (SIGRTMAX - 1) - -namespace DDM { - namespace VirtualTerminal { -#ifdef __FreeBSD__ - static const char *defaultVtPath = "/dev/ttyv0"; - - QString path(int vt) { - char c = (vt <= 10 ? '0' : 'a') + (vt - 1); - return QStringLiteral("/dev/ttyv%1").arg(c); - } - - int getVtActive(int fd) { - int vtActive = 0; - if (ioctl(fd, VT_GETACTIVE, &vtActive) < 0) { - qCritical() << "Failed to get current VT:" << strerror(errno); - return -1; - } - return vtActive; - } -#else - static const char *defaultVtPath = "/dev/tty0"; - - QString path(int vt) { - return QStringLiteral("/dev/tty%1").arg(vt); - } - - int getVtActive(int fd) { - vt_stat vtState { }; - if (ioctl(fd, VT_GETSTATE, &vtState) < 0) { - qCritical() << "Failed to get current VT:" << strerror(errno); - return -1; - } - return vtState.v_active; - } -#endif - - std::function onAcquireFunction = nullptr; - std::function onReleaseFunction = nullptr; - - static void onVtSignal(int signal) { - if (signal == RELEASE_DISPLAY_SIGNAL) { - if (onReleaseFunction) { - onReleaseFunction(); - } else { - int fd = open(defaultVtPath, O_RDWR | O_NOCTTY); - ioctl(fd, VT_RELDISP, 1); - close(fd); - } - } else if (signal == ACQUIRE_DISPLAY_SIGNAL) { - if (onAcquireFunction) { - onAcquireFunction(); - } else { - int fd = open(defaultVtPath, O_RDWR | O_NOCTTY); - ioctl(fd, VT_RELDISP, VT_ACKACQ); - close(fd); - } - } - } - - bool handleVtSwitches(int fd) { - vt_mode setModeRequest { }; - bool ok = true; - - setModeRequest.mode = VT_PROCESS; - setModeRequest.relsig = RELEASE_DISPLAY_SIGNAL; - setModeRequest.acqsig = ACQUIRE_DISPLAY_SIGNAL; - - if (ioctl(fd, VT_SETMODE, &setModeRequest) < 0) { - qDebug() << "Failed to manage VT manually:" << strerror(errno); - ok = false; - } - - daemonApp->signalHandler()->addCustomSignal(RELEASE_DISPLAY_SIGNAL); - daemonApp->signalHandler()->addCustomSignal(ACQUIRE_DISPLAY_SIGNAL); - static bool vtSignalConnected = false; - if (!vtSignalConnected) { - QObject::connect(daemonApp->signalHandler(), &SignalHandler::customSignalReceived, - daemonApp->signalHandler(), onVtSignal); - vtSignalConnected = true; - } - - return ok; - } - - static void fixVtMode(int fd, bool vt_auto) { - vt_mode getmodeReply { }; - int kernelDisplayMode = 0; - bool modeFixed = false; - bool ok = true; - - if (ioctl(fd, VT_GETMODE, &getmodeReply) < 0) { - qWarning() << "Failed to query VT mode:" << strerror(errno); - ok = false; - } - - if (getmodeReply.mode != VT_AUTO) - goto out; - - if (ioctl(fd, KDGETMODE, &kernelDisplayMode) < 0) { - qWarning() << "Failed to query kernel display mode:" << strerror(errno); - ok = false; - } - - if (kernelDisplayMode == KD_TEXT) - goto out; - - // VT is in the VT_AUTO + KD_GRAPHICS state, fix it - if (vt_auto) { - // If vt_auto is true, the controlling process is already gone, so there is no - // process which could send the VT_RELDISP 1 ioctl to release the vt. - // Switch to KD_TEXT and let the kernel switch vts automatically - if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) { - qWarning("Failed to set text mode for current VT: %s", strerror(errno)); - ok = false; - } - } - else { - ok = handleVtSwitches(fd); - modeFixed = true; - } -out: - if (!ok) { - qCritical() << "Failed to set up VT mode"; - return; - } - - if (modeFixed) - qDebug() << "VT mode fixed"; - else - qDebug() << "VT mode didn't need to be fixed"; - } - - int currentVt() - { - int fd = open(defaultVtPath, O_RDWR | O_NOCTTY); - if (fd < 0) { - qCritical() << "Failed to open VT master:" << strerror(errno); - return -1; - } - auto closeFd = qScopeGuard([fd] { - close(fd); - }); - - return getVtActive(fd); - } - - - int setUpNewVt() { - // open VT master - int fd = open(defaultVtPath, O_RDWR | O_NOCTTY); - if (fd < 0) { - qCritical() << "Failed to open VT master:" << strerror(errno); - return -1; - } - auto closeFd = qScopeGuard([fd] { - close(fd); - }); - - int vt = 0; - if (ioctl(fd, VT_OPENQRY, &vt) < 0) { - qCritical() << "Failed to open new VT:" << strerror(errno); - return -1; - } - - // fallback to active VT - if (vt <= 0) { - int vtActive = getVtActive(fd); - qWarning() << "New VT" << vt << "is not valid, fall back to" << vtActive; - return vtActive; - } - - return vt; - } - - static bool waitForVtActivation(int fd, int vt, bool wait) { - if (!wait) - return true; - - while (true) { - if (ioctl(fd, VT_WAITACTIVE, vt) == 0) - return true; - if (errno == EINTR) - continue; - - qWarning("Couldn't finalize jump to VT %d: %s", vt, strerror(errno)); - return false; - } - } - - static bool requestVtActivation(int fd, int vt, bool wait) { - while (true) { - if (ioctl(fd, VT_ACTIVATE, vt) == 0) - return waitForVtActivation(fd, vt, wait); - if (errno == EINTR) - continue; - - qWarning("Couldn't initiate jump to VT %d: %s", vt, strerror(errno)); - return false; - } - } - - void activateVt(int vt, bool wait) { - int fd = open(qPrintable(path(vt)), O_RDWR | O_NOCTTY); - if (fd == -1) { - qWarning("Failed to open VT %d: %s", vt, strerror(errno)); - return; - } - - requestVtActivation(fd, vt, wait); - close(fd); - } - - void jumpToVt(int vt, bool vt_auto, bool wait) { - qDebug() << "Jumping to VT" << vt; - - int fd; - - int activeVtFd = open(defaultVtPath, O_RDWR | O_NOCTTY); - - QString ttyString = path(vt); - int vtFd = open(qPrintable(ttyString), O_RDWR | O_NOCTTY); - if (vtFd != -1) { - fd = vtFd; - - // Clear VT - static const char *clearEscapeSequence = "\33[H\33[2J"; - if (write(vtFd, clearEscapeSequence, sizeof(clearEscapeSequence)) == -1) { - qWarning("Failed to clear VT %d: %s", vt, strerror(errno)); - } - - // set graphics mode to prevent flickering - if (ioctl(fd, KDSETMODE, KD_GRAPHICS) < 0) - qWarning("Failed to set graphics mode for VT %d: %s", vt, strerror(errno)); - - // it's possible that the current VT was left in a broken - // combination of states (KD_GRAPHICS with VT_AUTO) that we - // cannot switch from, so make sure things are in a way that - // will make VT_ACTIVATE work without hanging VT_WAITACTIVE - fixVtMode(activeVtFd, vt_auto); - } else { - qWarning("Failed to open %s: %s", qPrintable(ttyString), strerror(errno)); - qDebug("Using %s instead of %s!", defaultVtPath, qPrintable(ttyString)); - fd = activeVtFd; - } - - // If vt_auto is true, the controlling process is already gone, so there is no - // process which could send the VT_RELDISP 1 ioctl to release the vt. - // Let the kernel switch vts automatically - if (!vt_auto) - handleVtSwitches(fd); - - requestVtActivation(fd, vt, wait); - close(activeVtFd); - if (vtFd != -1) - close(vtFd); - } - - void setVtSignalHandler(std::function onAcquireDisplay, std::function onReleaseDisplay) { - onAcquireFunction = onAcquireDisplay; - onReleaseFunction = onReleaseDisplay; - } - } -} diff --git a/src/daemon/VirtualTerminal.h b/src/daemon/VirtualTerminal.h deleted file mode 100644 index c3d3bf0..0000000 --- a/src/daemon/VirtualTerminal.h +++ /dev/null @@ -1,39 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2023-2026 UnionTech Software Technology Co., Ltd. - * Copyright (c) 2015 Pier Luigi Fiorini - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - ***************************************************************************/ - -#ifndef DDM_VIRTUALTERMINAL_H -#define DDM_VIRTUALTERMINAL_H - -#include - -namespace DDM { - namespace VirtualTerminal { - QString path(int vt); - int getVtActive(int fd); - bool handleVtSwitches(int fd); - int currentVt(); - int setUpNewVt(); - void activateVt(int vt, bool wait = true); - void jumpToVt(int vt, bool vt_auto, bool wait = true); - void setVtSignalHandler(std::function onAcquireDisplay, std::function onReleaseDisplay); - } -} - -#endif // DDM_VIRTUALTERMINAL_H