File indexing completed on 2024-04-28 16:48:39

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "activities.h"
0010 // KWin
0011 #include "window.h"
0012 #include "workspace.h"
0013 // KDE
0014 #include <KConfigGroup>
0015 // Qt
0016 #include <QDBusInterface>
0017 #include <QDBusPendingCall>
0018 #include <QFutureWatcher>
0019 #include <QtConcurrentRun>
0020 
0021 namespace KWin
0022 {
0023 
0024 Activities::Activities()
0025     : m_controller(new KActivities::Controller(this))
0026 {
0027     connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::slotRemoved);
0028     connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::removed);
0029     connect(m_controller, &KActivities::Controller::activityAdded, this, &Activities::added);
0030     connect(m_controller, &KActivities::Controller::currentActivityChanged, this, &Activities::slotCurrentChanged);
0031     connect(m_controller, &KActivities::Controller::serviceStatusChanged, this, &Activities::slotServiceStatusChanged);
0032 }
0033 
0034 KActivities::Consumer::ServiceStatus Activities::serviceStatus() const
0035 {
0036     return m_controller->serviceStatus();
0037 }
0038 
0039 void Activities::slotServiceStatusChanged()
0040 {
0041     if (m_controller->serviceStatus() != KActivities::Consumer::Running) {
0042         return;
0043     }
0044     const auto windows = Workspace::self()->allClientList();
0045     for (auto *const window : windows) {
0046         if (window->isDesktop()) {
0047             continue;
0048         }
0049         window->checkActivities();
0050     }
0051 }
0052 
0053 void Activities::setCurrent(const QString &activity)
0054 {
0055     m_controller->setCurrentActivity(activity);
0056 }
0057 
0058 void Activities::slotCurrentChanged(const QString &newActivity)
0059 {
0060     if (m_current == newActivity) {
0061         return;
0062     }
0063     m_previous = m_current;
0064     m_current = newActivity;
0065     Q_EMIT currentChanged(newActivity);
0066 }
0067 
0068 void Activities::slotRemoved(const QString &activity)
0069 {
0070     const auto windows = Workspace::self()->allClientList();
0071     for (auto *const window : windows) {
0072         if (window->isDesktop()) {
0073             continue;
0074         }
0075         window->setOnActivity(activity, false);
0076     }
0077     // toss out any session data for it
0078     KConfigGroup cg(KSharedConfig::openConfig(), QByteArray("SubSession: ").append(activity.toUtf8()).constData());
0079     cg.deleteGroup();
0080 }
0081 
0082 void Activities::toggleWindowOnActivity(Window *window, const QString &activity, bool dont_activate)
0083 {
0084     // int old_desktop = window->desktop();
0085     bool was_on_activity = window->isOnActivity(activity);
0086     bool was_on_all = window->isOnAllActivities();
0087     // note: all activities === no activities
0088     bool enable = was_on_all || !was_on_activity;
0089     window->setOnActivity(activity, enable);
0090     if (window->isOnActivity(activity) == was_on_activity && window->isOnAllActivities() == was_on_all) { // No change
0091         return;
0092     }
0093 
0094     Workspace *ws = Workspace::self();
0095     if (window->isOnCurrentActivity()) {
0096         if (window->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_activity && // for stickyness changes
0097                                                                                                  // FIXME not sure if the line above refers to the correct activity
0098             !dont_activate) {
0099             ws->requestFocus(window);
0100         } else {
0101             ws->restackWindowUnderActive(window);
0102         }
0103     } else {
0104         ws->raiseWindow(window);
0105     }
0106 
0107     // notifyWindowDesktopChanged( c, old_desktop );
0108 
0109     const auto transients_stacking_order = ws->ensureStackingOrder(window->transients());
0110     for (auto *const window : transients_stacking_order) {
0111         if (!window) {
0112             continue;
0113         }
0114         toggleWindowOnActivity(window, activity, dont_activate);
0115     }
0116     ws->updateClientArea();
0117 }
0118 
0119 bool Activities::start(const QString &id)
0120 {
0121     Workspace *ws = Workspace::self();
0122     if (ws->sessionManager()->state() == SessionState::Saving) {
0123         return false; // ksmserver doesn't queue requests (yet)
0124     }
0125 
0126     if (!all().contains(id)) {
0127         return false; // bogus id
0128     }
0129 
0130     ws->sessionManager()->loadSubSessionInfo(id);
0131 
0132     QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface");
0133     if (ksmserver.isValid()) {
0134         ksmserver.asyncCall("restoreSubSession", id);
0135     } else {
0136         qCDebug(KWIN_CORE) << "couldn't get ksmserver interface";
0137         return false;
0138     }
0139     return true;
0140 }
0141 
0142 bool Activities::stop(const QString &id)
0143 {
0144     if (Workspace::self()->sessionManager()->state() == SessionState::Saving) {
0145         return false; // ksmserver doesn't queue requests (yet)
0146         // FIXME what about session *loading*?
0147     }
0148 
0149     // ugly hack to avoid dbus deadlocks
0150     QMetaObject::invokeMethod(
0151         this, [this, id] {
0152             reallyStop(id);
0153         },
0154         Qt::QueuedConnection);
0155     // then lie and assume it worked.
0156     return true;
0157 }
0158 
0159 void Activities::reallyStop(const QString &id)
0160 {
0161     Workspace *ws = Workspace::self();
0162     if (ws->sessionManager()->state() == SessionState::Saving) {
0163         return; // ksmserver doesn't queue requests (yet)
0164     }
0165 
0166     qCDebug(KWIN_CORE) << id;
0167 
0168     QSet<QByteArray> saveSessionIds;
0169     QSet<QByteArray> dontCloseSessionIds;
0170     const auto windows = ws->allClientList();
0171     for (auto *const window : windows) {
0172         if (window->isDesktop()) {
0173             continue;
0174         }
0175         const QByteArray sessionId = window->sessionId();
0176         if (sessionId.isEmpty()) {
0177             continue; // TODO support old wm_command apps too?
0178         }
0179 
0180         // qDebug() << sessionId;
0181 
0182         // if it's on the activity that's closing, it needs saving
0183         // but if a process is on some other open activity, I don't wanna close it yet
0184         // this is, of course, complicated by a process having many windows.
0185         if (window->isOnAllActivities()) {
0186             dontCloseSessionIds << sessionId;
0187             continue;
0188         }
0189 
0190         const QStringList activities = window->activities();
0191         for (const QString &activityId : activities) {
0192             if (activityId == id) {
0193                 saveSessionIds << sessionId;
0194             } else if (running().contains(activityId)) {
0195                 dontCloseSessionIds << sessionId;
0196             }
0197         }
0198     }
0199 
0200     ws->sessionManager()->storeSubSession(id, saveSessionIds);
0201 
0202     QStringList saveAndClose;
0203     QStringList saveOnly;
0204     for (const QByteArray &sessionId : std::as_const(saveSessionIds)) {
0205         if (dontCloseSessionIds.contains(sessionId)) {
0206             saveOnly << sessionId;
0207         } else {
0208             saveAndClose << sessionId;
0209         }
0210     }
0211 
0212     qCDebug(KWIN_CORE) << "saveActivity" << id << saveAndClose << saveOnly;
0213 
0214     // pass off to ksmserver
0215     QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface");
0216     if (ksmserver.isValid()) {
0217         ksmserver.asyncCall("saveSubSession", id, saveAndClose, saveOnly);
0218     } else {
0219         qCDebug(KWIN_CORE) << "couldn't get ksmserver interface";
0220     }
0221 }
0222 
0223 } // namespace