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/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/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/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/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/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) 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;