diff --git a/.github/workflows/ddm-archlinux-build.yml b/.github/workflows/ddm-archlinux-build.yml index f9143ed..fff6678 100644 --- a/.github/workflows/ddm-archlinux-build.yml +++ b/.github/workflows/ddm-archlinux-build.yml @@ -1,7 +1,6 @@ name: Build ddm on Arch Linux # Dependencies are based on the official ArchLinux PKGBUILD -# treeland-protocols-git is installed from source on: push: @@ -21,21 +20,7 @@ jobs: - name: Install base dependencies run: | - pacman -S --noconfirm --noprogressbar base-devel bash gcc-libs glibc libxau pam qt6-base qt6-declarative systemd-libs ttf-font xorg-server xorg-xauth git extra-cmake-modules python-docutils qt6-tools treeland-protocols ninja - - - name: Build and Install treeland-protocols - run: | - echo "Building treeland-protocols from source..." - git clone https://github.com/linuxdeepin/treeland-protocols.git --depth 1 - cd treeland-protocols - cmake -B build \ - -DCMAKE_INSTALL_LIBDIR=lib \ - -DCMAKE_INSTALL_LIBEXECDIR=lib \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_BUILD_TYPE=Release - cmake --build build - cmake --install build - echo "✅ treeland-protocols built and installed from source" + pacman -S --noconfirm --noprogressbar base-devel bash gcc-libs glibc libxau pam qt6-base qt6-declarative qt6-remoteobjects systemd-libs ttf-font xorg-server xorg-xauth git extra-cmake-modules python-docutils qt6-tools ninja - uses: actions/checkout@v4 with: diff --git a/.github/workflows/ddm-deepin-build.yml b/.github/workflows/ddm-deepin-build.yml index 2f51267..d1c06bc 100644 --- a/.github/workflows/ddm-deepin-build.yml +++ b/.github/workflows/ddm-deepin-build.yml @@ -23,22 +23,6 @@ jobs: apt-get update apt-get install -y devscripts equivs git - # Save the ddm workspace directory - DDM_DIR="$PWD" - echo "ddm workspace directory: $DDM_DIR" - - # Build and install treeland-protocols locally (required build dependency not from repo) - echo "Building treeland-protocols from source (master)" - cd /tmp - if [ ! -d treeland-protocols ]; then - git clone --depth=1 --branch master --single-branch https://github.com/linuxdeepin/treeland-protocols.git - fi - cd treeland-protocols - mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control - dpkg-buildpackage -uc -us -b - apt-get install -y ../*.deb || dpkg -i ../*.deb || apt-get -f install -y - cd "$DDM_DIR" - # Install build-deps defined in ddm/debian/control mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control diff --git a/CMakeLists.txt b/CMakeLists.txt index dfcc6f4..1d6885d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,14 +73,8 @@ pkg_search_module(LibSystemd REQUIRED IMPORTED_TARGET libsystemd) # XAU pkg_search_module(LibXau REQUIRED IMPORTED_TARGET xau) -# Wayland client -pkg_search_module(WaylandClient REQUIRED IMPORTED_TARGET wayland-client) - -# TreelandProtocols -find_package(TreelandProtocols 0.5.3 REQUIRED) - # Qt6 -find_package(Qt6 CONFIG REQUIRED Core DBus Network) +find_package(Qt6 CONFIG REQUIRED Core DBus Network RemoteObjects) qt_standard_project_setup(REQUIRES 6.6) # Uninstall target diff --git a/debian/control b/debian/control index 118d855..5ee343f 100644 --- a/debian/control +++ b/debian/control @@ -6,12 +6,11 @@ Build-Depends: debhelper-compat (= 13), extra-cmake-modules (>= 1.4.0~), libpam0g-dev, libsystemd-dev [linux-any], - libwayland-dev, libxau-dev, pkg-config, qt6-base-dev (>= 6.6.1~), + qt6-remoteobjects-dev, systemd [linux-any], - treeland-protocols, Standards-Version: 4.6.0 Section: libs Homepage: https://github.com/linuxdeepin/ddm.git diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 1a84cf2..6beb349 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -1,21 +1,3 @@ -# Generate treeland-ddm protocol files -set(TREELAND_DDM_HEADER ${CMAKE_CURRENT_BINARY_DIR}/treeland-ddm-v1.h) -set(TREELAND_DDM_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/treeland-ddm-v1.c) - -add_custom_command( - OUTPUT ${TREELAND_DDM_HEADER} - COMMAND wayland-scanner client-header < ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-ddm-v1.xml > ${TREELAND_DDM_HEADER} - DEPENDS ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-ddm-v1.xml - COMMENT "Generating treeland-ddm-v1.h" -) - -add_custom_command( - OUTPUT ${TREELAND_DDM_SOURCE} - COMMAND wayland-scanner private-code < ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-ddm-v1.xml > ${TREELAND_DDM_SOURCE} - DEPENDS ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-ddm-v1.xml - COMMENT "Generating treeland-ddm-v1.c" -) - configure_file(config.h.in config.h IMMEDIATE @ONLY) set(DAEMON_SOURCES Auth.cpp @@ -30,7 +12,6 @@ set(DAEMON_SOURCES UserSession.cpp XorgDisplayServer.cpp TreelandDisplayServer.cpp - ${TREELAND_DDM_SOURCE} ) qt_add_dbus_adaptor(DAEMON_SOURCES "${CMAKE_SOURCE_DIR}/data/interfaces/org.freedesktop.DisplayManager.xml" "DisplayManager.h" DDM::DisplayManager) @@ -59,15 +40,21 @@ qt_add_dbus_interface(DAEMON_SOURCES "${CMAKE_SOURCE_DIR}/data/interfaces/org.fr qt_add_dbus_interface(DAEMON_SOURCES "${CMAKE_SOURCE_DIR}/data/interfaces/org.freedesktop.login1.User.xml" "Login1User") add_executable(ddm ${DAEMON_SOURCES}) +qt_add_repc_sources(ddm + ${CMAKE_CURRENT_SOURCE_DIR}/ddmremote.rep +) +qt_add_repc_replicas(ddm + ${CMAKE_CURRENT_SOURCE_DIR}/treelandremote.rep +) target_link_libraries(ddm PRIVATE common PUBLIC Qt6::DBus Qt6::Network + Qt6::RemoteObjects PkgConfig::Pam PkgConfig::LibSystemd - PkgConfig::WaylandClient ) install(TARGETS ddm DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index 0bc69e0..c38d319 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -27,29 +27,31 @@ #include "DaemonApp.h" #include "DdeSeatdControl.h" #include "DisplayManager.h" -#include "Messages.h" +#include "Login1Manager.h" +#include "Login1Session.h" #include "SeatManager.h" #include "SocketServer.h" -#include "SocketWriter.h" #include "TreelandConnector.h" #include "TreelandDisplayServer.h" #include "XorgDisplayServer.h" - #include "config.h" -#include "Login1Manager.h" + +#include +#include #include +#include +#include +#include #include #include -#include #include +#include +#include #include -#include -#include #include #include -#include #include #define STRINGIFY(x) #x @@ -121,7 +123,7 @@ namespace DDM { Display::Display(SeatManager *parent, QString name) : QObject(parent) , name(name) - , m_socketServer(new SocketServer(this)) { + , m_socketServer(new SocketServer(this, this)) { // Create display server terminalId = fetchAvailableVt(); @@ -129,29 +131,16 @@ namespace DDM { m_treeland = new TreelandDisplayServer(m_socketServer, this); // Record current VT as ddm user session - DaemonApp::instance()->displayManager()->AddSession( - {}, - name, - "dde", - terminalId); - - // connect connected signal - connect(m_socketServer, &SocketServer::connected, this, &Display::connected); - - // connect login signal - connect(m_socketServer, &SocketServer::login, this, &Display::login); - - // connect logout signal - connect(m_socketServer, &SocketServer::logout, this, &Display::logout); - - // connect lock signal - connect(m_socketServer, &SocketServer::lock, this, &Display::lock); - - // connect unlock signal - connect(m_socketServer, &SocketServer::unlock,this, &Display::unlock); + DaemonApp::instance()->displayManager()->AddSession({ }, name, "dde", terminalId); // connect login result signals connect(this, &Display::loginFailed, m_socketServer, &SocketServer::loginFailed); + + // connect Treeland lock state → sync to logind + connect(daemonApp->treelandConnector(), + &TreelandConnector::lockStateChanged, + this, + &Display::onTreelandLockStateChanged); } Display::~Display() { @@ -159,18 +148,20 @@ namespace DDM { } void Display::activateSession(const QString &user, int xdgSessionId) { - qWarning() << "Display activateSession requested for user" << user - << "xdgSessionId" << xdgSessionId - << "display VT" << terminalId; + qWarning() << "Display activateSession requested for user" << user << "xdgSessionId" + << xdgSessionId << "display VT" << terminalId; if (xdgSessionId <= 0 && user != QStringLiteral("dde")) { qCritical() << "Invalid xdg session id" << xdgSessionId << "for user" << user; return; } + m_activeTreelandSessionId = xdgSessionId; m_treeland->activateUser(user, xdgSessionId); if (xdgSessionId > 0 && Logind::isAvailable()) { - OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), Logind::managerPath(), QDBusConnection::systemBus()); + OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), + Logind::managerPath(), + QDBusConnection::systemBus()); manager.ActivateSession(QString::number(xdgSessionId)); } } @@ -183,19 +174,14 @@ namespace DDM { qCritical() << "Failed to switch to greeter VT" << terminalId; return false; } - if (!m_treeland->start()) + // start socket server before treeland so the greeter can connect back to DDM + if (!m_socketServer->start()) return false; - // start socket server - m_socketServer->start("treeland"); - - // Update dbus info - DaemonApp::instance()->displayManager()->setAuthInfo(m_socketServer->socketAddress()); - - // change the owner and group of the socket to avoid permission denied errors - struct passwd *pw = getpwnam("dde"); - if (pw && chown(qPrintable(m_socketServer->socketAddress()), pw->pw_uid, pw->pw_gid) == -1) - qWarning() << "Failed to change owner of the socket"; + if (!m_treeland->start()) { + m_socketServer->stop(); + return false; + } // set flags m_started = true; @@ -230,21 +216,100 @@ namespace DDM { emit stopped(); } - void Display::connected(QLocalSocket *socket) { - // send logged in users (for possible crash recovery) - SocketWriter writer(socket); - for (Auth *auth : std::as_const(auths)) { - if (auth->sessionOpened) - writer << quint32(DaemonMessages::UserLoggedIn) << auth->user << auth->xdgSessionId; - } + void Display::connected() { + m_socketServer->replayUserSessions(); + } + + void Display::onTreelandLockStateChanged(bool locked) { + m_treelandLocked = locked; + if (m_activeTreelandSessionId <= 0) + return; + OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), + Logind::managerPath(), + QDBusConnection::systemBus()); + if (locked) + manager.LockSession(QString::number(m_activeTreelandSessionId)); + else + manager.UnlockSession(QString::number(m_activeTreelandSessionId)); } - void Display::login(QLocalSocket *socket, - const QString &user, const QString &password, - const Session &session) { + void Display::watchUserSession(Auth *auth) { + if (!auth || auth->xdgSessionId <= 0) + return; + + OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), + Logind::managerPath(), + QDBusConnection::systemBus()); + auto reply = manager.GetSession(QString::number(auth->xdgSessionId)); + auto *watcher = new QDBusPendingCallWatcher(reply, this); + QPointer authPtr(auth); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, authPtr] { + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + if (!authPtr) + return; + if (reply.isError()) { + qWarning() << "Failed to get logind session path" << authPtr->xdgSessionId + << reply.error().message(); + return; + } + + auto *session = new OrgFreedesktopLogin1SessionInterface(Logind::serviceName(), + reply.value().path(), + QDBusConnection::systemBus(), + authPtr); + session->setObjectName(QStringLiteral("logindSessionWatcher")); + connect(session, &OrgFreedesktopLogin1SessionInterface::Lock, this, [this, authPtr] { + if (!authPtr) + return; + if (m_activeTreelandSessionId != authPtr->xdgSessionId) + return; + daemonApp->treelandConnector()->lock(); + }); + connect(session, &OrgFreedesktopLogin1SessionInterface::Unlock, this, [this, authPtr] { + if (!authPtr) + return; + if (m_activeTreelandSessionId != authPtr->xdgSessionId) { + OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), + Logind::managerPath(), + QDBusConnection::systemBus()); + manager.LockSession(QString::number(authPtr->xdgSessionId)); + return; + } + if (m_treelandLocked) { + constexpr int windowMs = 2000; + constexpr int maxLockBacks = 3; + const auto now = std::chrono::steady_clock::now().time_since_epoch().count(); + if (now - m_lockBackWindowStart > windowMs) { + m_lockBackWindowStart = now; + m_lockBackCount = 0; + } + if (m_lockBackCount >= maxLockBacks) + return; + OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), + Logind::managerPath(), + QDBusConnection::systemBus()); + manager.LockSession(QString::number(authPtr->xdgSessionId)); + ++m_lockBackCount; + } + }); + }); + } + + void Display::unwatchUserSession(Auth *auth) { + if (!auth) + return; + + const auto watchers = auth->findChildren( + QStringLiteral("logindSessionWatcher")); + for (auto *watcher : watchers) + watcher->deleteLater(); + } + + void Display::login(const QString &user, const QString &password, const Session &session) { if (user == QLatin1String("dde")) { qWarning() << "Login attempt for user dde"; - emit loginFailed(socket, user); + emit loginFailed(user); return; } @@ -304,7 +369,7 @@ namespace DDM { auths.removeAll(auth); delete auth; } - Q_EMIT loginFailed(socket, user); + Q_EMIT loginFailed(user); return; } @@ -330,7 +395,7 @@ namespace DDM { QByteArray cookie; if (session.isSingleMode()) { auth->type = Treeland; - const int ownerPid = daemonApp->treelandConnector()->mainPid(); + const int ownerPid = daemonApp->treelandConnector()->treelandMainPid(); auth->tty = daemonApp->seatdControl()->createGroupVt(ownerPid, user, sessionId); if (auth->tty <= 0) { qCritical() << "Failed to allocate grouped VT for Treeland user session"; @@ -360,7 +425,8 @@ namespace DDM { QProcessEnvironment env; env.insert(session.additionalEnv()); - env.insert(QStringLiteral("XDG_SESSION_PATH"), daemonApp->displayManager()->sessionPath(sessionId)); + env.insert(QStringLiteral("XDG_SESSION_PATH"), + daemonApp->displayManager()->sessionPath(sessionId)); env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); env.insert(QStringLiteral("DESKTOP_SESSION"), session.desktopSession()); @@ -418,6 +484,8 @@ namespace DDM { connect(auth, &Auth::sessionFinished, this, [this, auth]() { qWarning() << "Session for user" << auth->user << "finished"; + unwatchUserSession(auth); + m_socketServer->removeUserSession(auth->user, auth->xdgSessionId); auths.removeAll(auth); daemonApp->displayManager()->RemoveSession(auth->sessionId); if (auth->type == Treeland) @@ -426,13 +494,15 @@ namespace DDM { }); daemonApp->displayManager()->AddSession(sessionId, name, user, auth->tty); daemonApp->displayManager()->setLastSession(sessionId); + m_socketServer->addUserSession(user, xdgSessionId); + watchUserSession(auth); if (auth->type == Treeland) activateSession(user, xdgSessionId); qInfo() << "Successfully logged in user" << user; } - void Display::logout([[maybe_unused]] QLocalSocket *socket, int id) { + void Display::logout(int id) { qDebug() << "Logout requested for session id" << id; // Do not kill the session leader process before // TerminateSession! Logind will only kill the session's @@ -447,57 +517,4 @@ namespace DDM { manager.TerminateSession(QString::number(id)); } - void Display::lock([[maybe_unused]] QLocalSocket *socket, int id) { - qDebug() << "Lock requested for session id" << id; - - OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), - Logind::managerPath(), - QDBusConnection::systemBus()); - manager.LockSession(QString::number(id)); - } - - void Display::unlock(QLocalSocket *socket, const QString &user, const QString &password) { - if (user == QLatin1String("dde")) { - emit loginFailed(socket, user); - return; - } - - qInfo() << "Start identify user" << user; - - // Only run password check - // - // No user process execution, so the auth can be thrown away - // immediately after use - Auth auth(this, user); - if (!auth.authenticate(password.toLocal8Bit())) { - Q_EMIT loginFailed(socket, user); - return; - } - - // Save last user - DaemonApp::instance()->displayManager()->setLastActivatedUser(user); - if (mainConfig.Users.RememberLastUser.get()) - stateConfig.Last.User.set(user); - else - stateConfig.Last.User.setDefault(); - stateConfig.save(); - - // Find the auth that started the session, which contains full informations - for (auto *auth : std::as_const(auths)) { - if (auth->user == user && auth->xdgSessionId > 0) { - OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), - Logind::managerPath(), - QDBusConnection::systemBus()); - manager.UnlockSession(QString::number(auth->xdgSessionId)); - if (auth->type == Treeland) - activateSession(user, auth->xdgSessionId); - 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; - } - } - qWarning() << "No active session found for user" << user; - Q_EMIT loginFailed(socket, user); - } -} +} // namespace DDM diff --git a/src/daemon/Display.h b/src/daemon/Display.h index f341f10..0b7bb33 100644 --- a/src/daemon/Display.h +++ b/src/daemon/Display.h @@ -29,8 +29,6 @@ #include "Session.h" -class QLocalSocket; - namespace DDM { class Auth; class XorgDisplayServer; @@ -70,6 +68,8 @@ namespace DDM { * @param xdgSessionId Logind session ID */ void activateSession(const QString &user, int xdgSessionId); + void watchUserSession(Auth *auth); + void unwatchUserSession(Auth *auth); /** Seat name */ QString name{}; @@ -95,28 +95,18 @@ namespace DDM { */ void stop(); - /////////////////////////////////////////////////// - // Slots for socket to communicate with Treeland // - /////////////////////////////////////////////////// - - void connected(QLocalSocket *socket); - void login(QLocalSocket *socket, - const QString &user, + void connected(); + void login(const QString &user, const QString &password, const Session &session); - void logout(QLocalSocket *socket, int id); - void lock(QLocalSocket *socket, int id); - void unlock(QLocalSocket *socket, const QString &user, const QString &password); + void logout(int id); + void onTreelandLockStateChanged(bool locked); signals: /** Emitted when stop() */ void stopped(); - ///////////////////////////////////////////////////// - // Signals for socket to communicate with Treeland // - ///////////////////////////////////////////////////// - - void loginFailed(QLocalSocket *socket, const QString &user); + void loginFailed(const QString &user); private: /** Indicates whether the display is started */ @@ -130,6 +120,11 @@ namespace DDM { /** Socket server for communication with Treeland */ SocketServer *m_socketServer { nullptr }; + + int m_activeTreelandSessionId { 0 }; + bool m_treelandLocked { false }; + qint64 m_lockBackWindowStart { 0 }; + int m_lockBackCount { 0 }; }; } diff --git a/src/daemon/PowerManager.cpp b/src/daemon/PowerManager.cpp index 9cacc44..16b0e66 100644 --- a/src/daemon/PowerManager.cpp +++ b/src/daemon/PowerManager.cpp @@ -41,7 +41,28 @@ namespace DDM { virtual ~PowerManagerBackend() { } - virtual Capabilities capabilities() const = 0; + Capabilities capabilities() const { + Capabilities caps = Capability::None; + + if (canPowerOff()) + caps |= Capability::PowerOff; + if (canReboot()) + caps |= Capability::Reboot; + if (canSuspend()) + caps |= Capability::Suspend; + if (canHibernate()) + caps |= Capability::Hibernate; + if (canHybridSleep()) + caps |= Capability::HybridSleep; + + return caps; + } + + virtual bool canPowerOff() const = 0; + virtual bool canReboot() const = 0; + virtual bool canSuspend() const = 0; + virtual bool canHibernate() const = 0; + virtual bool canHybridSleep() const = 0; virtual void powerOff() const = 0; virtual void reboot() const = 0; @@ -68,23 +89,26 @@ const QString UPOWER_OBJECT = QStringLiteral("org.freedesktop.UPower"); delete m_interface; } - Capabilities capabilities() const { - Capabilities caps = Capability::PowerOff | Capability::Reboot; + bool canPowerOff() const { + return true; + } - QDBusReply reply; + bool canReboot() const { + return true; + } - // suspend - reply = m_interface->call(QStringLiteral("SuspendAllowed")); - if (reply.isValid() && reply.value()) - caps |= Capability::Suspend; + bool canSuspend() const { + const QDBusReply reply = m_interface->call(QStringLiteral("SuspendAllowed")); + return reply.isValid() && reply.value(); + } - // hibernate - reply = m_interface->call(QStringLiteral("HibernateAllowed")); - if (reply.isValid() && reply.value()) - caps |= Capability::Hibernate; + bool canHibernate() const { + const QDBusReply reply = m_interface->call(QStringLiteral("HibernateAllowed")); + return reply.isValid() && reply.value(); + } - // return capabilities - return caps; + bool canHybridSleep() const { + return false; } void powerOff() const { @@ -136,38 +160,24 @@ const QString CK2_OBJECT = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); delete m_interface; } - Capabilities capabilities() const { - Capabilities caps = Capability::None; - - QDBusReply reply; - - // power off - reply = m_interface->call(QStringLiteral("CanPowerOff")); - if (reply.isValid() && (reply.value() == QLatin1String("yes"))) - caps |= Capability::PowerOff; - - // reboot - reply = m_interface->call(QStringLiteral("CanReboot")); - if (reply.isValid() && (reply.value() == QLatin1String("yes"))) - caps |= Capability::Reboot; + bool canPowerOff() const { + return can(QStringLiteral("CanPowerOff")); + } - // suspend - reply = m_interface->call(QStringLiteral("CanSuspend")); - if (reply.isValid() && (reply.value() == QLatin1String("yes"))) - caps |= Capability::Suspend; + bool canReboot() const { + return can(QStringLiteral("CanReboot")); + } - // hibernate - reply = m_interface->call(QStringLiteral("CanHibernate")); - if (reply.isValid() && (reply.value() == QLatin1String("yes"))) - caps |= Capability::Hibernate; + bool canSuspend() const { + return can(QStringLiteral("CanSuspend")); + } - // hybrid sleep - reply = m_interface->call(QStringLiteral("CanHybridSleep")); - if (reply.isValid() && (reply.value() == QLatin1String("yes"))) - caps |= Capability::HybridSleep; + bool canHibernate() const { + return can(QStringLiteral("CanHibernate")); + } - // return capabilities - return caps; + bool canHybridSleep() const { + return can(QStringLiteral("CanHybridSleep")); } void powerOff() const { @@ -190,6 +200,11 @@ const QString CK2_OBJECT = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); m_interface->call(QStringLiteral("HybridSleep"), true); } + + bool can(const QString &method) const { + const QDBusReply reply = m_interface->call(method); + return reply.isValid() && reply.value() == QLatin1String("yes"); + } private: QDBusInterface *m_interface { nullptr }; }; @@ -227,9 +242,54 @@ const QString CK2_OBJECT = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); return caps; } + bool PowerManager::canPowerOff() const { + for (PowerManagerBackend *backend: m_backends) { + if (backend->canPowerOff()) + return true; + } + + return false; + } + + bool PowerManager::canReboot() const { + for (PowerManagerBackend *backend: m_backends) { + if (backend->canReboot()) + return true; + } + + return false; + } + + bool PowerManager::canSuspend() const { + for (PowerManagerBackend *backend: m_backends) { + if (backend->canSuspend()) + return true; + } + + return false; + } + + bool PowerManager::canHibernate() const { + for (PowerManagerBackend *backend: m_backends) { + if (backend->canHibernate()) + return true; + } + + return false; + } + + bool PowerManager::canHybridSleep() const { + for (PowerManagerBackend *backend: m_backends) { + if (backend->canHybridSleep()) + return true; + } + + return false; + } + void PowerManager::powerOff() const { for (PowerManagerBackend *backend: m_backends) { - if (backend->capabilities() & Capability::PowerOff) { + if (backend->canPowerOff()) { backend->powerOff(); break; } @@ -238,7 +298,7 @@ const QString CK2_OBJECT = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); void PowerManager::reboot() const { for (PowerManagerBackend *backend: m_backends) { - if (backend->capabilities() & Capability::Reboot) { + if (backend->canReboot()) { backend->reboot(); break; } @@ -247,7 +307,7 @@ const QString CK2_OBJECT = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); void PowerManager::suspend() const { for (PowerManagerBackend *backend: m_backends) { - if (backend->capabilities() & Capability::Suspend) { + if (backend->canSuspend()) { backend->suspend(); break; } @@ -256,7 +316,7 @@ const QString CK2_OBJECT = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); void PowerManager::hibernate() const { for (PowerManagerBackend *backend: m_backends) { - if (backend->capabilities() & Capability::Hibernate) { + if (backend->canHibernate()) { backend->hibernate(); break; } @@ -265,7 +325,7 @@ const QString CK2_OBJECT = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); void PowerManager::hybridSleep() const { for (PowerManagerBackend *backend: m_backends) { - if (backend->capabilities() & Capability::HybridSleep) { + if (backend->canHybridSleep()) { backend->hybridSleep(); break; } diff --git a/src/daemon/PowerManager.h b/src/daemon/PowerManager.h index 9478d51..630cf39 100644 --- a/src/daemon/PowerManager.h +++ b/src/daemon/PowerManager.h @@ -38,6 +38,11 @@ namespace DDM { public slots: Capabilities capabilities() const; + bool canPowerOff() const; + bool canReboot() const; + bool canSuspend() const; + bool canHibernate() const; + bool canHybridSleep() const; void powerOff() const; void reboot() const; diff --git a/src/daemon/SeatManager.cpp b/src/daemon/SeatManager.cpp index 7f90d15..577461b 100644 --- a/src/daemon/SeatManager.cpp +++ b/src/daemon/SeatManager.cpp @@ -59,6 +59,14 @@ namespace DDM { return false; } + static Display *findDisplayByGreeterVt(int vtnr) { + for (Display *display : std::as_const(daemonApp->seatManager()->displays)) { + if (display->terminalId == vtnr) + return display; + } + return nullptr; + } + static QString findTreelandUserByVt(int vtnr) { if (vtnr <= 0) return {}; @@ -214,6 +222,7 @@ namespace DDM { if (display->name == name) { // switch to greeter display->activateSession("dde", 0); + daemonApp->treelandConnector()->lock(); return; } } @@ -269,7 +278,9 @@ namespace DDM { } if (newIsGreeterVt || newUser == QString::fromLatin1(greeterUserName)) { - daemonApp->treelandConnector()->switchToGreeter(); + if (auto *display = findDisplayByGreeterVt(newVt)) + display->activateSession(QString::fromLatin1(greeterUserName), 0); + daemonApp->treelandConnector()->lock(); } else { daemonApp->treelandConnector()->switchToUser(newUser); } diff --git a/src/daemon/SocketServer.cpp b/src/daemon/SocketServer.cpp index a040b00..e87f4a8 100644 --- a/src/daemon/SocketServer.cpp +++ b/src/daemon/SocketServer.cpp @@ -1,247 +1,237 @@ /*************************************************************************** -* Copyright (c) 2015 Pier Luigi Fiorini -* Copyright (c) 2013 Abdurrahman AVCI -* -* 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. -***************************************************************************/ + * Copyright (c) 2015 Pier Luigi Fiorini + * Copyright (c) 2013 Abdurrahman AVCI + * + * 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 "SocketServer.h" +#include "Auth.h" +#include "Configuration.h" #include "DaemonApp.h" -#include "Messages.h" +#include "Display.h" #include "PowerManager.h" -#include "SocketWriter.h" #include "TreelandConnector.h" -#include "Utils.h" -#include +#include +#include +#include +#include +#include +#include + +static constexpr auto ddmRemoteUrl = "local:org.deepin.dde.ddm.qro"; +static constexpr auto ddmRemoteSourceName = "DDMRemote"; + +namespace { + bool executableAllowed(const QString &tryExec) { + if (tryExec.isEmpty()) + return true; + + QFileInfo executable(tryExec); + if (executable.isAbsolute()) + return executable.exists() && executable.isExecutable(); + + const auto paths = QProcessEnvironment::systemEnvironment() + .value(QStringLiteral("PATH")) + .split(QLatin1Char(':'), Qt::SkipEmptyParts); + for (const auto &path : paths) { + executable.setFile(QDir(path), tryExec); + if (executable.exists() && executable.isExecutable()) + return true; + } + + return false; + } + + void appendSessions(QList &entries, + DDM::Session::Type type, + const QStringList &directories) { + QStringList sessionFiles; + for (const auto &path : directories) { + QDir dir(path); + dir.setNameFilters({ QStringLiteral("*.desktop") }); + dir.setFilter(QDir::Files); + sessionFiles += dir.entryList(); + } + sessionFiles.removeDuplicates(); + + for (const auto &sessionFile : std::as_const(sessionFiles)) { + DDM::Session session(type, sessionFile); + if (!session.isValid() || session.isHidden() || session.isNoDisplay() + || !executableAllowed(session.tryExec())) { + continue; + } + + SessionEntry entry; + entry.setFileName(session.fileName()); + entry.setType(session.type()); + entry.setDisplayName(session.displayName()); + entry.setComment(session.comment()); + entry.setExec(session.exec()); + + if (entry.displayName() == QStringLiteral("Treeland")) + entries.prepend(entry); + else + entries.append(entry); + } + } +} // namespace namespace DDM { - SocketServer::SocketServer(QObject *parent) : QObject(parent) { + SocketServer::SocketServer(Display *display, QObject *parent) + : DDMRemoteSimpleSource(parent) + , m_display(display) + , m_powerManager(daemonApp->powerManager()) { } + + bool SocketServer::canPowerOff() { + return m_powerManager->canPowerOff(); } - QString SocketServer::socketAddress() const { - if (m_server) - return m_server->fullServerName(); - return QString(); + bool SocketServer::canReboot() { + return m_powerManager->canReboot(); } - bool SocketServer::start(const QString &displayName) { - // check if the server has been created already - if (m_server) - return false; + bool SocketServer::canSuspend() { + return m_powerManager->canSuspend(); + } - QString socketName = QStringLiteral("ddm-%1-%2").arg(displayName).arg(generateName(6)); + bool SocketServer::canHibernate() { + return m_powerManager->canHibernate(); + } - // log message - qDebug() << "Socket server starting..."; + QList SocketServer::sessions() { + QList entries; + if (QFileInfo::exists(QStringLiteral("/dev/dri"))) + appendSessions(entries, Session::WaylandSession, mainConfig.Wayland.SessionDir.get()); + appendSessions(entries, Session::X11Session, mainConfig.X11.SessionDir.get()); + return entries; + } - // create server - m_server = new QLocalServer(this); + QString SocketServer::lastSession() { + return stateConfig.Last.Session.get(); + } + + QString SocketServer::lastUser() { + return stateConfig.Last.User.get(); + } - // set server options - m_server->setSocketOptions(QLocalServer::UserAccessOption); + bool SocketServer::rememberLastSession() { + return mainConfig.Users.RememberLastSession.get(); + } - // start listening - if (!m_server->listen(socketName)) { - // log message - qCritical() << "Failed to start socket server."; + bool SocketServer::canHybridSleep() { + return m_powerManager->canHybridSleep(); + } - // return fail + bool SocketServer::start() { + if (m_host) return false; - } + qDebug() << "Socket server starting..."; - // log message - qDebug() << "Socket server started."; - - // connect signals - connect(m_server, &QLocalServer::newConnection, this, &SocketServer::newConnection); + m_host = new QRemoteObjectHost(QUrl(QString::fromLatin1(ddmRemoteUrl)), this); + if (!m_host->enableRemoting(this, QString::fromLatin1(ddmRemoteSourceName))) { + qCritical() << "Failed to enable DDMRemote source."; + delete m_host; + m_host = nullptr; + return false; + } - // return success + setHostName(daemonApp->hostName()); + qDebug() << "Socket server started on" << ddmRemoteUrl; return true; } void SocketServer::stop() { - // check flag - if (!m_server) + if (!m_host) return; - // log message qDebug() << "Socket server stopping..."; + m_host->deleteLater(); + m_host = nullptr; + qDebug() << "Socket server stopped."; + } - // delete server - m_server->deleteLater(); - m_server = nullptr; + bool SocketServer::connectGreeter() { + qDebug() << "Message received from greeter: Connect"; + if (!daemonApp->treelandConnector()->connect()) + return false; + m_display->connected(); + return true; + } - // log message - qDebug() << "Socket server stopped."; + void SocketServer::replayUserSessions() { + for (Auth *auth : std::as_const(m_display->auths)) { + if (auth->sessionOpened) + addUserSession(auth->user, auth->xdgSessionId); + } } - void SocketServer::newConnection() { - // get pending connection - QLocalSocket *socket = m_server->nextPendingConnection(); + void SocketServer::addUserSession(const QString &user, int sessionId) { + if (sessionId > 0) + emit userSessionAdded(user, sessionId); + } - // connect signals - connect(socket, &QLocalSocket::readyRead, this, &SocketServer::readyRead); - connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater); - connect(socket, &QLocalSocket::disconnected, this, [this, socket] { - emit disconnected(socket); - }); + void SocketServer::removeUserSession(const QString &user, int sessionId) { + if (sessionId > 0) + emit userSessionRemoved(user, sessionId); } - void SocketServer::readyRead() { - QLocalSocket *socket = qobject_cast(sender()); + bool SocketServer::login(QString user, QString password, int sessionType, QString sessionFile) { + qDebug() << "Message received from greeter: Login"; + Session session(static_cast(sessionType), sessionFile); + m_display->login(user, password, session); + return true; + } - // check socket - if (!socket) - return; + bool SocketServer::logout(int id) { + qDebug() << "Message received from greeter: Logout"; + m_display->logout(id); + return true; + } - // input stream - QDataStream input(socket); - - // Qt's QLocalSocket::readyRead is not designed to be called at every socket.write(), - // so we need to use a loop to read all the signals. - while(socket->bytesAvailable()) { - // read message - quint32 message; - input >> message; - - switch (GreeterMessages(message)) { - case GreeterMessages::Connect: { - // log message - qDebug() << "Message received from greeter: Connect"; - - // Connect wayland socket - QString socketPath; - input >> socketPath; - daemonApp->treelandConnector()->connect(socketPath); - - // send capabilities - SocketWriter(socket) << quint32(DaemonMessages::Capabilities) << quint32(daemonApp->powerManager()->capabilities()); - - // send host name - SocketWriter(socket) << quint32(DaemonMessages::HostName) << daemonApp->hostName(); - - // emit signal - emit connected(socket); - } - break; - case GreeterMessages::Login: { - // log message - qDebug() << "Message received from greeter: Login"; - - // read username, pasword etc. - QString user, password, filename; - Session session; - input >> user >> password >> session; - - // emit signal - emit login(socket, user, password, session); - } - break; - case GreeterMessages::Logout: { - // log message - qDebug() << "Message received from greeter: Logout"; - // read username - int id; - input >> id; - // emit signal - emit logout(socket, id); - } - break; - case GreeterMessages::Lock : { - // log message - qDebug() << "Message received from greeter: Lock"; - int id; - - input >> id; - emit lock(socket, id); - } - break; - case GreeterMessages::Unlock : { - // log message - qDebug() << "Message received from greeter: Unlock"; - QString user; - QString password; - - input >> user >> password; - emit unlock(socket, user, password); - } - break; - case GreeterMessages::PowerOff: { - // log message - qDebug() << "Message received from greeter: PowerOff"; - - // power off - daemonApp->powerManager()->powerOff(); - } - break; - case GreeterMessages::Reboot: { - // log message - qDebug() << "Message received from greeter: Reboot"; - - // reboot - daemonApp->powerManager()->reboot(); - } - break; - case GreeterMessages::Suspend: { - // log message - qDebug() << "Message received from greeter: Suspend"; - - // suspend - daemonApp->powerManager()->suspend(); - } - break; - case GreeterMessages::Hibernate: { - // log message - qDebug() << "Message received from greeter: Hibernate"; - - // hibernate - daemonApp->powerManager()->hibernate(); - } - break; - case GreeterMessages::HybridSleep: { - // log message - qDebug() << "Message received from greeter: HybridSleep"; - // hybrid sleep - daemonApp->powerManager()->hybridSleep(); - } - break; - case GreeterMessages::BackToNormal: { - // log message - qDebug() << "Message received from greeter: Back to normal"; - // hybrid sleep - daemonApp->backToNormal(); - } - break; - default: { - // log message - qWarning() << "Unknown message" << message; - } - } - } + bool SocketServer::powerOff() { + qDebug() << "Message received from greeter: PowerOff"; + m_powerManager->powerOff(); + return true; + } + bool SocketServer::reboot() { + qDebug() << "Message received from greeter: Reboot"; + m_powerManager->reboot(); + return true; + } + + bool SocketServer::suspend() { + qDebug() << "Message received from greeter: Suspend"; + m_powerManager->suspend(); + return true; } - void SocketServer::loginFailed(QLocalSocket *socket, const QString &user) { - SocketWriter(socket) << quint32(DaemonMessages::LoginFailed) << user; + bool SocketServer::hibernate() { + qDebug() << "Message received from greeter: Hibernate"; + m_powerManager->hibernate(); + return true; } - void SocketServer::informationMessage(QLocalSocket *socket, const QString &message) { - SocketWriter(socket) << quint32(DaemonMessages::InformationMessage) << message; + bool SocketServer::hybridSleep() { + qDebug() << "Message received from greeter: HybridSleep"; + m_powerManager->hybridSleep(); + return true; } -} +} // namespace DDM diff --git a/src/daemon/SocketServer.h b/src/daemon/SocketServer.h index e66382d..bf2d8d9 100644 --- a/src/daemon/SocketServer.h +++ b/src/daemon/SocketServer.h @@ -1,70 +1,77 @@ /*************************************************************************** -* Copyright (c) 2015 Pier Luigi Fiorini -* Copyright (c) 2013 Abdurrahman AVCI -* -* 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. -***************************************************************************/ + * Copyright (c) 2015 Pier Luigi Fiorini + * Copyright (c) 2013 Abdurrahman AVCI + * + * 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_SOCKETSERVER_H #define DDM_SOCKETSERVER_H +#include "Messages.h" +#include "Session.h" +#include "rep_ddmremote_source.h" + #include #include -#include "Session.h" - -class QLocalServer; -class QLocalSocket; +class QRemoteObjectHost; namespace DDM { - class SocketServer : public QObject { + class Display; + class PowerManager; + + class SocketServer : public DDMRemoteSimpleSource { Q_OBJECT Q_DISABLE_COPY(SocketServer) public: - explicit SocketServer(QObject *parent = 0); + explicit SocketServer(Display *display, QObject *parent = 0); - bool start(const QString &sddmName); + bool start(); void stop(); - QString socketAddress() const; + bool canPowerOff() override; + bool canReboot() override; + bool canSuspend() override; + bool canHibernate() override; + bool canHybridSleep() override; - private slots: - void newConnection(); - void readyRead(); + QList sessions() override; + QString lastSession() override; + QString lastUser() override; + bool rememberLastSession() override; + void replayUserSessions(); + void addUserSession(const QString &user, int sessionId); + void removeUserSession(const QString &user, int sessionId); public slots: - void informationMessage(QLocalSocket *socket, const QString &message); - void loginFailed(QLocalSocket *socket, const QString &user); - - signals: - void login(QLocalSocket *socket, - const QString &user, const QString &password, - const Session &session); - void logout(QLocalSocket *socket, - int id); - void lock(QLocalSocket *socket, - int id); - void unlock(QLocalSocket *socket, - const QString &user, const QString &password); - void connected(QLocalSocket *socket); - void disconnected(QLocalSocket *socket); + bool connectGreeter() override; + bool login(QString user, QString password, int sessionType, QString sessionFile) override; + bool logout(int id) override; + bool powerOff() override; + bool reboot() override; + bool suspend() override; + bool hibernate() override; + bool hybridSleep() override; private: - QLocalServer *m_server { nullptr }; + QRemoteObjectHost *m_host{ nullptr }; + Display *m_display{ nullptr }; + PowerManager *m_powerManager{ nullptr }; }; -} +} // namespace DDM #endif // DDM_SOCKETSERVER_H diff --git a/src/daemon/TreelandConnector.cpp b/src/daemon/TreelandConnector.cpp index 49b9ba1..39493f2 100644 --- a/src/daemon/TreelandConnector.cpp +++ b/src/daemon/TreelandConnector.cpp @@ -3,179 +3,168 @@ #include "TreelandConnector.h" -#include "treeland-ddm-v1.h" +#include "rep_treelandremote_replica.h" #include #include #include #include #include -#include +#include +#include +#include -#include -#include - -#include #include -#include -#include namespace DDM { -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"; - -TreelandConnector::TreelandConnector(QObject *parent) - : QObject(parent) { -} - -TreelandConnector::~TreelandConnector() { - disconnect(); -} - -bool TreelandConnector::isConnected() { - return m_ddm; -} - -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; + 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 auto treelandRemoteUrl = "local:org.deepin.dde.treeland.qro"; + + // TreelandConnector + + TreelandConnector::TreelandConnector(QObject *parent) + : QObject(parent) { } + + TreelandConnector::~TreelandConnector() { + disconnect(); } - 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; + bool TreelandConnector::isConnected() { + return m_remoteReplica && m_remoteReplica->state() == QRemoteObjectReplica::Valid; } - 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; + bool TreelandConnector::connect() { + return ensureRemote(); } - return static_cast(pid); -} - -void TreelandConnector::setPrivateObject(struct treeland_ddm_v1 *ddm) { - m_ddm = ddm; -} - -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) { -} - -const struct treeland_ddm_v1_listener treelandDDMListener { - .switch_to_vt = switchToVt, - .acquire_vt = acquireVt, -}; - -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( - wl_registry_bind(registry, name, &treeland_ddm_v1_interface, version) - ); - treeland_ddm_v1_add_listener(ddm, &treelandDDMListener, connector); - connector->setPrivateObject(ddm); - qDebug("Connected to treeland_ddm global object"); + + void TreelandConnector::disconnect() { + m_remoteReplica.reset(); + m_remoteNode.reset(); } -} -static void removeGlobal([[maybe_unused]] void *data, - [[maybe_unused]] struct wl_registry *registry, - [[maybe_unused]] uint32_t name) { -} + bool TreelandConnector::ensureRemote() { + if (isConnected()) + return true; -const struct wl_registry_listener registryListener { - .global = registerGlobal, - .global_remove = removeGlobal, -}; + if (!m_remoteNode) { + m_remoteNode.reset(new QRemoteObjectNode); + if (!m_remoteNode->connectToNode(QUrl(QString::fromLatin1(treelandRemoteUrl)))) { + qWarning() << "Failed to connect Treeland remote node:" << treelandRemoteUrl; + m_remoteNode.reset(); + return false; + } + } -void TreelandConnector::connect(const QString &socketPath) { - disconnect(); + if (!m_remoteReplica) { + m_remoteReplica.reset(m_remoteNode->acquire()); + QObject::connect( + m_remoteReplica.get(), + &QRemoteObjectReplica::stateChanged, + this, + [this](QRemoteObjectReplica::State state, QRemoteObjectReplica::State oldState) { + qInfo() << "Treeland remote replica state changed from" << oldState << "to" + << state; + }); + QObject::connect(m_remoteReplica.get(), + &DDMTreelandRemoteReplica::lockChanged, + this, + [this](bool locked) { + qDebug() << "Treeland lock state changed:" << locked; + Q_EMIT lockStateChanged(locked); + }); + if (!m_remoteReplica->waitForSource(3000)) { + qWarning() << "Timed out waiting for Treeland remote source"; + disconnect(); + return false; + } + auto *lockStateWatcher = + new QRemoteObjectPendingCallWatcher(m_remoteReplica->lockState(), this); + QObject::connect(lockStateWatcher, + &QRemoteObjectPendingCallWatcher::finished, + this, + [this](QRemoteObjectPendingCallWatcher *watcher) { + if (watcher->error() == QRemoteObjectPendingCall::NoError) { + const QVariant value = watcher->returnValue(); + if (value.canConvert()) { + const bool locked = value.toBool(); + qDebug() << "Treeland initial lock state:" << locked; + Q_EMIT lockStateChanged(locked); + } else { + qWarning() << "Treeland lockState returned invalid value:" + << value; + } + } else { + qWarning() << "Failed to query Treeland lock state:" + << watcher->error(); + } + watcher->deleteLater(); + }); + } - m_display = wl_display_connect(qPrintable(socketPath)); - if (m_display == nullptr) { - qWarning("Failed to connect to Treeland Wayland socket %s", qPrintable(socketPath)); - return; + return isConnected(); } - auto registry = wl_display_get_registry(m_display); - wl_registry_add_listener(registry, ®istryListener, this); + 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; + } - wl_display_roundtrip(m_display); + 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; + } - 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, 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) { - qWarning("Treeland connection lost!"); - disconnect(); - } + 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; } - }); -} - -void TreelandConnector::disconnect() { - if (m_notifier) { - m_notifier->setEnabled(false); - delete m_notifier; - m_notifier = nullptr; + return static_cast(pid); } - if (m_display) { - wl_display_disconnect(m_display); - m_display = nullptr; - } - m_ddm = nullptr; -} - -void TreelandConnector::switchToGreeter() { - if (isConnected()) { - qDebug("Calling treeland switch_to_greeter"); - treeland_ddm_v1_switch_to_greeter(m_ddm); - wl_display_flush(m_display); - } else { - qWarning("Treeland is not connected when trying to call switchToGreeter"); + + // Request wrapper + + void TreelandConnector::switchToUser(const QString &username) { + if (!ensureRemote()) { + qWarning("Treeland is not connected when trying to call switchToUser"); + return; + } + + qDebug("Calling treeland switchToUser: user=%s", qPrintable(username)); + m_remoteReplica->switchToUser(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)); - wl_display_flush(m_display); - } else { - qWarning("Treeland is not connected when trying to call switchToUser"); + + void TreelandConnector::lock() { + if (!ensureRemote()) { + qWarning("Treeland is not connected when trying to call lock"); + return; + } + + qDebug("Calling treeland lock"); + m_remoteReplica->lock(); } -} -} +} // namespace DDM diff --git a/src/daemon/TreelandConnector.h b/src/daemon/TreelandConnector.h index 7261e1c..daa4afc 100644 --- a/src/daemon/TreelandConnector.h +++ b/src/daemon/TreelandConnector.h @@ -1,30 +1,36 @@ // Copyright (C) 2025-2026 UnionTech Software Technology Co., Ltd. // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include -#include -#include -struct wl_display; -struct treeland_ddm_v1; +#include + +class QRemoteObjectNode; +class DDMTreelandRemoteReplica; namespace DDM { -class TreelandConnector : public QObject { - Q_OBJECT -public: - explicit TreelandConnector(QObject *parent = nullptr); - ~TreelandConnector(); - bool isConnected(); - int mainPid(); - void setPrivateObject(struct treeland_ddm_v1 *ddm); - void connect(const QString &socketPath); - void disconnect(); - void switchToGreeter(); - void switchToUser(const QString &username); - -private: - struct wl_display *m_display { nullptr }; - QSocketNotifier *m_notifier { nullptr }; - struct treeland_ddm_v1 *m_ddm { nullptr }; -}; -} + class TreelandConnector : public QObject { + Q_OBJECT + public: + explicit TreelandConnector(QObject *parent = nullptr); + ~TreelandConnector(); + bool isConnected(); + int treelandMainPid() const; + bool connect(); + void disconnect(); + + void switchToUser(const QString &username); + void lock(); + + Q_SIGNALS: + void lockStateChanged(bool locked); + + private: + bool ensureRemote(); + + std::unique_ptr m_remoteNode; + std::unique_ptr m_remoteReplica; + }; +} // namespace DDM diff --git a/src/daemon/TreelandDisplayServer.cpp b/src/daemon/TreelandDisplayServer.cpp index 6d1c5bd..7ab1bca 100644 --- a/src/daemon/TreelandDisplayServer.cpp +++ b/src/daemon/TreelandDisplayServer.cpp @@ -2,18 +2,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "TreelandDisplayServer.h" -#include "Messages.h" #include "SocketServer.h" -#include "SocketWriter.h" #include "Display.h" +#include "DaemonApp.h" +#include "TreelandConnector.h" #include #include #include #include -#include -#include -#include #include #include @@ -25,16 +22,6 @@ using namespace DDM; TreelandDisplayServer::TreelandDisplayServer(SocketServer *socketServer, Display *parent) : QObject(parent) , m_socketServer(socketServer) { - connect(m_socketServer, &SocketServer::connected, this, [this, parent](QLocalSocket *socket) { - m_greeterSockets << socket; - qWarning("Treeland greeter socket connected: socket=%p total=%lld", - socket, static_cast(m_greeterSockets.size())); - }); - connect(m_socketServer, &SocketServer::disconnected, this, [this](QLocalSocket *socket) { - m_greeterSockets.removeOne(socket); - qWarning("Treeland greeter socket disconnected: socket=%p total=%lld", - socket, static_cast(m_greeterSockets.size())); - }); } TreelandDisplayServer::~TreelandDisplayServer() { @@ -76,22 +63,14 @@ void TreelandDisplayServer::stop() { } void TreelandDisplayServer::activateUser(const QString &user, int xdgSessionId) { - qDebug("Send greeter activation: user=%s xdgSessionId=%d sockets=%lld", - qPrintable(user), xdgSessionId, static_cast(m_greeterSockets.size())); - for (auto greeter : m_greeterSockets) { - if (user == "dde") { - qDebug("Sending SwitchToGreeter to socket=%p", greeter); - SocketWriter(greeter) << quint32(DaemonMessages::SwitchToGreeter); - } - - qDebug("Sending UserActivateMessage to socket=%p user=%s xdgSessionId=%d", - greeter, qPrintable(user), xdgSessionId); - SocketWriter(greeter) << quint32(DaemonMessages::UserActivateMessage) << user << xdgSessionId; - } + qDebug("Send greeter activation: user=%s xdgSessionId=%d", + qPrintable(user), xdgSessionId); + if (user == "dde") + daemonApp->treelandConnector()->lock(); + else + daemonApp->treelandConnector()->switchToUser(user); } void TreelandDisplayServer::onLoginFailed(const QString &user) { - for (auto greeter : m_greeterSockets) { - SocketWriter(greeter) << quint32(DaemonMessages::LoginFailed) << user; - } + m_socketServer->loginFailed(user); } diff --git a/src/daemon/TreelandDisplayServer.h b/src/daemon/TreelandDisplayServer.h index e51eb96..fbadc9f 100644 --- a/src/daemon/TreelandDisplayServer.h +++ b/src/daemon/TreelandDisplayServer.h @@ -4,9 +4,6 @@ #include #include -class QLocalSocket; -class QLocalServer; - namespace DDM { class Display; class SocketServer; @@ -26,7 +23,6 @@ namespace DDM { private: SocketServer *m_socketServer; - QList m_greeterSockets; bool m_started{ false }; }; } diff --git a/src/daemon/ddmremote.rep b/src/daemon/ddmremote.rep new file mode 100644 index 0000000..bf4cfea --- /dev/null +++ b/src/daemon/ddmremote.rep @@ -0,0 +1,29 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-2.0-or-later + +POD SessionEntry(QString fileName, int type, QString displayName, QString comment, QString exec) + +class DDMRemote { + PROP(QString hostName SOURCEONLYSETTER) + SLOT(bool canPowerOff()) + SLOT(bool canReboot()) + SLOT(bool canSuspend()) + SLOT(bool canHibernate()) + SLOT(bool canHybridSleep()) + SIGNAL(loginFailed(QString user)) + SIGNAL(informationMessage(QString message)) + SIGNAL(userSessionAdded(QString user, int sessionId)) + SIGNAL(userSessionRemoved(QString user, int sessionId)) + SLOT(QList sessions()) + SLOT(QString lastSession()) + SLOT(QString lastUser()) + SLOT(bool rememberLastSession()) + SLOT(bool connectGreeter()) + SLOT(bool login(QString user, QString password, int sessionType, QString sessionFile)) + SLOT(bool logout(int id)) + SLOT(bool powerOff()) + SLOT(bool reboot()) + SLOT(bool suspend()) + SLOT(bool hibernate()) + SLOT(bool hybridSleep()) +}; diff --git a/src/daemon/treelandremote.rep b/src/daemon/treelandremote.rep new file mode 100644 index 0000000..7ae78b6 --- /dev/null +++ b/src/daemon/treelandremote.rep @@ -0,0 +1,9 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-2.0-or-later + +class DDMTreelandRemote { + SLOT(void switchToUser(QString username)); + SLOT(void lock()); + SLOT(bool lockState()); + SIGNAL(lockChanged(bool locked)); +};