From effea46f4f660440f1b01048dec743d220f61ff8 Mon Sep 17 00:00:00 2001 From: April & May Date: Tue, 5 Aug 2025 06:12:55 +0800 Subject: [PATCH 1/2] feat: Support TTY switching with DDM 1. Remove dependence of seatd-dde.service, use system provided seatd service; 2. Add private wayland protocol treeland-private-protocol.xml for IPC 3. Acquire VT control immediately after first rendering complete; 4. Call DDM to switch VT when Ctrl+Alt+Fx is pressed, if available; 5. Expose wayland request for deactivate and re-activate session to DDM; --- misc/systemd/treeland.service.in | 4 +- src/greeter/greeterproxy.cpp | 28 ++++- src/modules/CMakeLists.txt | 1 + src/modules/ddm/CMakeLists.txt | 14 +++ src/modules/ddm/ddminterfacev1.cpp | 117 ++++++++++++++++++ src/modules/ddm/ddminterfacev1.h | 23 ++++ .../impl/output_manager_impl.cpp | 11 +- src/seat/helper.cpp | 61 ++++++--- src/seat/helper.h | 10 +- waylib/src/server/kernel/wbackend.cpp | 27 ++++ waylib/src/server/kernel/wbackend.h | 6 + 11 files changed, 277 insertions(+), 25 deletions(-) create mode 100644 src/modules/ddm/CMakeLists.txt create mode 100644 src/modules/ddm/ddminterfacev1.cpp create mode 100644 src/modules/ddm/ddminterfacev1.h diff --git a/misc/systemd/treeland.service.in b/misc/systemd/treeland.service.in index dedf285c3a..0922083130 100644 --- a/misc/systemd/treeland.service.in +++ b/misc/systemd/treeland.service.in @@ -4,8 +4,8 @@ PartOf=graphical.target StartLimitIntervalSec=30 StartLimitBurst=2 -Requires=seatd-dde.service -After=seatd-dde.service +Requires=seatd.service +After=seatd.service [Service] User=dde diff --git a/src/greeter/greeterproxy.cpp b/src/greeter/greeterproxy.cpp index 8360519225..fc127ca4a2 100644 --- a/src/greeter/greeterproxy.cpp +++ b/src/greeter/greeterproxy.cpp @@ -39,7 +39,11 @@ #include #include #include +#include +#include +#include +#include struct SessionInfo { @@ -216,10 +220,23 @@ void GreeterProxy::init() this, &GreeterProxy::onSessionRemoved); - auto sessions = d->displayManager->sessions(); - for (auto session : sessions) { - onSessionAdded(session); - } + // Use async call to avoid blocking + QDBusInterface dbus("org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus.Properties", + QDBusConnection::systemBus()); + QDBusPendingCall call = dbus.asyncCall("Get", DisplayManager::staticInterfaceName(), "Sessions"); + auto *watcher = new QDBusPendingCallWatcher(call); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { + QDBusReply> reply = watcher->reply(); + if (reply.isValid()) { + auto sessions = reply.value(); + for (auto session : sessions) { + onSessionAdded(session); + } + } + watcher->deleteLater(); + }); } void GreeterProxy::login(const QString &user, const QString &password, const int sessionIndex) @@ -344,7 +361,8 @@ void GreeterProxy::connected() { qCDebug(treelandGreeter) << "Connected to the daemon."; - SocketWriter(d->socket) << quint32(GreeterMessages::Connect); + SocketWriter(d->socket) << quint32(GreeterMessages::Connect) + << Helper::instance()->defaultWaylandSocket()->fullServerName(); } void GreeterProxy::disconnected() diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 4269ac8afe..ecf5493ba5 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -31,3 +31,4 @@ add_subdirectory(virtual-output) add_subdirectory(dde-shell) add_subdirectory(capture) add_subdirectory(item-selector) +add_subdirectory(ddm) diff --git a/src/modules/ddm/CMakeLists.txt b/src/modules/ddm/CMakeLists.txt new file mode 100644 index 0000000000..d1f10ae16a --- /dev/null +++ b/src/modules/ddm/CMakeLists.txt @@ -0,0 +1,14 @@ +find_package(TreelandProtocols REQUIRED) + +ws_generate_local(server ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-ddm-v1.xml treeland-ddm-v1-protocol) + +impl_treeland( + NAME + module_ddm + SOURCE + ${CMAKE_SOURCE_DIR}/src/modules/ddm/ddminterfacev1.h + ${CMAKE_SOURCE_DIR}/src/modules/ddm/ddminterfacev1.cpp + ${WAYLAND_PROTOCOLS_OUTPUTDIR}/treeland-ddm-v1-protocol.c + INCLUDE + $ +) diff --git a/src/modules/ddm/ddminterfacev1.cpp b/src/modules/ddm/ddminterfacev1.cpp new file mode 100644 index 0000000000..842c2c6f3d --- /dev/null +++ b/src/modules/ddm/ddminterfacev1.cpp @@ -0,0 +1,117 @@ +// Copyright (C) 2025 April Lu . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "ddminterfacev1.h" +#include "treeland-ddm-v1-protocol.h" +#include "common/treelandlogging.h" +#include "helper.h" +#include "usermodel.h" + +#include +#include +#include +#include + +struct treeland_ddm { + wl_resource *resource; +}; + +// request implementation + +static void switchToGreeter([[maybe_unused]] struct wl_client *client, [[maybe_unused]] struct wl_resource *resource) { + Helper::instance()->showLockScreen(false); +} + +static void switchToUser([[maybe_unused]] struct wl_client *client, [[maybe_unused]] struct wl_resource *resource, const char *username) { + if (strcmp(username, "ddm") != 0) { + Helper::instance()->userModel()->setCurrentUserName(QString(username)); + } +} + +static void activateSession([[maybe_unused]] struct wl_client *client, [[maybe_unused]] struct wl_resource *resource) { + Helper::instance()->activateSession(); +} + +static void deactivateSession([[maybe_unused]] struct wl_client *client, [[maybe_unused]] struct wl_resource *resource) { + Helper::instance()->deactivateSession(); +} + +static const struct treeland_ddm_interface treeland_ddm_impl { + .switch_to_greeter = switchToGreeter, + .switch_to_user = switchToUser, + .activate_session = activateSession, + .deactivate_session = deactivateSession, +}; + +// wayland object binding + +static void handleResourceDestroy(struct wl_resource *resource) { + qCWarning(treelandCore) << "DDM connection lost"; + auto ddm = static_cast(wl_resource_get_user_data(resource)); + ddm->resource = nullptr; +} + +void handleBindingGlobal(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + auto ddm = static_cast(data); + auto *resource = wl_resource_create(client, &treeland_ddm_interface, version, id); + wl_resource_set_implementation(resource, &treeland_ddm_impl, ddm, handleResourceDestroy); + ddm->resource = resource; + qCDebug(treelandCore) << "DDM connection established"; + + treeland_ddm_send_acquire_vt(resource, 0); +} + +// DDMInterfaceV1 + +DDMInterfaceV1::DDMInterfaceV1() { + +} + +DDMInterfaceV1::~DDMInterfaceV1() { +} + +QByteArrayView DDMInterfaceV1::interfaceName() const { + QByteArray arr(treeland_ddm_interface.name); + return QByteArrayView(arr); +} + +bool DDMInterfaceV1::isConnected() const { + auto ddm = static_cast(m_handle); + return ddm && ddm->resource; +} + +void DDMInterfaceV1::create(WServer *server) { + auto ddm = new treeland_ddm { .resource = nullptr }; + m_handle = ddm; + m_global = wl_global_create(server->handle()->handle(), &treeland_ddm_interface, + treeland_ddm_interface.version, ddm, handleBindingGlobal); +} + +void DDMInterfaceV1::destroy([[maybe_unused]] WServer *server) { + wl_global_destroy(m_global); + auto ddm = static_cast(m_handle); + delete ddm; + m_handle = nullptr; +} + +wl_global *DDMInterfaceV1::global() const { + return m_global; +} + +// Event wrapper + +void DDMInterfaceV1::switchToVt(int vtnr) { + auto ddm = static_cast(m_handle); + if (isConnected()) + treeland_ddm_send_switch_to_vt(ddm->resource, vtnr); + else + qCWarning(treelandCore) << "DDM is not conected when trying to call switchToVt"; +} + +void DDMInterfaceV1::acquireVt(int vtnr) { + auto ddm = static_cast(m_handle); + if (isConnected()) + treeland_ddm_send_acquire_vt(ddm->resource, vtnr); + else + qCWarning(treelandCore) << "DDM is not connected when trying to call acquireVt"; +} diff --git a/src/modules/ddm/ddminterfacev1.h b/src/modules/ddm/ddminterfacev1.h new file mode 100644 index 0000000000..2eae63b57d --- /dev/null +++ b/src/modules/ddm/ddminterfacev1.h @@ -0,0 +1,23 @@ +// Copyright (C) 2025 April Lu . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wserver.h" + +/** + * The server-side wrapper for treeland private protocol. + */ +class DDMInterfaceV1 : public Waylib::Server::WServerInterface { +public: + DDMInterfaceV1(); + ~DDMInterfaceV1() override; + QByteArrayView interfaceName() const override; + bool isConnected() const; + void switchToVt(int vtnr); + void acquireVt(int vtnr); +protected: + void create(Waylib::Server::WServer *server) override; + void destroy(Waylib::Server::WServer *server) override; + wl_global *global() const override; +private: + struct wl_global *m_global { nullptr }; +}; diff --git a/src/modules/primary-output/impl/output_manager_impl.cpp b/src/modules/primary-output/impl/output_manager_impl.cpp index d5ad907a9e..331dcab7ae 100644 --- a/src/modules/primary-output/impl/output_manager_impl.cpp +++ b/src/modules/primary-output/impl/output_manager_impl.cpp @@ -4,6 +4,7 @@ #include "output_manager_impl.h" #include +#include #include @@ -65,6 +66,11 @@ treeland_output_manager_v1 *treeland_output_manager_v1::create(qw_display *displ void treeland_output_manager_v1::set_primary_output(const char *name) { this->primary_output_name = name; + + // Sometimes the whole resources list will be removed and leaved in an invalid state + // (e.g. after a VT switching), so we need to ensure resources is valid before operations. + wl_list_init(&this->resources); + wl_resource *resource; wl_list_for_each(resource, &this->resources, link) { @@ -85,7 +91,10 @@ static treeland_output_manager_v1 *output_manager_from_resource(wl_resource *res static void output_manager_resource_destroy(struct wl_resource *resource) { - wl_list_remove(wl_resource_get_link(resource)); + // Safely remove resource from list + auto link = wl_resource_get_link(resource); + wl_list_init(link); + wl_list_remove(link); } static void output_manager_bind(wl_client *client, void *data, uint32_t version, uint32_t id) diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index e50a8cca10..c232fc6be8 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -34,6 +34,7 @@ #include "core/windowpicker.h" #include "workspace/workspace.h" #include "common/treelandlogging.h" +#include "modules/ddm/ddminterfacev1.h" #include #include @@ -848,6 +849,8 @@ void Helper::init() m_seat->detachInputDevice(device); }); + m_ddmInterfaceV1 = m_server->attach(); + m_outputManager = m_server->attach(); connect(m_backend, &WBackend::outputAdded, this, &Helper::onOutputAdded); connect(m_backend, &WBackend::outputRemoved, this, &Helper::onOutputRemoved); @@ -1216,10 +1219,19 @@ bool Helper::beforeDisposeEvent(WSeat *seat, QWindow *, QInputEvent *event) // Switch TTY with Ctrl + Alt + F1-F12 if (kevent->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) { auto key = kevent->key(); - if (key >= Qt::Key_F1 && key <= Qt::Key_F12) { - // Use syncronized call here to ensure DM to be shown on correct VT. - showLockScreen(false); - m_backend->session()->change_vt(key - Qt::Key_F1 + 1); + // We don't call libseat_disable_seat after switching TTY by + // calling DDM, which will cause the keyboard stuck in current + // state (Ctrl + Alt + Fx), and send switchToVt repeatly. + // Check if the backend is active to avoid this. + if (key >= Qt::Key_F1 && key <= Qt::Key_F12 && m_backend->isSessionActive()) { + const int vtnr = key - Qt::Key_F1 + 1; + if (m_ddmInterfaceV1 && m_ddmInterfaceV1->isConnected()) { + m_ddmInterfaceV1->switchToVt(vtnr); + } else { + qCDebug(treelandCore) << "DDM is not connected"; + showLockScreen(false); + m_backend->session()->change_vt(vtnr); + } return true; } } @@ -1970,7 +1982,7 @@ void Helper::setLockScreenImpl(ILockScreen *impl) } if (CmdLine::ref().useLockScreen()) { - showLockScreen(); + showLockScreen(false); } #endif } @@ -1987,7 +1999,7 @@ void Helper::setCurrentMode(CurrentMode mode) Q_EMIT currentModeChanged(); } -void Helper::showLockScreen(bool async) +void Helper::showLockScreen(bool switchToGreeter) { if (m_lockScreen->isLocked()) { return; @@ -2006,14 +2018,13 @@ void Helper::showLockScreen(bool async) // send DDM switch to greeter mode // FIXME: DDM and Treeland should listen to the lock signal of login1 - QDBusInterface interface("org.freedesktop.DisplayManager", - "/org/freedesktop/DisplayManager/Seat0", - "org.freedesktop.DisplayManager.Seat", - QDBusConnection::systemBus()); - if (async) + if (switchToGreeter) { + QDBusInterface interface("org.freedesktop.DisplayManager", + "/org/freedesktop/DisplayManager/Seat0", + "org.freedesktop.DisplayManager.Seat", + QDBusConnection::systemBus()); interface.asyncCall("SwitchToGreeter"); - else - interface.call("SwitchToGreeter"); + } } WSeat *Helper::seat() const @@ -2091,19 +2102,19 @@ void Helper::handleNewForeignToplevelCaptureRequest(wlr_ext_foreign_toplevel_ima qCWarning(treelandCapture) << "Could not find toplevel surface for handle"; return; } - + SurfaceWrapper *surfaceWrapper = m_rootSurfaceContainer->getSurface(toplevelSurface); if (!surfaceWrapper) { qCWarning(treelandCapture) << "Could not find SurfaceWrapper for toplevel surface"; return; } - + WSurfaceItem *surfaceItem = surfaceWrapper->surfaceItem(); if (!surfaceItem) { qCWarning(treelandCapture) << "Could not get WSurfaceItem from SurfaceWrapper"; return; } - + WSurfaceItemContent *surfaceContent = surfaceItem->findItemContent(); if (!surfaceContent) { qCWarning(treelandCapture) << "Could not find WSurfaceItemContent"; @@ -2131,3 +2142,21 @@ void Helper::handleNewForeignToplevelCaptureRequest(wlr_ext_foreign_toplevel_ima delete imageCaptureSource; } } + +UserModel *Helper::userModel() const { + return m_userModel; +} + +DDMInterfaceV1 *Helper::ddmInterfaceV1() const { + return m_ddmInterfaceV1; +} + +void Helper::activateSession() { + if (!m_backend->isSessionActive()) + m_backend->activateSession(); +} + +void Helper::deactivateSession() { + if (m_backend->isSessionActive()) + m_backend->deactivateSession(); +} diff --git a/src/seat/helper.h b/src/seat/helper.h index bb23d9a836..229cec42e0 100644 --- a/src/seat/helper.h +++ b/src/seat/helper.h @@ -90,6 +90,7 @@ class IMultitaskView; class LockScreenInterface; class ILockScreen; class UserModel; +class DDMInterfaceV1; struct wlr_idle_inhibitor_v1; struct wlr_output_power_v1_set_mode_event; struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request; @@ -200,9 +201,15 @@ class Helper : public WSeatEventFilter void setCurrentMode(CurrentMode mode); - void showLockScreen(bool async = true); + void showLockScreen(bool switchToGreeter = true); Output* getOutputAtCursor() const; + + UserModel *userModel() const; + DDMInterfaceV1 *ddmInterfaceV1() const; + + void activateSession(); + void deactivateSession(); public Q_SLOTS: void activateSurface(SurfaceWrapper *wrapper, Qt::FocusReason reason = Qt::OtherFocusReason); void forceActivateSurface(SurfaceWrapper *wrapper, @@ -330,6 +337,7 @@ private Q_SLOTS: DDEShellManagerInterfaceV1 *m_ddeShellV1 = nullptr; VirtualOutputV1 *m_virtualOutput = nullptr; PrimaryOutputV1 *m_primaryOutputV1 = nullptr; + DDMInterfaceV1 *m_ddmInterfaceV1 = nullptr; // private data QList m_outputList; diff --git a/waylib/src/server/kernel/wbackend.cpp b/waylib/src/server/kernel/wbackend.cpp index bc578e1c1c..8d59cc5128 100644 --- a/waylib/src/server/kernel/wbackend.cpp +++ b/waylib/src/server/kernel/wbackend.cpp @@ -198,6 +198,32 @@ bool WBackend::hasWayland() const return hasBackend(handle()); } +bool WBackend::isSessionActive() const +{ + W_D(const WBackend); + return d->session && d->session->handle()->active; +} + +void WBackend::activateSession() +{ + W_D(WBackend); + if (d->session) { + struct wlr_session *session = d->session->handle(); + session->active = true; + wl_signal_emit_mutable(&session->events.active, nullptr); + } +} + +void WBackend::deactivateSession() +{ + W_D(WBackend); + if (d->session) { + struct wlr_session *session = d->session->handle(); + session->active = false; + wl_signal_emit_mutable(&session->events.active, nullptr); + } +} + void WBackend::create(WServer *server) { W_D(WBackend); @@ -207,6 +233,7 @@ void WBackend::create(WServer *server) m_handle = qw_backend::autocreate(server->handle()->get_event_loop(), &session); Q_ASSERT(m_handle); d->session = qw_session::from(session); + Q_EMIT created(); } d->connect(); diff --git a/waylib/src/server/kernel/wbackend.h b/waylib/src/server/kernel/wbackend.h index 594a309504..391058e7d8 100644 --- a/waylib/src/server/kernel/wbackend.h +++ b/waylib/src/server/kernel/wbackend.h @@ -37,6 +37,10 @@ class WAYLIB_SERVER_EXPORT WBackend : public QObject, public WObject, public WS bool hasX11() const; bool hasWayland() const; + bool isSessionActive() const; + void activateSession(); + void deactivateSession(); + Q_SIGNALS: void outputAdded(WOutput *output); void outputRemoved(WOutput *output); @@ -44,6 +48,8 @@ class WAYLIB_SERVER_EXPORT WBackend : public QObject, public WObject, public WS void inputAdded(WInputDevice *input); void inputRemoved(WInputDevice *input); + void created(); + protected: void create(WServer *server) override; void destroy(WServer *server) override; From 6cac7e226835ed617a98f26e9f9ba56bbefaa6cf Mon Sep 17 00:00:00 2001 From: April & May Date: Tue, 19 Aug 2025 07:46:09 +0800 Subject: [PATCH 2/2] Totally disable lockscreen animation when switching TTY to prevent stuck for switching too quickly Treeland will failed to deactivate session when the animation is still playing, and get the whole VT stuck. Disable all animation to prevent it as mostly as possible. --- src/core/qml/PrimaryOutput.qml | 15 +++++++++++++++ src/plugins/lockscreen/qml/Greeter.qml | 8 ++++++-- src/wallpaper/wallpapercontroller.cpp | 13 ++++++++++++- src/wallpaper/wallpapercontroller.h | 1 + 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/core/qml/PrimaryOutput.qml b/src/core/qml/PrimaryOutput.qml index 2079d6b210..ac4028dfff 100644 --- a/src/core/qml/PrimaryOutput.qml +++ b/src/core/qml/PrimaryOutput.qml @@ -108,6 +108,13 @@ OutputItem { target: wallpaper scale: 1.4 } + }, + State { + name: "ScaleWithoutAnimation" + PropertyChanges { + target: wallpaper + scale: 1.4 + } } ] @@ -129,6 +136,14 @@ OutputItem { duration: 1000 easing.type: Easing.OutExpo } + }, + Transition { + from: "*" + to: "ScaleWithoutAnimation" + PropertyAnimation { + property: "scale" + duration: 0 + } } ] } diff --git a/src/plugins/lockscreen/qml/Greeter.qml b/src/plugins/lockscreen/qml/Greeter.qml index e0e45cb08a..9b1bb917cc 100644 --- a/src/plugins/lockscreen/qml/Greeter.qml +++ b/src/plugins/lockscreen/qml/Greeter.qml @@ -31,7 +31,11 @@ FocusScope { } lockView.showAnimation = showAnimation lockView.forceActiveFocus() - wallpaperController.type = WallpaperController.Scale + if (showAnimation) { + wallpaperController.type = WallpaperController.Scale + } else { + wallpaperController.type = WallpaperController.ScaleWithoutAnimation + } switch (root.currentMode) { case Greeter.CurrentMode.Lock: lockView.start() @@ -69,7 +73,7 @@ FocusScope { opacity: wallpaperController.type === WallpaperController.Normal ? 0 : 0.6 Behavior on opacity { PropertyAnimation { - duration: 1000 + duration: wallpaperController.type === WallpaperController.ScaleWithoutAnimation ? 0 : 1000 easing.type: Easing.OutExpo } } diff --git a/src/wallpaper/wallpapercontroller.cpp b/src/wallpaper/wallpapercontroller.cpp index b3fc93ce14..d36cf5fc62 100644 --- a/src/wallpaper/wallpapercontroller.cpp +++ b/src/wallpaper/wallpapercontroller.cpp @@ -56,7 +56,18 @@ void WallpaperController::updateState() Q_ASSERT(proxy); - proxy->setState(m_type == Normal ? "Normal" : "Scale"); + QString state = "Normal"; + switch (m_type) { + case Normal: + break; + case Scale: + state = "Scale"; + break; + case ScaleWithoutAnimation: + state = "ScaleWithoutAnimation"; + break; + } + proxy->setState(state); Q_EMIT proxyChanged(); Q_EMIT typeChanged(); diff --git a/src/wallpaper/wallpapercontroller.h b/src/wallpaper/wallpapercontroller.h index 5dbe2b9fb3..816f92545c 100644 --- a/src/wallpaper/wallpapercontroller.h +++ b/src/wallpaper/wallpapercontroller.h @@ -30,6 +30,7 @@ class WallpaperController : public QObject { Normal, Scale, + ScaleWithoutAnimation, }; Q_ENUM(WallpaperType)