diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 369aed990..5d86e3abb 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -1282,8 +1282,20 @@ bool Helper::surfaceBelongsToCurrentSession(SurfaceWrapper *wrapper) return true; } WClient *client = wrapper->surface()->waylandClient(); - WSocket *socket = client ? client->socket()->rootSocket() : nullptr; - return socket && socket->isEnabled(); + if (!client) + return false; + + auto session = m_sessionManager->sessionForClient(client); + // If session lookup fails, conservatively treat as not belonging to current + // session rather than falling back to rootSocket check (which would + // incorrectly classify sandbox apps as belonging to the current session). + if (!session) + return false; + // Global session surfaces (Dock, launcher) are always visible + if (session == m_sessionManager->globalSession()) + return true; + // User session surfaces are visible only when their session is active + return m_sessionManager->activeSession().lock() == session; } void Helper::deleteTaskSwitch() diff --git a/src/session/session.cpp b/src/session/session.cpp index 151748550..afcf3990d 100644 --- a/src/session/session.cpp +++ b/src/session/session.cpp @@ -199,6 +199,22 @@ void SessionManager::removeSession(std::shared_ptr session) Helper::instance()->activateSurface(nullptr); } + // Force-destroy all surfaces belonging to this session so that sandbox + // apps (which bypass the closeSurface request) are cleaned up on logout. + // Collect first to avoid mutating the surface list during iteration. + auto *container = Helper::instance()->rootSurfaceContainer(); + QList toDestroy; + for (auto *wrapper : std::as_const(container->surfaces())) { + WClient *client = wrapper->surface()->waylandClient(); + if (!client) + continue; + auto wrapperSession = sessionForClient(client); + if (wrapperSession == session) + toDestroy.append(wrapper); + } + for (auto *wrapper : std::as_const(toDestroy)) + container->destroyForSurface(wrapper); + for (auto s : std::as_const(m_sessions)) { if (s.get() == session.get()) { m_sessions.removeOne(s); @@ -436,6 +452,51 @@ std::shared_ptr SessionManager::sessionForSocket(WSocket *socket) const return nullptr; } +/** + * Find the session for the given WClient + * + * For sandbox clients (socket has parentSocket), uid matching is used first + * because their socket chain traverses the global socket rather than a user + * session socket. Falls back to walking the socket parent chain for normal + * and system UI clients. + * + * @param client WClient to find session for + * @returns Session for the given client, or nullptr if not found + */ +std::shared_ptr SessionManager::sessionForClient(WClient *client) const +{ + if (!client) + return nullptr; + + auto global = globalSession(); + WSocket *socket = client->socket(); + + // Sandbox clients connect via a child socket whose parent chain goes + // through the global socket, so socket-based lookup would always find + // the global session. Use uid matching instead to find the real user session. + // The uid comes from Wayland client credentials (wl_client_get_credentials), + // which reflects the OS-level uid of the process that opened the Wayland + // connection — for sandbox apps this is the user who launched them, even + // though their socket chain only reaches the global socket. + if (socket && socket->parentSocket()) { + auto creds = client->credentials(); + if (creds) { + for (const auto &session : std::as_const(m_sessions)) { + if (session && session != global && session->uid() == creds->uid) + return session; + } + } + } + + // Walk socket parent chain for normal and system UI clients + while (socket) { + if (auto session = sessionForSocket(socket)) + return session; + socket = socket->parentSocket(); + } + return nullptr; +} + bool SessionManager::isDDEUserClient(WClient *client) { return client->socket() == globalSession()->socket(); diff --git a/src/session/session.h b/src/session/session.h index 73daf8bc9..2d6babbe8 100644 --- a/src/session/session.h +++ b/src/session/session.h @@ -69,6 +69,7 @@ class SessionManager : public QObject { std::shared_ptr sessionForUser(const QString &username) const; std::shared_ptr sessionForXWayland(WXWayland *xwayland) const; std::shared_ptr sessionForSocket(WSocket *socket) const; + std::shared_ptr sessionForClient(WClient *client) const; bool isDDEUserClient(WClient *client); void syncActiveSessionCursorSettings();