1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org> |
6 | |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along 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 | |
35 | namespace KWin |
36 | { |
37 | |
38 | KWIN_SINGLETON_FACTORY(Activities) |
39 | |
40 | Activities::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 | |
51 | Activities::~Activities() |
52 | { |
53 | s_self = NULL; |
54 | } |
55 | |
56 | void Activities::setCurrent(const QString &activity) |
57 | { |
58 | m_controller->setCurrentActivity(activity); |
59 | } |
60 | |
61 | void 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 | |
71 | void Activities::slotAdded(const QString &activity) |
72 | { |
73 | m_all << activity; |
74 | } |
75 | |
76 | void 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 | |
87 | void 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 | |
120 | bool 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 | |
143 | bool 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 | |
157 | void 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 |
219 | typedef QPair<QStringList*, QStringList> AssignedList; |
220 | typedef QPair<QString, QStringList> CurrentAndList; |
221 | |
222 | static AssignedList |
223 | fetchActivityList(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 | |
229 | static CurrentAndList |
230 | fetchActivityListAndCurrent(KActivities::Controller *controller) |
231 | { |
232 | QStringList l = controller->listActivities(); |
233 | QString c = controller->currentActivity(); |
234 | return CurrentAndList(c, l); |
235 | } |
236 | |
237 | void 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 | |
259 | void 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 | |