1/*
2 This file is part of the KDE libraries
3
4 Copyright (c) 2007 Andreas Hartmetz <ahartmetz@gmail.com>
5 Copyright (c) 2007 Michael Jansen <kde@michael-jansen.biz>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library 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 GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22
23#include "kglobalacceld.h"
24
25#include "component.h"
26#include "globalshortcut.h"
27#include "globalshortcutcontext.h"
28#include "globalshortcutsregistry.h"
29
30#include <QtCore/QTimer>
31#include <QtCore/QMetaMethod>
32#include <QtDBus/QDBusMetaType>
33#include <QtDBus/QDBusObjectPath>
34
35#include "klocale.h"
36#include "kglobalaccel.h"
37#include "kglobalsettings.h"
38#include "knotification.h"
39#include "kdebug.h"
40
41
42struct KGlobalAccelDPrivate
43 {
44
45 KGlobalAccelDPrivate(KGlobalAccelD *q)
46 : q(q)
47 {}
48
49
50 GlobalShortcut *findAction(const QStringList &actionId) const;
51
52 /**
53 * Find the action @a shortcutUnique in @a componentUnique.
54 *
55 * @return the action or @c null if doesn't exist
56 */
57 GlobalShortcut *findAction(
58 const QString &componentUnique,
59 const QString &shortcutUnique) const;
60
61 GlobalShortcut *addAction(const QStringList &actionId);
62 KdeDGlobalAccel::Component *component(const QStringList &actionId) const;
63
64
65 //! Use KNotification to inform the user new global shortcuts were
66 //! registered
67 void _k_newGlobalShortcutNotification();
68
69 void splitComponent(QString &component, QString &context) const
70 {
71 context = "default";
72 if (component.indexOf('|')!=-1)
73 {
74 QStringList tmp = component.split('|');
75 Q_ASSERT(tmp.size()==2);
76 component= tmp.at(0);
77 context= tmp.at(1);
78 }
79 }
80
81 enum ChangeType
82 {
83 NewShortcut
84 };
85
86 // List if changed components for _k_globalShortcutNotification
87 QMap<QString, ChangeType> changedComponents;
88
89 //! Timer for delayed popup on new shortcut registration
90 QTimer popupTimer;
91
92 //! Timer for delayed writing to kglobalshortcutsrc
93 QTimer writeoutTimer;
94
95 //! Our holder
96 KGlobalAccelD *q;
97 };
98
99
100GlobalShortcut *KGlobalAccelDPrivate::findAction(const QStringList &actionId) const
101 {
102 // Check if actionId is valid
103 if (actionId.size() != 4)
104 {
105 kDebug() << "Invalid! '" << actionId << "'";
106 return NULL;
107 }
108
109 return findAction(
110 actionId.at(KGlobalAccel::ComponentUnique),
111 actionId.at(KGlobalAccel::ActionUnique));
112 }
113
114
115GlobalShortcut *KGlobalAccelDPrivate::findAction(
116 const QString &_componentUnique,
117 const QString &shortcutUnique) const
118 {
119 QString componentUnique = _componentUnique;
120
121 KdeDGlobalAccel::Component *component;
122 QString contextUnique;
123 if (componentUnique.indexOf('|')==-1)
124 {
125 component = GlobalShortcutsRegistry::self()->getComponent( componentUnique);
126 if (component) contextUnique = component->currentContext()->uniqueName();
127 }
128 else
129 {
130 splitComponent(componentUnique, contextUnique);
131 component = GlobalShortcutsRegistry::self()->getComponent( componentUnique);
132 }
133
134 if (!component)
135 {
136#ifdef KDEDGLOBALACCEL_TRACE
137 kDebug() << componentUnique << "not found";
138#endif
139 return NULL;
140 }
141
142 GlobalShortcut *shortcut = component
143 ? component->getShortcutByName(shortcutUnique, contextUnique)
144 : NULL;
145
146#ifdef KDEDGLOBALACCEL_TRACE
147 if (shortcut)
148 {
149 kDebug() << componentUnique
150 << contextUnique
151 << shortcut->uniqueName();
152 }
153 else
154 {
155 kDebug() << "No match for" << shortcutUnique;
156 }
157#endif
158 return shortcut;
159 }
160
161
162KdeDGlobalAccel::Component *KGlobalAccelDPrivate::component(const QStringList &actionId) const
163{
164 // Get the component for the action. If we have none create a new one
165 KdeDGlobalAccel::Component *component = GlobalShortcutsRegistry::self()->getComponent(actionId.at(KGlobalAccel::ComponentUnique));
166 if (!component)
167 {
168 component = new KdeDGlobalAccel::Component(
169 actionId.at(KGlobalAccel::ComponentUnique),
170 actionId.at(KGlobalAccel::ComponentFriendly),
171 GlobalShortcutsRegistry::self());
172 Q_ASSERT(component);
173 }
174 return component;
175}
176
177
178GlobalShortcut *KGlobalAccelDPrivate::addAction(const QStringList &actionId)
179{
180 Q_ASSERT(actionId.size() >= 4);
181
182 QString componentUnique = actionId.at(KGlobalAccel::ComponentUnique);
183
184 QString contextUnique = "default";
185
186 if (componentUnique.indexOf("|")!=-1) {
187 QStringList tmp = componentUnique.split('|');
188 Q_ASSERT(tmp.size()==2);
189 componentUnique = tmp.at(0);
190 contextUnique = tmp.at(1);
191 }
192
193 QStringList actionIdTmp = actionId;
194 actionIdTmp.replace(KGlobalAccel::ComponentUnique, componentUnique);
195
196 // Create the component if necessary
197 KdeDGlobalAccel::Component *component = this->component(actionIdTmp);
198 Q_ASSERT(component);
199
200 // Create the context if necessary
201 if (component->getShortcutContexts().count(contextUnique)==0) {
202 component->createGlobalShortcutContext(contextUnique);
203 }
204
205 Q_ASSERT(!component->getShortcutByName(componentUnique, contextUnique));
206 changedComponents.insert(actionId.at(KGlobalAccel::ComponentUnique), NewShortcut);
207
208 if (!popupTimer.isActive()) popupTimer.start(500);
209
210 return new GlobalShortcut(
211 actionId.at(KGlobalAccel::ActionUnique),
212 actionId.at(KGlobalAccel::ActionFriendly),
213 component->shortcutContext(contextUnique));
214}
215
216
217void KGlobalAccelDPrivate::_k_newGlobalShortcutNotification()
218{
219 Q_FOREACH(const QString &uniqueName, changedComponents.keys())
220 {
221 kDebug() << "Showing Notification for component" << uniqueName;
222
223 KdeDGlobalAccel::Component *component = GlobalShortcutsRegistry::self()->getComponent(uniqueName);
224 if (!component)
225 {
226 // Can happen if a component is removed immediately after
227 // registering it. kdelibs/kdeui/tests/kglobalshortcuttests does
228 // it for example.
229 continue;
230 }
231
232 KNotification *notification = new KNotification(
233 "newshortcutregistered",
234 KNotification::CloseOnTimeout,
235 q->parent());
236
237 notification->setText(
238 i18n("The application %1 has registered a new global shortcut",
239 component->friendlyName()));
240
241 notification->setActions( QStringList( i18n( "Open Global Shortcuts Editor" ) ) );
242
243 notification->addContext( "application", component->friendlyName() );
244
245 QObject::connect(notification, SIGNAL(action1Activated()),
246 component, SLOT(showKCM()));
247
248 notification->sendEvent();
249 }
250
251 changedComponents.clear();
252}
253
254
255
256
257Q_DECLARE_METATYPE(QStringList)
258
259KGlobalAccelD::KGlobalAccelD(QObject* parent)
260: QObject(parent),
261 d(new KGlobalAccelDPrivate(this))
262{}
263
264
265bool KGlobalAccelD::init()
266{
267 qDBusRegisterMetaType< QList<int> >();
268 qDBusRegisterMetaType< QList<QDBusObjectPath> >();
269 qDBusRegisterMetaType< QList<QStringList> >();
270 qDBusRegisterMetaType<QStringList>();
271 qDBusRegisterMetaType<KGlobalShortcutInfo>();
272 qDBusRegisterMetaType< QList<KGlobalShortcutInfo> >();
273
274 GlobalShortcutsRegistry *reg = GlobalShortcutsRegistry::self();
275 Q_ASSERT(reg);
276
277 d->writeoutTimer.setSingleShot(true);
278 connect(&d->writeoutTimer, SIGNAL(timeout()),
279 reg, SLOT(writeSettings()));
280
281 d->popupTimer.setSingleShot(true);
282 connect(&d->popupTimer, SIGNAL(timeout()),
283 this, SLOT(_k_newGlobalShortcutNotification()));
284
285 if (!QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.kglobalaccel"))) {
286 kWarning() << "Failed to register service org.kde.kglobalaccel";
287 return false;
288 }
289
290 if (!QDBusConnection::sessionBus().registerObject(
291 QLatin1String("/kglobalaccel"),
292 this,
293 QDBusConnection::ExportScriptableContents)) {
294 kWarning() << "Failed to register object kglobalaccel in org.kde.kglobalaccel";
295 return false;
296 }
297
298 GlobalShortcutsRegistry::self()->setDBusPath(QDBusObjectPath("/"));
299 GlobalShortcutsRegistry::self()->loadSettings();
300
301 connect(KGlobalSettings::self(), SIGNAL(blockShortcuts(int)),
302 SLOT(blockGlobalShortcuts(int)));
303
304 return true;
305}
306
307
308KGlobalAccelD::~KGlobalAccelD()
309{
310 GlobalShortcutsRegistry::self()->deactivateShortcuts();
311 delete d;
312}
313
314QList<QStringList> KGlobalAccelD::allMainComponents() const
315{
316 QList<QStringList> ret;
317 QStringList emptyList;
318 for (int i = 0; i < 4; i++) {
319 emptyList.append(QString());
320 }
321
322 foreach (const KdeDGlobalAccel::Component *component, GlobalShortcutsRegistry::self()->allMainComponents()) {
323 QStringList actionId(emptyList);
324 actionId[KGlobalAccel::ComponentUnique] = component->uniqueName();
325 actionId[KGlobalAccel::ComponentFriendly] = component->friendlyName();
326 ret.append(actionId);
327 }
328
329 return ret;
330}
331
332
333QList<QStringList> KGlobalAccelD::allActionsForComponent(const QStringList &actionId) const
334{
335 //### Would it be advantageous to sort the actions by unique name?
336 QList<QStringList> ret;
337
338 KdeDGlobalAccel::Component *const component =
339 GlobalShortcutsRegistry::self()->getComponent(actionId[KGlobalAccel::ComponentUnique]);
340 if (!component) {
341 return ret;
342 }
343
344 QStringList partialId(actionId[KGlobalAccel::ComponentUnique]); //ComponentUnique
345 partialId.append(QString()); //ActionUnique
346 //Use our internal friendlyName, not the one passed in. We should have the latest data.
347 partialId.append(component->friendlyName()); //ComponentFriendly
348 partialId.append(QString()); //ActionFriendly
349
350 foreach (const GlobalShortcut *const shortcut, component->allShortcuts()) {
351 if (shortcut->isFresh()) {
352 // isFresh is only an intermediate state, not to be reported outside.
353 continue;
354 }
355 QStringList actionId(partialId);
356 actionId[KGlobalAccel::ActionUnique] = shortcut->uniqueName();
357 actionId[KGlobalAccel::ActionFriendly] = shortcut->friendlyName();
358 ret.append(actionId);
359 }
360 return ret;
361}
362
363
364QStringList KGlobalAccelD::action(int key) const
365{
366 GlobalShortcut *shortcut = GlobalShortcutsRegistry::self()->getShortcutByKey(key);
367 QStringList ret;
368 if (shortcut) {
369 ret.append(shortcut->context()->component()->uniqueName());
370 ret.append(shortcut->uniqueName());
371 ret.append(shortcut->context()->component()->friendlyName());
372 ret.append(shortcut->friendlyName());
373 }
374 return ret;
375}
376
377
378void KGlobalAccelD::activateGlobalShortcutContext(
379 const QString &component,
380 const QString &uniqueName)
381{
382 KdeDGlobalAccel::Component *const comp =
383 GlobalShortcutsRegistry::self()->getComponent(component);
384 if (comp)
385 comp->activateGlobalShortcutContext(uniqueName);
386}
387
388
389QList<QDBusObjectPath> KGlobalAccelD::allComponents() const
390 {
391 QList<QDBusObjectPath> allComp;
392
393 Q_FOREACH (const KdeDGlobalAccel::Component *component,
394 GlobalShortcutsRegistry::self()->allMainComponents())
395 {
396 allComp.append(component->dbusPath());
397 }
398
399 return allComp;
400 }
401
402
403void KGlobalAccelD::blockGlobalShortcuts(int block)
404 {
405#ifdef KDEDGLOBALACCEL_TRACE
406 kDebug() << block;
407#endif
408 block
409 ? GlobalShortcutsRegistry::self()->deactivateShortcuts(true)
410 : GlobalShortcutsRegistry::self()->activateShortcuts();
411 }
412
413
414QList<int> KGlobalAccelD::shortcut(const QStringList &action) const
415{
416 GlobalShortcut *shortcut = d->findAction(action);
417 if (shortcut)
418 return shortcut->keys();
419 return QList<int>();
420}
421
422
423QList<int> KGlobalAccelD::defaultShortcut(const QStringList &action) const
424{
425 GlobalShortcut *shortcut = d->findAction(action);
426 if (shortcut)
427 return shortcut->defaultKeys();
428 return QList<int>();
429}
430
431
432// This method just registers the action. Nothing else. Shortcut has to be set
433// later.
434void KGlobalAccelD::doRegister(const QStringList &actionId)
435{
436#ifdef KDEDGLOBALACCEL_TRACE
437 kDebug() << actionId;
438#endif
439
440 // Check because we would not want to add a action for an invalid
441 // actionId. findAction returns NULL in that case.
442 if (actionId.size() < 4) {
443 return;
444 }
445
446 GlobalShortcut *shortcut = d->findAction(actionId);
447 if (!shortcut) {
448 shortcut = d->addAction(actionId);
449 } else {
450 //a switch of locales is one common reason for a changing friendlyName
451 if ((!actionId[KGlobalAccel::ActionFriendly].isEmpty()) && shortcut->friendlyName() != actionId[KGlobalAccel::ActionFriendly]) {
452 shortcut->setFriendlyName(actionId[KGlobalAccel::ActionFriendly]);
453 scheduleWriteSettings();
454 }
455 if ((!actionId[KGlobalAccel::ComponentFriendly].isEmpty())
456 && shortcut->context()->component()->friendlyName() != actionId[KGlobalAccel::ComponentFriendly]) {
457 shortcut->context()->component()->setFriendlyName(actionId[KGlobalAccel::ComponentFriendly]);
458 scheduleWriteSettings();
459 }
460 }
461}
462
463
464QDBusObjectPath KGlobalAccelD::getComponent(const QString &componentUnique) const
465 {
466#ifdef KDEDGLOBALACCEL_TRACE
467 kDebug() << componentUnique;
468#endif
469
470 KdeDGlobalAccel::Component *component =
471 GlobalShortcutsRegistry::self()->getComponent(componentUnique);
472
473 if (component)
474 {
475 return component->dbusPath();
476 }
477 else
478 {
479 sendErrorReply("org.kde.kglobalaccel.NoSuchComponent", QString("The component '%1' doesn't exist.").arg(componentUnique));
480 return QDBusObjectPath("/");
481 }
482 }
483
484
485QList<KGlobalShortcutInfo> KGlobalAccelD::getGlobalShortcutsByKey(int key) const
486 {
487#ifdef KDEDGLOBALACCEL_TRACE
488 kDebug() << key;
489#endif
490 QList<GlobalShortcut*> shortcuts =
491 GlobalShortcutsRegistry::self()->getShortcutsByKey(key);
492
493 QList<KGlobalShortcutInfo> rc;
494 Q_FOREACH(const GlobalShortcut *sc, shortcuts)
495 {
496#ifdef KDEDGLOBALACCEL_TRACE
497 kDebug() << sc->context()->uniqueName() << sc->uniqueName();
498#endif
499 rc.append(static_cast<KGlobalShortcutInfo>(*sc));
500 }
501
502 return rc;
503 }
504
505
506bool KGlobalAccelD::isGlobalShortcutAvailable(int shortcut, const QString &component) const
507 {
508 QString realComponent = component;
509 QString context;
510 d->splitComponent(realComponent, context);
511 return GlobalShortcutsRegistry::self()->isShortcutAvailable(shortcut, realComponent, context);
512 }
513
514
515void KGlobalAccelD::setInactive(const QStringList &actionId)
516 {
517#ifdef KDEDGLOBALACCEL_TRACE
518 kDebug() << actionId;
519#endif
520
521 GlobalShortcut *shortcut = d->findAction(actionId);
522 if (shortcut)
523 shortcut->setIsPresent(false);
524 }
525
526
527bool KGlobalAccelD::unregister(const QString &componentUnique, const QString &shortcutUnique)
528{
529#ifdef KDEDGLOBALACCEL_TRACE
530 kDebug() << componentUnique << shortcutUnique;
531#endif
532
533 // Stop grabbing the key
534 GlobalShortcut *shortcut = d->findAction(componentUnique, shortcutUnique);
535 if (shortcut) {
536 shortcut->unRegister();
537 scheduleWriteSettings();
538 }
539
540 return shortcut;
541
542}
543
544
545void KGlobalAccelD::unRegister(const QStringList &actionId)
546{
547#ifdef KDEDGLOBALACCEL_TRACE
548 kDebug() << actionId;
549#endif
550
551 // Stop grabbing the key
552 GlobalShortcut *shortcut = d->findAction(actionId);
553 if (shortcut) {
554 shortcut->unRegister();
555 scheduleWriteSettings();
556 }
557
558}
559
560
561QList<int> KGlobalAccelD::setShortcut(const QStringList &actionId,
562 const QList<int> &keys, uint flags)
563{
564 //spare the DBus framework some work
565 const bool setPresent = (flags & SetPresent);
566 const bool isAutoloading = !(flags & NoAutoloading);
567 const bool isDefault = (flags & IsDefault);
568
569 GlobalShortcut *shortcut = d->findAction(actionId);
570 if (!shortcut) {
571 return QList<int>();
572 }
573
574 //default shortcuts cannot clash because they don't do anything
575 if (isDefault) {
576 if (shortcut->defaultKeys() != keys) {
577 shortcut->setDefaultKeys(keys);
578 scheduleWriteSettings();
579 }
580 return keys; //doesn't matter
581 }
582
583 if (isAutoloading && !shortcut->isFresh()) {
584 //the trivial and common case - synchronize the action from our data
585 //and exit.
586 if (!shortcut->isPresent() && setPresent) {
587 shortcut->setIsPresent(true);
588 }
589 // We are finished here. Return the list of current active keys.
590 return shortcut->keys();
591 }
592
593 //now we are actually changing the shortcut of the action
594 shortcut->setKeys(keys);
595
596 if (setPresent) {
597 shortcut->setIsPresent(true);
598 }
599
600 //maybe isFresh should really only be set if setPresent, but only two things should use !setPresent:
601 //- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state
602 //- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts
603 // which can never be fresh if created the usual way
604 shortcut->setIsFresh(false);
605
606 scheduleWriteSettings();
607
608 return shortcut->keys();
609}
610
611
612void KGlobalAccelD::setForeignShortcut(const QStringList &actionId, const QList<int> &keys)
613{
614#ifdef KDEDGLOBALACCEL_TRACE
615 kDebug() << actionId;
616#endif
617
618 GlobalShortcut *shortcut = d->findAction(actionId);
619 if (!shortcut)
620 return;
621
622 QList<int> newKeys = setShortcut(actionId, keys, NoAutoloading);
623
624 emit yourShortcutGotChanged(actionId, newKeys);
625}
626
627
628void KGlobalAccelD::scheduleWriteSettings() const
629 {
630 if (!d->writeoutTimer.isActive())
631 d->writeoutTimer.start(500);
632 }
633
634
635#include "moc_kglobalacceld.cpp"
636