You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
9.8 KiB
284 lines
9.8 KiB
/******************************************************************** |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org> |
|
|
|
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 <http://www.gnu.org/licenses/>. |
|
*********************************************************************/ |
|
#include "activities.h" |
|
// KWin |
|
#include "client.h" |
|
#include "workspace.h" |
|
// KDE |
|
#include <KConfigGroup> |
|
#include <kactivities/controller.h> |
|
// Qt |
|
#include <QtConcurrentRun> |
|
#include <QDBusInterface> |
|
#include <QDBusPendingCall> |
|
#include <QFutureWatcher> |
|
|
|
namespace KWin |
|
{ |
|
|
|
KWIN_SINGLETON_FACTORY(Activities) |
|
|
|
Activities::Activities(QObject *parent) |
|
: QObject(parent) |
|
, m_controller(new KActivities::Controller(this)) |
|
{ |
|
connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::slotRemoved); |
|
connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::removed); |
|
connect(m_controller, &KActivities::Controller::activityAdded, this, &Activities::slotAdded); |
|
connect(m_controller, &KActivities::Controller::activityAdded, this, &Activities::added); |
|
connect(m_controller, &KActivities::Controller::currentActivityChanged, this, &Activities::slotCurrentChanged); |
|
} |
|
|
|
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(KSharedConfig::openConfig(), 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 { |
|
qDebug() << "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) |
|
|
|
qDebug() << id; |
|
|
|
QSet<QByteArray> saveSessionIds; |
|
QSet<QByteArray> 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? |
|
} |
|
|
|
//qDebug() << 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; |
|
} |
|
} |
|
|
|
qDebug() << "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 { |
|
qDebug() << "couldn't get ksmserver interface"; |
|
} |
|
} |
|
|
|
//BEGIN threaded activity list fetching |
|
typedef QPair<QStringList*, QStringList> AssignedList; |
|
typedef QPair<QString, QStringList> 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->activities(KActivities::Info::Running) : |
|
controller->activities()); |
|
} |
|
|
|
static CurrentAndList |
|
fetchActivityListAndCurrent(KActivities::Controller *controller) |
|
{ |
|
QStringList l = controller->activities(); |
|
QString c = controller->currentActivity(); |
|
return CurrentAndList(c, l); |
|
} |
|
|
|
void Activities::update(bool running, bool updateCurrent, QObject *target, QString slot) |
|
{ |
|
if (updateCurrent) { |
|
QFutureWatcher<CurrentAndList>* watcher = new QFutureWatcher<CurrentAndList>; |
|
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<AssignedList>* watcher = new QFutureWatcher<AssignedList>; |
|
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<AssignedList>* watcher = dynamic_cast< QFutureWatcher<AssignedList>* >(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<CurrentAndList>* watcher = dynamic_cast< QFutureWatcher<CurrentAndList>* >(sender())) { |
|
m_all = watcher->result().second; |
|
slotCurrentChanged(watcher->result().first); |
|
watcherObject = watcher; |
|
} |
|
} |
|
|
|
if (watcherObject) { |
|
QString slot = watcherObject->property("activityControllerCallback").toString(); |
|
QObject *target = static_cast<QObject*>(watcherObject->property("activityControllerCallbackTarget").value<void*>()); |
|
watcherObject->deleteLater(); // has done it's job |
|
if (!slot.isEmpty()) |
|
QMetaObject::invokeMethod(target, slot.toAscii().data(), Qt::DirectConnection); |
|
} |
|
} |
|
//END threaded activity list fetching |
|
|
|
} // namespace
|
|
|