2
* Copyright (C) 2010 Ivan Cukic <ivan.cukic(at)kde.org>
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 2,
6
* or (at your option) any later version, as published by the Free
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details
14
* You should have received a copy of the GNU General Public
15
* License along with this program; if not, write to the
16
* Free Software Foundation, Inc.,
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
#include "ActivityManager.h"
21
#include "ActivityManager_p.h"
24
#include <QDBusConnection>
27
#include <KConfigGroup>
32
#include <KWindowSystem>
35
#include <Nepomuk/ResourceManager>
36
#include <Nepomuk/Resource>
37
#include <Nepomuk/Variant>
42
#include "activitymanageradaptor.h"
43
#include "EventProcessor.h"
45
#include "config-features.h"
48
#define NEPOMUK_RUNNING d->nepomukInitialized()
50
using namespace Nepomuk::Vocabulary;
52
#define NEPOMUK_RUNNING false
55
// #define ACTIVITIES_PROTOCOL "activities://"
57
// copied from kdelibs\kdeui\notifications\kstatusnotifieritemdbus_p.cpp
58
// if there is a common place for such definitions please move
60
__inline int toInt(WId wid)
62
return (int)((__int64)wid);
66
__inline int toInt(WId wid)
74
ActivityManagerPrivate::ActivityManagerPrivate(ActivityManager * parent,
75
QHash < WId, SharedInfo::WindowData > & _windows,
76
QHash < KUrl, SharedInfo::ResourceData > & _resources
78
: config("activitymanagerrc"),
80
resources(_resources),
82
m_nepomukInitCalled(false),
87
kDebug() << "\n\n-------------------------------------------------------";
88
kDebug() << "Starting the KDE Activity Manager daemon" << QDateTime::currentDateTime();
89
kDebug() << "-------------------------------------------------------";
91
// Initializing config
92
connect(&configSyncTimer, SIGNAL(timeout()),
93
this, SLOT(configSync()));
95
configSyncTimer.setSingleShot(true);
96
configSyncTimer.setInterval(2 * 60 * 1000);
98
// kDebug() << "reading activities:";
99
foreach (const QString & activity, activitiesConfig().keyList()) {
100
// kDebug() << activity;
101
activities[activity] = ActivityManager::Stopped;
104
foreach (const QString & activity, mainConfig().readEntry("runningActivities", activities.keys())) {
105
// kDebug() << "setting" << activity << "as" << "running";
106
if (activities.contains(activity)) {
107
activities[activity] = ActivityManager::Running;
111
syncActivitiesWithNepomuk();
113
currentActivity = mainConfig().readEntry("currentActivity", QString());
114
// kDebug() << "currentActivity is" << currentActivity;
115
SharedInfo::self()->setCurrentActivity(currentActivity);
117
connect(KWindowSystem::self(), SIGNAL(windowRemoved(WId)),
118
this, SLOT(windowClosed(WId)));
119
connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)),
120
this, SLOT(activeWindowChanged(WId)));
122
//listen to ksmserver for starting/stopping
123
QDBusServiceWatcher *watcher = new QDBusServiceWatcher("org.kde.ksmserver",
124
QDBusConnection::sessionBus(),
125
QDBusServiceWatcher::WatchForRegistration);
126
connect(watcher, SIGNAL(serviceRegistered(QString)), this, SLOT(sessionServiceRegistered()));
127
sessionServiceRegistered();
130
void ActivityManagerPrivate::sessionServiceRegistered()
132
delete ksmserverInterface;
133
ksmserverInterface = new QDBusInterface("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface");
134
if (ksmserverInterface->isValid()) {
135
ksmserverInterface->setParent(this);
136
connect(ksmserverInterface, SIGNAL(subSessionOpened()), this, SLOT(startCompleted()));
137
connect(ksmserverInterface, SIGNAL(subSessionClosed()), this, SLOT(stopCompleted()));
138
connect(ksmserverInterface, SIGNAL(subSessionCloseCanceled()), this, SLOT(stopCancelled())); //spelling fail :)
140
delete ksmserverInterface;
141
ksmserverInterface = 0;
142
kDebug() << "couldn't connect to ksmserver! session stuff won't work";
146
ActivityManagerPrivate::~ActivityManagerPrivate()
151
void ActivityManagerPrivate::windowClosed(WId windowId)
153
// kDebug() << "Window closed..." << windowId
154
// << "one of ours?" << windows.contains(windowId);
156
if (!windows.contains(windowId)) {
160
foreach (const KUrl & uri, windows[windowId].resources) {
161
q->RegisterResourceEvent(windows[windowId].application,
162
toInt(windowId), uri.url(), Event::Closed, resources[uri].reason);
166
void ActivityManagerPrivate::activeWindowChanged(WId windowId)
169
// kDebug() << "Window focussed..." << windowId
170
// << "one of ours?" << windows.contains(windowId);
174
void ActivityManagerPrivate::setActivityState(const QString & id, ActivityManager::State state)
176
if (activities[id] == state) return;
178
// kDebug() << "Set the state of" << id << "to" << state;
181
* Treating 'Starting' as 'Running', and 'Stopping' as 'Stopped'
182
* as far as the config file is concerned
184
bool configNeedsUpdating = ((activities[id] & 4) != (state & 4));
186
activities[id] = state;
189
case ActivityManager::Running:
190
// kDebug() << "sending ActivityStarted signal";
191
emit q->ActivityStarted(id);
194
case ActivityManager::Stopped:
195
// kDebug() << "sending ActivityStopped signal";
196
emit q->ActivityStopped(id);
203
// kDebug() << "sending ActivityStateChanged signal";
204
emit q->ActivityStateChanged(id, state);
206
if (configNeedsUpdating) {
207
mainConfig().writeEntry("runningActivities",
208
activities.keys(ActivityManager::Running) +
209
activities.keys(ActivityManager::Starting));
210
scheduleConfigSync();
214
KConfigGroup ActivityManagerPrivate::activitiesConfig()
216
return KConfigGroup(&config, "activities");
219
KConfigGroup ActivityManagerPrivate::mainConfig()
221
return KConfigGroup(&config, "main");
224
void ActivityManagerPrivate::ensureCurrentActivityIsRunning()
226
QStringList runningActivities = q->ListActivities(ActivityManager::Running);
228
if (!runningActivities.contains(currentActivity)) {
229
if (runningActivities.size() > 0) {
230
setCurrentActivity(runningActivities.first());
232
// kDebug() << "there are no running activities! eek!";
237
bool ActivityManagerPrivate::setCurrentActivity(const QString & id)
239
kDebug() << "Changing rhe activity to:" << id;
241
currentActivity.clear();
244
if (!activities.contains(id)) {
248
// if (currentActivity != id) {
249
// kDebug() << "registering the events";
250
// // Closing the previous activity:
251
// if (!currentActivity.isEmpty()) {
252
// q->RegisterResourceEvent(
253
// "kactivitymanagerd", 0,
254
// "activities://" + currentActivity,
255
// Event::Closed, Event::User
259
// q->RegisterResourceEvent(
260
// "kactivitymanagerd", 0,
261
// "activities://" + id,
262
// Event::Accessed, Event::User
264
// q->RegisterResourceEvent(
265
// "kactivitymanagerd", 0,
266
// "activities://" + id,
267
// Event::Opened, Event::User
271
q->StartActivity(id);
273
currentActivity = id;
274
mainConfig().writeEntry("currentActivity", id);
276
scheduleConfigSync();
279
// kDebug() << (void*) SharedInfo::self() << "Rankings << shared info";
280
SharedInfo::self()->setCurrentActivity(id);
281
emit q->CurrentActivityChanged(id);
285
QString ActivityManagerPrivate::activityName(const QString & id)
287
return activitiesConfig().readEntry(id, QString());
290
void ActivityManagerPrivate::scheduleConfigSync()
292
if (!configSyncTimer.isActive()) {
293
configSyncTimer.start();
297
void ActivityManagerPrivate::configSync()
299
configSyncTimer.stop();
303
void ActivityManagerPrivate::syncActivitiesWithNepomuk()
306
foreach (const QString & activityId, activities.keys()) {
307
Nepomuk::Resource activityResource(activityId, KEXT::Activity());
309
QString name = activitiesConfig().readEntry(activityId, QString());
311
activityResource.setProperty(KEXT::activityIdentifier(), activityId);
313
if (!name.isEmpty()) {
314
activityResource.setLabel(name);
317
#endif // HAVE_NEPOMUK
322
Nepomuk::Resource ActivityManagerPrivate::activityResource(const QString & id)
324
// kDebug() << "testing for nepomuk";
326
if (nepomukInitialized()) {
327
return Nepomuk::Resource(
328
id, KEXT::Activity());
330
return Nepomuk::Resource();
334
/* lazy init of nepomuk */
335
bool ActivityManagerPrivate::nepomukInitialized()
337
if (m_nepomukInitCalled) return
338
Nepomuk::ResourceManager::instance()->initialized();
340
m_nepomukInitCalled = true;
342
connect(Nepomuk::ResourceManager::instance(), SIGNAL(nepomukSystemStarted()), this, SLOT(backstoreAvailable()));
344
return (Nepomuk::ResourceManager::instance()->init() == 0);
347
void ActivityManagerPrivate::backstoreAvailable()
349
//emit q->BackstoreAvailable();
350
//kick the icons, so that clients don't need to know that they depend on nepomuk
351
for (QHash<QString, ActivityManager::State>::const_iterator i = activities.constBegin();
352
i != activities.constEnd(); ++i) {
353
emit q->ActivityChanged(i.key());
357
#else // HAVE_NEPOMUK
359
void ActivityManagerPrivate::backstoreAvailable()
363
#endif // HAVE_NEPOMUK
367
ActivityManager::ActivityManager()
368
: d(new ActivityManagerPrivate(this,
369
SharedInfo::self()->m_windows,
370
SharedInfo::self()->m_resources))
373
QDBusConnection dbus = QDBusConnection::sessionBus();
374
new ActivityManagerAdaptor(this);
375
dbus.registerObject("/ActivityManager", this);
377
// TODO: Sync activities in nepomuk with currently existing ones
378
// but later, when we are sure nepomuk is running
380
// ensureCurrentActivityIsRunning();
382
KCrash::setFlags(KCrash::AutoRestart);
384
EventProcessor::self();
386
// kDebug() << "RegisterResourceEvent open" << d->currentActivity;
387
// RegisterResourceEvent(
388
// "kactivitymanagerd", 0,
389
// "activities://" + d->currentActivity,
390
// Event::Accessed, Event::User
392
// RegisterResourceEvent(
393
// "kactivitymanagerd", 0,
394
// "activities://" + d->currentActivity,
395
// Event::Opened, Event::User
400
ActivityManager::~ActivityManager()
402
// kDebug() << "RegisterResourceEvent close" << d->currentActivity;
403
// RegisterResourceEvent(
404
// "kactivitymanagerd", 0,
405
// "activities://" + d->currentActivity,
406
// Event::Closed, Event::User
411
void ActivityManager::Start()
413
// doing absolutely nothing
416
void ActivityManager::Stop()
419
QCoreApplication::quit();
422
bool ActivityManager::IsBackstoreAvailable() const
424
return NEPOMUK_RUNNING;
428
// workspace activities control
430
QString ActivityManager::CurrentActivity() const
432
return d->currentActivity;
435
bool ActivityManager::SetCurrentActivity(const QString & id)
443
return d->setCurrentActivity(id);
446
QString ActivityManager::AddActivity(const QString & name)
452
// Ensuring a new Uuid. The loop should usually end after only
454
QStringList existingActivities = d->activities.keys();
455
while (id.isEmpty() || existingActivities.contains(id)) {
456
id = QUuid::createUuid();
457
id.replace(QRegExp("[{}]"), QString());
460
d->setActivityState(id, Running);
462
SetActivityName(id, name);
464
emit ActivityAdded(id);
470
void ActivityManager::RemoveActivity(const QString & id)
474
if (d->activities.size() < 2 ||
475
!d->activities.contains(id)) {
479
// If the activity is running, stash it
482
d->setActivityState(id, Invalid);
484
// Removing the activity
485
d->activities.remove(id);
486
d->activitiesConfig().deleteEntry(id);
488
// If the removed activity was the current one,
489
// set another activity as current
490
if (d->currentActivity == id) {
491
d->ensureCurrentActivityIsRunning();
494
if (d->transitioningActivity == id) {
495
//very unlikely, but perhaps not impossible
496
//but it being deleted doesn't mean ksmserver is un-busy..
497
//in fact, I'm not quite sure what would happen.... FIXME
498
//so I'll just add some output to warn that it happened.
499
// kDebug() << "deleting activity in transition. watch out!";
502
emit ActivityRemoved(id);
506
void ActivityManager::StartActivity(const QString & id)
510
if (!d->activities.contains(id) ||
511
d->activities[id] != Stopped) {
515
if (!d->transitioningActivity.isEmpty()) {
516
// kDebug() << "busy!!";
517
//TODO: implement a queue instead
521
d->transitioningActivity = id;
522
d->setActivityState(id, Starting);
524
//ugly hack to avoid dbus deadlocks
525
QMetaObject::invokeMethod(d, "reallyStartActivity", Qt::QueuedConnection, Q_ARG(QString, id));
528
void ActivityManagerPrivate::reallyStartActivity(const QString & id)
531
// start the starting :)
532
QDBusInterface kwin("org.kde.kwin", "/KWin", "org.kde.KWin");
533
if (kwin.isValid()) {
534
QDBusMessage reply = kwin.call("startActivity", id);
535
if (reply.type() == QDBusMessage::ErrorMessage) {
536
// kDebug() << "dbus error:" << reply.errorMessage();
539
QList<QVariant> ret = reply.arguments();
540
if (ret.length() == 1 && ret.first().toBool()) {
543
// kDebug() << "call returned false; probably ksmserver is busy";
544
setActivityState(transitioningActivity, ActivityManager::Stopped);
545
transitioningActivity.clear();
546
return; //assume we're mid-logout and just don't touch anything
550
// kDebug() << "couldn't get kwin interface";
554
//maybe they use compiz?
555
//go ahead without the session
558
configSync(); //force immediate sync
561
void ActivityManagerPrivate::startCompleted()
563
if (transitioningActivity.isEmpty()) {
564
// kDebug() << "huh?";
567
setActivityState(transitioningActivity, ActivityManager::Running);
568
transitioningActivity.clear();
571
void ActivityManager::StopActivity(const QString & id)
575
if (!d->activities.contains(id) ||
576
d->activities[id] == Stopped) {
580
if (!d->transitioningActivity.isEmpty()) {
581
// kDebug() << "busy!!";
582
//TODO: implement a queue instead
586
d->transitioningActivity = id;
587
d->setActivityState(id, Stopping);
589
//ugly hack to avoid dbus deadlocks
590
QMetaObject::invokeMethod(d, "reallyStopActivity", Qt::QueuedConnection, Q_ARG(QString, id));
593
void ActivityManagerPrivate::reallyStopActivity(const QString & id)
596
// start the stopping :)
597
QDBusInterface kwin("org.kde.kwin", "/KWin", "org.kde.KWin");
598
if (kwin.isValid()) {
599
QDBusMessage reply = kwin.call("stopActivity", id);
600
if (reply.type() == QDBusMessage::ErrorMessage) {
601
// kDebug() << "dbus error:" << reply.errorMessage();
604
QList<QVariant> ret = reply.arguments();
605
if (ret.length() == 1 && ret.first().toBool()) {
609
// kDebug() << "call returned false; probably ksmserver is busy";
611
return; //assume we're mid-logout and just don't touch anything
615
// kDebug() << "couldn't get kwin interface";
619
//maybe they use compiz?
620
//go ahead without the session
625
void ActivityManagerPrivate::stopCompleted()
627
if (transitioningActivity.isEmpty()) {
628
// kDebug() << "huh?";
631
setActivityState(transitioningActivity, ActivityManager::Stopped);
632
if (currentActivity == transitioningActivity) {
633
ensureCurrentActivityIsRunning();
635
transitioningActivity.clear();
636
configSync(); //force immediate sync
639
void ActivityManagerPrivate::stopCancelled()
641
if (transitioningActivity.isEmpty()) {
642
// kDebug() << "huh?";
645
setActivityState(transitioningActivity, ActivityManager::Running);
646
transitioningActivity.clear();
649
int ActivityManager::ActivityState(const QString & id) const
651
//kDebug() << id << "- is it in" << d->activities << "?";
652
if (!d->activities.contains(id)) {
655
// kDebug() << "state of" << id << "is" << d->activities[id];
656
return d->activities[id];
660
QStringList ActivityManager::ListActivities() const
662
return d->activities.keys();
665
QStringList ActivityManager::ListActivities(int state) const
667
return d->activities.keys((State)state);
670
QString ActivityManager::ActivityName(const QString & id) const
672
return d->activityName(id);
675
void ActivityManager::SetActivityName(const QString & id, const QString & name)
677
// kDebug() << id << name;
679
if (!d->activities.contains(id)) {
683
d->activitiesConfig().writeEntry(id, name);
686
if (NEPOMUK_RUNNING) {
687
d->activityResource(id).setLabel(name);
691
d->scheduleConfigSync();
693
// kDebug() << "emit ActivityChanged" << id;
694
emit ActivityChanged(id);
697
QString ActivityManager::ActivityDescription(const QString & id) const
699
if (!NEPOMUK_RUNNING || !d->activities.contains(id)) {
704
return d->activityResource(id).description();
708
void ActivityManager::SetActivityDescription(const QString & id, const QString & description)
710
// kDebug() << id << description;
712
if (!NEPOMUK_RUNNING || !d->activities.contains(id)) {
717
d->activityResource(id).setDescription(description);
720
// kDebug() << "emit ActivityChanged" << id;
721
emit ActivityChanged(id);
724
QString ActivityManager::ActivityIcon(const QString & id) const
726
if (!NEPOMUK_RUNNING || !d->activities.contains(id)) {
731
QStringList symbols = d->activityResource(id).symbols();
733
if (symbols.isEmpty()) {
736
return symbols.first();
743
void ActivityManager::SetActivityIcon(const QString & id, const QString & icon)
745
// kDebug() << id << icon;
747
if (!NEPOMUK_RUNNING || !d->activities.contains(id)) {
752
d->activityResource(id).setSymbols(QStringList() << icon);
754
// kDebug() << "emit ActivityChanged" << id;
755
emit ActivityChanged(id);
760
// Resource related mothods
761
void ActivityManager::RegisterResourceEvent(const QString & application, uint _windowId,
762
const QString & uri, uint event, uint reason)
764
if (event > Event::LastEventType || reason > Event::LastEventReason)
767
// Dirty way to skip special web browser URIs
768
if (uri.startsWith("about:"))
772
WId windowId = (WId) _windowId;
774
kDebug() << "New event on the horizon" << application << windowId << event;
777
if (uri.startsWith("nepomuk:")) {
778
Nepomuk::Resource resource(kuri);
780
if (resource.hasProperty(NIE::url())) {
781
kuri = resource.property(NIE::url()).toUrl();
782
// kDebug() << "Passing real url" << kuri;
784
// kWarning() << "Passing nepomuk:// url" << kuri;
789
if (event == Event::Opened) {
791
// kDebug() << "Saving the open event for the window" << windowId;
793
d->windows[windowId].resources << kuri;
794
d->resources[kuri].activities << CurrentActivity();
796
// kDebug() << d->windows.keys();
798
} else if (event == Event::Closed) {
800
// TODO: Remove from d->resources if needed
801
d->windows.remove(windowId);
805
EventProcessor::self()->addEvent(application, windowId,
806
kuri.url(), (Event::Type) event, (Event::Reason) reason);
810
void ActivityManager::RegisterResourceMimeType(const QString & uri, const QString & mimetype)
814
d->resources[kuri].mimetype = mimetype;
817
void ActivityManager::RegisterResourceTitle(const QString & uri, const QString & title)
821
d->resources[kuri].title = title;
824
void ActivityManager::LinkResourceToActivity(const QString & uri, const QString & activity)
827
if (!d->nepomukInitialized()) return;
829
kDebug() << "Linking" << uri << "to" << activity << CurrentActivity();
832
// I'd like a resource isRelated activity more than vice-versa
833
// but the active models are checking for the other way round.
834
// It is defined in the ontologies as a symmetric relation, but
835
// Nepomuk doesn't care about that.
837
// Nepomuk::Resource(KUrl(uri)).
838
// addIsRelated(d->activityResource(
839
// activity.isEmpty() ?
840
// CurrentActivity() : activity
844
d->activityResource(activity.isEmpty() ? CurrentActivity() : activity).
845
addIsRelated(Nepomuk::Resource(KUrl(uri))
851
// void ActivityManager::UnlinkResourceFromActivity(const QString & uri, const QString & activity)
853
// #ifdef HAVE_NEPOMUK
854
// if (!d->nepomukInitialized()) return;
856
// Nepomuk::Resource(KUrl(uri)).
857
// addIsRelated(d->activityResource(
858
// activity.isEmpty() ?
859
// CurrentActivity() : activity
867
ActivityManager * ActivityManager::self()
869
return static_cast<ActivityManager*>(kapp);