1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program. If not, see <http://www.gnu.org/licenses/>.
19*********************************************************************/
20#include "activities.h"
21// KWin
22#include "client.h"
23#include "workspace.h"
24// KDE
25#include <KDE/KConfigGroup>
26#include <KDE/KDebug>
27#include <KDE/KGlobal>
28#include <KActivities/Controller>
29// Qt
30#include <QtConcurrentRun>
31#include <QDBusInterface>
32#include <QDBusPendingCall>
33#include <QFutureWatcher>
34
35namespace KWin
36{
37
38KWIN_SINGLETON_FACTORY(Activities)
39
40Activities::Activities(QObject *parent)
41 : QObject(parent)
42 , m_controller(new KActivities::Controller(this))
43{
44 connect(m_controller, SIGNAL(activityRemoved(QString)), SLOT(slotRemoved(QString)));
45 connect(m_controller, SIGNAL(activityRemoved(QString)), SIGNAL(removed(QString)));
46 connect(m_controller, SIGNAL(activityAdded(QString)), SLOT(slotAdded(QString)));
47 connect(m_controller, SIGNAL(activityAdded(QString)), SIGNAL(added(QString)));
48 connect(m_controller, SIGNAL(currentActivityChanged(QString)), SLOT(slotCurrentChanged(QString)));
49}
50
51Activities::~Activities()
52{
53 s_self = NULL;
54}
55
56void Activities::setCurrent(const QString &activity)
57{
58 m_controller->setCurrentActivity(activity);
59}
60
61void Activities::slotCurrentChanged(const QString &newActivity)
62{
63 if (m_current == newActivity) {
64 return;
65 }
66 m_previous = m_current;
67 m_current = newActivity;
68 emit currentChanged(newActivity);
69}
70
71void Activities::slotAdded(const QString &activity)
72{
73 m_all << activity;
74}
75
76void Activities::slotRemoved(const QString &activity)
77{
78 m_all.removeOne(activity);
79 foreach (Client * client, Workspace::self()->clientList()) {
80 client->setOnActivity(activity, false);
81 }
82 //toss out any session data for it
83 KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + activity);
84 cg.deleteGroup();
85}
86
87void Activities::toggleClientOnActivity(Client* c, const QString &activity, bool dont_activate)
88{
89 //int old_desktop = c->desktop();
90 bool was_on_activity = c->isOnActivity(activity);
91 bool was_on_all = c->isOnAllActivities();
92 //note: all activities === no activities
93 bool enable = was_on_all || !was_on_activity;
94 c->setOnActivity(activity, enable);
95 if (c->isOnActivity(activity) == was_on_activity && c->isOnAllActivities() == was_on_all) // No change
96 return;
97
98 Workspace *ws = Workspace::self();
99 if (c->isOnCurrentActivity()) {
100 if (c->wantsTabFocus() && options->focusPolicyIsReasonable() &&
101 !was_on_activity && // for stickyness changes
102 //FIXME not sure if the line above refers to the correct activity
103 !dont_activate)
104 ws->requestFocus(c);
105 else
106 ws->restackClientUnderActive(c);
107 } else
108 ws->raiseClient(c);
109
110 //notifyWindowDesktopChanged( c, old_desktop );
111
112 ClientList transients_stacking_order = ws->ensureStackingOrder(c->transients());
113 for (ClientList::ConstIterator it = transients_stacking_order.constBegin();
114 it != transients_stacking_order.constEnd();
115 ++it)
116 toggleClientOnActivity(*it, activity, dont_activate);
117 ws->updateClientArea();
118}
119
120bool Activities::start(const QString &id)
121{
122 Workspace *ws = Workspace::self();
123 if (ws->sessionSaving()) {
124 return false; //ksmserver doesn't queue requests (yet)
125 }
126
127 if (!m_all.contains(id)) {
128 return false; //bogus id
129 }
130
131 ws->loadSubSessionInfo(id);
132
133 QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface");
134 if (ksmserver.isValid()) {
135 ksmserver.asyncCall("restoreSubSession", id);
136 } else {
137 kDebug(1212) << "couldn't get ksmserver interface";
138 return false;
139 }
140 return true;
141}
142
143bool Activities::stop(const QString &id)
144{
145 if (Workspace::self()->sessionSaving()) {
146 return false; //ksmserver doesn't queue requests (yet)
147 //FIXME what about session *loading*?
148 }
149
150 //ugly hack to avoid dbus deadlocks
151 update(true, false);
152 QMetaObject::invokeMethod(this, "reallyStop", Qt::QueuedConnection, Q_ARG(QString, id));
153 //then lie and assume it worked.
154 return true;
155}
156
157void Activities::reallyStop(const QString &id)
158{
159 Workspace *ws = Workspace::self();
160 if (ws->sessionSaving())
161 return; //ksmserver doesn't queue requests (yet)
162
163 kDebug(1212) << id;
164
165 QSet<QByteArray> saveSessionIds;
166 QSet<QByteArray> dontCloseSessionIds;
167 const ClientList &clients = ws->clientList();
168 for (ClientList::const_iterator it = clients.constBegin(); it != clients.constEnd(); ++it) {
169 const Client* c = (*it);
170 const QByteArray sessionId = c->sessionId();
171 if (sessionId.isEmpty()) {
172 continue; //TODO support old wm_command apps too?
173 }
174
175 //kDebug() << sessionId;
176
177 //if it's on the activity that's closing, it needs saving
178 //but if a process is on some other open activity, I don't wanna close it yet
179 //this is, of course, complicated by a process having many windows.
180 if (c->isOnAllActivities()) {
181 dontCloseSessionIds << sessionId;
182 continue;
183 }
184
185 const QStringList activities = c->activities();
186 foreach (const QString & activityId, activities) {
187 if (activityId == id) {
188 saveSessionIds << sessionId;
189 } else if (m_running.contains(activityId)) {
190 dontCloseSessionIds << sessionId;
191 }
192 }
193 }
194
195 ws->storeSubSession(id, saveSessionIds);
196
197 QStringList saveAndClose;
198 QStringList saveOnly;
199 foreach (const QByteArray & sessionId, saveSessionIds) {
200 if (dontCloseSessionIds.contains(sessionId)) {
201 saveOnly << sessionId;
202 } else {
203 saveAndClose << sessionId;
204 }
205 }
206
207 kDebug(1212) << "saveActivity" << id << saveAndClose << saveOnly;
208
209 //pass off to ksmserver
210 QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface");
211 if (ksmserver.isValid()) {
212 ksmserver.asyncCall("saveSubSession", id, saveAndClose, saveOnly);
213 } else {
214 kDebug(1212) << "couldn't get ksmserver interface";
215 }
216}
217
218//BEGIN threaded activity list fetching
219typedef QPair<QStringList*, QStringList> AssignedList;
220typedef QPair<QString, QStringList> CurrentAndList;
221
222static AssignedList
223fetchActivityList(KActivities::Controller *controller, QStringList *target, bool running) // could be member function, but actually it's much simpler this way
224{
225 return AssignedList(target, running ? controller->listActivities(KActivities::Info::Running) :
226 controller->listActivities());
227}
228
229static CurrentAndList
230fetchActivityListAndCurrent(KActivities::Controller *controller)
231{
232 QStringList l = controller->listActivities();
233 QString c = controller->currentActivity();
234 return CurrentAndList(c, l);
235}
236
237void Activities::update(bool running, bool updateCurrent, QObject *target, QString slot)
238{
239 if (updateCurrent) {
240 QFutureWatcher<CurrentAndList>* watcher = new QFutureWatcher<CurrentAndList>;
241 connect( watcher, SIGNAL(finished()), SLOT(handleReply()) );
242 if (!slot.isEmpty()) {
243 watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger"
244 watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target));
245 }
246 watcher->setFuture(QtConcurrent::run(fetchActivityListAndCurrent, m_controller));
247 } else {
248 QFutureWatcher<AssignedList>* watcher = new QFutureWatcher<AssignedList>;
249 connect(watcher, SIGNAL(finished()), SLOT(handleReply()));
250 if (!slot.isEmpty()) {
251 watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger"
252 watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target));
253 }
254 QStringList *target = running ? &m_running : &m_all;
255 watcher->setFuture(QtConcurrent::run(fetchActivityList, m_controller, target, running));
256 }
257}
258
259void Activities::handleReply()
260{
261 QObject *watcherObject = 0;
262 if (QFutureWatcher<AssignedList>* watcher = dynamic_cast< QFutureWatcher<AssignedList>* >(sender())) {
263 // we carry over the to-be-updated StringList member as pointer in the threaded return
264 *(watcher->result().first) = watcher->result().second;
265 watcherObject = watcher;
266 }
267
268 if (!watcherObject) {
269 if (QFutureWatcher<CurrentAndList>* watcher = dynamic_cast< QFutureWatcher<CurrentAndList>* >(sender())) {
270 m_all = watcher->result().second;
271 slotCurrentChanged(watcher->result().first);
272 watcherObject = watcher;
273 }
274 }
275
276 if (watcherObject) {
277 QString slot = watcherObject->property("activityControllerCallback").toString();
278 QObject *target = static_cast<QObject*>(watcherObject->property("activityControllerCallbackTarget").value<void*>());
279 watcherObject->deleteLater(); // has done it's job
280 if (!slot.isEmpty())
281 QMetaObject::invokeMethod(target, slot.toAscii().data(), Qt::DirectConnection);
282 }
283}
284//END threaded activity list fetching
285
286} // namespace
287