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 | |
42 | struct 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 ; |
91 | |
92 | //! Timer for delayed writing to kglobalshortcutsrc |
93 | QTimer writeoutTimer; |
94 | |
95 | //! Our holder |
96 | KGlobalAccelD *q; |
97 | }; |
98 | |
99 | |
100 | GlobalShortcut *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 | |
115 | GlobalShortcut *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 | |
162 | KdeDGlobalAccel::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 | |
178 | GlobalShortcut *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 | |
217 | void 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 | |
257 | Q_DECLARE_METATYPE(QStringList) |
258 | |
259 | KGlobalAccelD::KGlobalAccelD(QObject* parent) |
260 | : QObject(parent), |
261 | d(new KGlobalAccelDPrivate(this)) |
262 | {} |
263 | |
264 | |
265 | bool 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 | |
308 | KGlobalAccelD::~KGlobalAccelD() |
309 | { |
310 | GlobalShortcutsRegistry::self()->deactivateShortcuts(); |
311 | delete d; |
312 | } |
313 | |
314 | QList<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 | |
333 | QList<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 | |
364 | QStringList 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 | |
378 | void 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 | |
389 | QList<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 | |
403 | void 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 | |
414 | QList<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 | |
423 | QList<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. |
434 | void 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 | |
464 | QDBusObjectPath 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 | |
485 | QList<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 | |
506 | bool 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 | |
515 | void 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 | |
527 | bool 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 | |
545 | void 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 | |
561 | QList<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 | |
612 | void 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 | |
628 | void KGlobalAccelD::scheduleWriteSettings() const |
629 | { |
630 | if (!d->writeoutTimer.isActive()) |
631 | d->writeoutTimer.start(500); |
632 | } |
633 | |
634 | |
635 | #include "moc_kglobalacceld.cpp" |
636 | |