/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin 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, see . *********************************************************************/ #include "activities.h" // KWin #include "client.h" #include "workspace.h" // KDE #include #include #include #include // Qt #include #include #include #include namespace KWin { KWIN_SINGLETON_FACTORY(Activities) Activities::Activities(QObject *parent) : QObject(parent) , m_controller(new KActivities::Controller(this)) { connect(m_controller, SIGNAL(activityRemoved(QString)), SLOT(slotRemoved(QString))); connect(m_controller, SIGNAL(activityRemoved(QString)), SIGNAL(removed(QString))); connect(m_controller, SIGNAL(activityAdded(QString)), SLOT(slotAdded(QString))); connect(m_controller, SIGNAL(activityAdded(QString)), SIGNAL(added(QString))); connect(m_controller, SIGNAL(currentActivityChanged(QString)), SLOT(slotCurrentChanged(QString))); } Activities::~Activities() { s_self = NULL; } void Activities::setCurrent(const QString &activity) { m_controller->setCurrentActivity(activity); } void Activities::slotCurrentChanged(const QString &newActivity) { if (m_current == newActivity) { return; } m_previous = m_current; m_current = newActivity; emit currentChanged(newActivity); } void Activities::slotAdded(const QString &activity) { m_all << activity; } void Activities::slotRemoved(const QString &activity) { m_all.removeOne(activity); foreach (Client * client, Workspace::self()->clientList()) { client->setOnActivity(activity, false); } //toss out any session data for it KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + activity); cg.deleteGroup(); } void Activities::toggleClientOnActivity(Client* c, const QString &activity, bool dont_activate) { //int old_desktop = c->desktop(); bool was_on_activity = c->isOnActivity(activity); bool was_on_all = c->isOnAllActivities(); //note: all activities === no activities bool enable = was_on_all || !was_on_activity; c->setOnActivity(activity, enable); if (c->isOnActivity(activity) == was_on_activity && c->isOnAllActivities() == was_on_all) // No change return; Workspace *ws = Workspace::self(); if (c->isOnCurrentActivity()) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_activity && // for stickyness changes //FIXME not sure if the line above refers to the correct activity !dont_activate) ws->requestFocus(c); else ws->restackClientUnderActive(c); } else ws->raiseClient(c); //notifyWindowDesktopChanged( c, old_desktop ); ClientList transients_stacking_order = ws->ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) toggleClientOnActivity(*it, activity, dont_activate); ws->updateClientArea(); } bool Activities::start(const QString &id) { Workspace *ws = Workspace::self(); if (ws->sessionSaving()) { return false; //ksmserver doesn't queue requests (yet) } if (!m_all.contains(id)) { return false; //bogus id } ws->loadSubSessionInfo(id); QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface"); if (ksmserver.isValid()) { ksmserver.asyncCall("restoreSubSession", id); } else { kDebug(1212) << "couldn't get ksmserver interface"; return false; } return true; } bool Activities::stop(const QString &id) { if (Workspace::self()->sessionSaving()) { return false; //ksmserver doesn't queue requests (yet) //FIXME what about session *loading*? } //ugly hack to avoid dbus deadlocks update(true, false); QMetaObject::invokeMethod(this, "reallyStop", Qt::QueuedConnection, Q_ARG(QString, id)); //then lie and assume it worked. return true; } void Activities::reallyStop(const QString &id) { Workspace *ws = Workspace::self(); if (ws->sessionSaving()) return; //ksmserver doesn't queue requests (yet) kDebug(1212) << id; QSet saveSessionIds; QSet dontCloseSessionIds; const ClientList &clients = ws->clientList(); for (ClientList::const_iterator it = clients.constBegin(); it != clients.constEnd(); ++it) { const Client* c = (*it); const QByteArray sessionId = c->sessionId(); if (sessionId.isEmpty()) { continue; //TODO support old wm_command apps too? } //kDebug() << sessionId; //if it's on the activity that's closing, it needs saving //but if a process is on some other open activity, I don't wanna close it yet //this is, of course, complicated by a process having many windows. if (c->isOnAllActivities()) { dontCloseSessionIds << sessionId; continue; } const QStringList activities = c->activities(); foreach (const QString & activityId, activities) { if (activityId == id) { saveSessionIds << sessionId; } else if (m_running.contains(activityId)) { dontCloseSessionIds << sessionId; } } } ws->storeSubSession(id, saveSessionIds); QStringList saveAndClose; QStringList saveOnly; foreach (const QByteArray & sessionId, saveSessionIds) { if (dontCloseSessionIds.contains(sessionId)) { saveOnly << sessionId; } else { saveAndClose << sessionId; } } kDebug(1212) << "saveActivity" << id << saveAndClose << saveOnly; //pass off to ksmserver QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface"); if (ksmserver.isValid()) { ksmserver.asyncCall("saveSubSession", id, saveAndClose, saveOnly); } else { kDebug(1212) << "couldn't get ksmserver interface"; } } //BEGIN threaded activity list fetching typedef QPair AssignedList; typedef QPair CurrentAndList; static AssignedList fetchActivityList(KActivities::Controller *controller, QStringList *target, bool running) // could be member function, but actually it's much simpler this way { return AssignedList(target, running ? controller->listActivities(KActivities::Info::Running) : controller->listActivities()); } static CurrentAndList fetchActivityListAndCurrent(KActivities::Controller *controller) { QStringList l = controller->listActivities(); QString c = controller->currentActivity(); return CurrentAndList(c, l); } void Activities::update(bool running, bool updateCurrent, QObject *target, QString slot) { if (updateCurrent) { QFutureWatcher* watcher = new QFutureWatcher; connect( watcher, SIGNAL(finished()), SLOT(handleReply()) ); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } watcher->setFuture(QtConcurrent::run(fetchActivityListAndCurrent, m_controller)); } else { QFutureWatcher* watcher = new QFutureWatcher; connect(watcher, SIGNAL(finished()), SLOT(handleReply())); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } QStringList *target = running ? &m_running : &m_all; watcher->setFuture(QtConcurrent::run(fetchActivityList, m_controller, target, running)); } } void Activities::handleReply() { QObject *watcherObject = 0; if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { // we carry over the to-be-updated StringList member as pointer in the threaded return *(watcher->result().first) = watcher->result().second; watcherObject = watcher; } if (!watcherObject) { if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { m_all = watcher->result().second; slotCurrentChanged(watcher->result().first); watcherObject = watcher; } } if (watcherObject) { QString slot = watcherObject->property("activityControllerCallback").toString(); QObject *target = static_cast(watcherObject->property("activityControllerCallbackTarget").value()); watcherObject->deleteLater(); // has done it's job if (!slot.isEmpty()) QMetaObject::invokeMethod(target, slot.toAscii().data(), Qt::DirectConnection); } } //END threaded activity list fetching } // namespace