1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2001, 2002 Ellis Whitehead <ellis@kde.org> |
4 | SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org> |
5 | SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> |
6 | SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz> |
7 | |
8 | SPDX-License-Identifier: LGPL-2.0-or-later |
9 | */ |
10 | |
11 | #include "kglobalaccel.h" |
12 | #include "kglobalaccel_debug.h" |
13 | #include "kglobalaccel_p.h" |
14 | |
15 | #include <memory> |
16 | |
17 | #include <QAction> |
18 | #include <QDBusMessage> |
19 | #include <QDBusMetaType> |
20 | #include <QGuiApplication> |
21 | #include <QMessageBox> |
22 | #include <QPushButton> |
23 | #include <config-kglobalaccel.h> |
24 | |
25 | #if HAVE_X11 |
26 | #include <private/qtx11extras_p.h> |
27 | #endif |
28 | |
29 | org::kde::kglobalaccel::Component *KGlobalAccelPrivate::getComponent(const QString &componentUnique, bool remember = false) |
30 | { |
31 | // Check if we already have this component |
32 | { |
33 | auto component = components.value(componentUnique); |
34 | if (component) { |
35 | return component; |
36 | } |
37 | } |
38 | |
39 | // Get the path for our component. We have to do that because |
40 | // componentUnique is probably not a valid dbus object path |
41 | QDBusReply<QDBusObjectPath> reply = iface()->getComponent(componentUnique); |
42 | if (!reply.isValid()) { |
43 | if (reply.error().name() == QLatin1String("org.kde.kglobalaccel.NoSuchComponent" )) { |
44 | // No problem. The component doesn't exists. That's normal |
45 | return nullptr; |
46 | } |
47 | |
48 | // An unknown error. |
49 | qCDebug(KGLOBALACCEL_LOG) << "Failed to get dbus path for component " << componentUnique << reply.error(); |
50 | return nullptr; |
51 | } |
52 | |
53 | // Now get the component |
54 | org::kde::kglobalaccel::Component *component = |
55 | new org::kde::kglobalaccel::Component(QStringLiteral("org.kde.kglobalaccel" ), reply.value().path(), QDBusConnection::sessionBus(), q); |
56 | |
57 | // No component no cleaning |
58 | if (!component->isValid()) { |
59 | qCDebug(KGLOBALACCEL_LOG) << "Failed to get component" << componentUnique << QDBusConnection::sessionBus().lastError(); |
60 | return nullptr; |
61 | } |
62 | |
63 | if (remember) { |
64 | // Connect to the signals we are interested in. |
65 | q->connect(component, |
66 | &org::kde::kglobalaccel::Component::globalShortcutPressed, |
67 | q, |
68 | [this](const QString &componentUnique, const QString &shortcutUnique, qlonglong timestamp) { |
69 | invokeAction(componentUnique, shortcutUnique, timestamp); |
70 | }); |
71 | |
72 | q->connect(component, |
73 | &org::kde::kglobalaccel::Component::globalShortcutReleased, |
74 | q, |
75 | [this](const QString &componentUnique, const QString &shortcutUnique, qlonglong) { |
76 | invokeDeactivate(componentUnique, shortcutUnique); |
77 | }); |
78 | |
79 | components[componentUnique] = component; |
80 | } |
81 | |
82 | return component; |
83 | } |
84 | |
85 | namespace |
86 | { |
87 | QString serviceName() |
88 | { |
89 | return QStringLiteral("org.kde.kglobalaccel" ); |
90 | } |
91 | } |
92 | |
93 | void KGlobalAccelPrivate::cleanup() |
94 | { |
95 | qDeleteAll(components); |
96 | delete m_iface; |
97 | m_iface = nullptr; |
98 | delete m_watcher; |
99 | m_watcher = nullptr; |
100 | } |
101 | |
102 | KGlobalAccelPrivate::KGlobalAccelPrivate(KGlobalAccel *qq) |
103 | : q(qq) |
104 | { |
105 | m_watcher = new QDBusServiceWatcher(serviceName(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, q); |
106 | q->connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, q, [this](const QString &serviceName, const QString &oldOwner, const QString &newOwner) { |
107 | serviceOwnerChanged(serviceName, oldOwner, newOwner); |
108 | }); |
109 | } |
110 | |
111 | org::kde::KGlobalAccel *KGlobalAccelPrivate::iface() |
112 | { |
113 | if (!m_iface) { |
114 | m_iface = new org::kde::KGlobalAccel(serviceName(), QStringLiteral("/kglobalaccel" ), QDBusConnection::sessionBus()); |
115 | // Make sure kglobalaccel is running. The iface declaration above somehow works anyway. |
116 | QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); |
117 | if (bus && !bus->isServiceRegistered(serviceName())) { |
118 | QDBusReply<void> reply = bus->startService(serviceName()); |
119 | if (!reply.isValid()) { |
120 | qCritical() << "Couldn't start kglobalaccel from org.kde.kglobalaccel.service:" << reply.error(); |
121 | } |
122 | } |
123 | |
124 | q->connect(m_iface, &org::kde::KGlobalAccel::yourShortcutsChanged, q, [this](const QStringList &actionId, const QList<QKeySequence> &newKeys) { |
125 | shortcutsChanged(actionId, newKeys); |
126 | }); |
127 | } |
128 | return m_iface; |
129 | } |
130 | |
131 | KGlobalAccel::KGlobalAccel() |
132 | : d(new KGlobalAccelPrivate(this)) |
133 | { |
134 | qDBusRegisterMetaType<QList<int>>(); |
135 | qDBusRegisterMetaType<QKeySequence>(); |
136 | qDBusRegisterMetaType<QList<QKeySequence>>(); |
137 | qDBusRegisterMetaType<QList<QStringList>>(); |
138 | qDBusRegisterMetaType<KGlobalShortcutInfo>(); |
139 | qDBusRegisterMetaType<QList<KGlobalShortcutInfo>>(); |
140 | qDBusRegisterMetaType<KGlobalAccel::MatchType>(); |
141 | } |
142 | |
143 | KGlobalAccel::~KGlobalAccel() |
144 | { |
145 | delete d; |
146 | } |
147 | |
148 | // static |
149 | bool KGlobalAccel::cleanComponent(const QString &componentUnique) |
150 | { |
151 | org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); |
152 | if (!component) { |
153 | return false; |
154 | } |
155 | |
156 | return component->cleanUp(); |
157 | } |
158 | |
159 | // static |
160 | bool KGlobalAccel::isComponentActive(const QString &componentUnique) |
161 | { |
162 | org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); |
163 | if (!component) { |
164 | return false; |
165 | } |
166 | |
167 | return component->isActive(); |
168 | } |
169 | |
170 | org::kde::kglobalaccel::Component *KGlobalAccel::getComponent(const QString &componentUnique) |
171 | { |
172 | return d->getComponent(componentUnique); |
173 | } |
174 | |
175 | class KGlobalAccelSingleton |
176 | { |
177 | public: |
178 | KGlobalAccelSingleton(); |
179 | |
180 | KGlobalAccel instance; |
181 | }; |
182 | |
183 | Q_GLOBAL_STATIC(KGlobalAccelSingleton, s_instance) |
184 | |
185 | KGlobalAccelSingleton::KGlobalAccelSingleton() |
186 | { |
187 | qAddPostRoutine([]() { |
188 | s_instance->instance.d->cleanup(); |
189 | }); |
190 | } |
191 | |
192 | KGlobalAccel *KGlobalAccel::self() |
193 | { |
194 | return &s_instance()->instance; |
195 | } |
196 | |
197 | bool KGlobalAccelPrivate::doRegister(QAction *action) |
198 | { |
199 | if (!action || action->objectName().isEmpty() || action->objectName().startsWith(QLatin1String("unnamed-" ))) { |
200 | qWarning() << "Attempt to set global shortcut for action without objectName()." |
201 | " Read the setGlobalShortcut() documentation." ; |
202 | return false; |
203 | } |
204 | |
205 | const bool isRegistered = actions.contains(action); |
206 | if (isRegistered) { |
207 | return true; |
208 | } |
209 | |
210 | QStringList actionId = makeActionId(action); |
211 | |
212 | nameToAction.insert(actionId.at(KGlobalAccel::ActionUnique), action); |
213 | actions.insert(action); |
214 | iface()->doRegister(actionId); |
215 | |
216 | QObject::connect(action, &QObject::destroyed, [this, action](QObject *) { |
217 | if (actions.contains(action) && (actionShortcuts.contains(action) || actionDefaultShortcuts.contains(action))) { |
218 | remove(action, KGlobalAccelPrivate::SetInactive); |
219 | } |
220 | }); |
221 | |
222 | return true; |
223 | } |
224 | |
225 | void KGlobalAccelPrivate::remove(QAction *action, Removal removal) |
226 | { |
227 | if (!action || action->objectName().isEmpty()) { |
228 | return; |
229 | } |
230 | |
231 | const bool isRegistered = actions.contains(action); |
232 | if (!isRegistered) { |
233 | return; |
234 | } |
235 | |
236 | QStringList actionId = makeActionId(action); |
237 | |
238 | nameToAction.remove(actionId.at(KGlobalAccel::ActionUnique), action); |
239 | actions.remove(action); |
240 | |
241 | if (removal == UnRegister) { |
242 | // Complete removal of the shortcut is requested |
243 | // (forgetGlobalShortcut) |
244 | unregister(actionId); |
245 | } else { |
246 | // If the action is a configurationAction wen only remove it from our |
247 | // internal registry. That happened above. |
248 | |
249 | // If we are merely marking a callback as inactive there is nothing for kglobalaccel to do if kglobalaccel is not running |
250 | // this can happen on shutdown where all apps and kglobalaccel are all torn down at once |
251 | // For this reason we turn off the autostart flag in the DBus message call |
252 | |
253 | if (!action->property("isConfigurationAction" ).toBool()) { |
254 | // If it's a session shortcut unregister it. |
255 | if (action->objectName().startsWith(QLatin1String("_k_session:" ))) { |
256 | unregister(actionId); |
257 | } else { |
258 | setInactive(actionId); |
259 | } |
260 | } |
261 | } |
262 | |
263 | actionDefaultShortcuts.remove(action); |
264 | actionShortcuts.remove(action); |
265 | } |
266 | |
267 | void KGlobalAccelPrivate::unregister(const QStringList &actionId) |
268 | { |
269 | const auto component = actionId.at(KGlobalAccel::ComponentUnique); |
270 | const auto action = actionId.at(KGlobalAccel::ActionUnique); |
271 | |
272 | auto message = QDBusMessage::createMethodCall(iface()->service(), iface()->path(), iface()->interface(), QStringLiteral("unregister" )); |
273 | message.setArguments({component, action}); |
274 | message.setAutoStartService(false); |
275 | QDBusConnection::sessionBus().call(message); |
276 | } |
277 | |
278 | void KGlobalAccelPrivate::setInactive(const QStringList &actionId) |
279 | { |
280 | auto message = QDBusMessage::createMethodCall(iface()->service(), iface()->path(), iface()->interface(), QStringLiteral("setInactive" )); |
281 | message.setArguments({actionId}); |
282 | message.setAutoStartService(false); |
283 | QDBusConnection::sessionBus().call(message); |
284 | } |
285 | |
286 | void KGlobalAccelPrivate::updateGlobalShortcut(/*const would be better*/ QAction *action, |
287 | ShortcutTypes actionFlags, |
288 | KGlobalAccel::GlobalShortcutLoading globalFlags) |
289 | { |
290 | // No action or no objectname -> Do nothing |
291 | if (!action || action->objectName().isEmpty()) { |
292 | return; |
293 | } |
294 | |
295 | QStringList actionId = makeActionId(action); |
296 | |
297 | uint setterFlags = 0; |
298 | if (globalFlags & NoAutoloading) { |
299 | setterFlags |= NoAutoloading; |
300 | } |
301 | |
302 | if (actionFlags & ActiveShortcut) { |
303 | const QList<QKeySequence> activeShortcut = actionShortcuts.value(action); |
304 | bool isConfigurationAction = action->property("isConfigurationAction" ).toBool(); |
305 | uint activeSetterFlags = setterFlags; |
306 | |
307 | // setPresent tells kglobalaccel that the shortcut is active |
308 | if (!isConfigurationAction) { |
309 | activeSetterFlags |= SetPresent; |
310 | } |
311 | |
312 | // Sets the shortcut, returns the active/real keys |
313 | const auto result = iface()->setShortcutKeys(actionId, activeShortcut, activeSetterFlags); |
314 | |
315 | // Make sure we get informed about changes in the component by kglobalaccel |
316 | getComponent(componentUniqueForAction(action), true); |
317 | |
318 | // Create a shortcut from the result |
319 | const QList<QKeySequence> scResult(result); |
320 | |
321 | if (isConfigurationAction && (globalFlags & NoAutoloading)) { |
322 | // If this is a configuration action and we have set the shortcut, |
323 | // inform the real owner of the change. |
324 | // Note that setForeignShortcut will cause a signal to be sent to applications |
325 | // even if it did not "see" that the shortcut has changed. This is Good because |
326 | // at the time of comparison (now) the action *already has* the new shortcut. |
327 | // We called setShortcut(), remember? |
328 | // Also note that we will see our own signal so we may not need to call |
329 | // setActiveGlobalShortcutNoEnable - shortcutGotChanged() does it. |
330 | // In practice it's probably better to get the change propagated here without |
331 | // DBus delay as we do below. |
332 | iface()->setForeignShortcutKeys(actionId, result); |
333 | } |
334 | if (scResult != activeShortcut) { |
335 | // If kglobalaccel returned a shortcut that differs from the one we |
336 | // sent, use that one. There must have been clashes or some other problem. |
337 | actionShortcuts.insert(action, scResult); |
338 | Q_EMIT q->globalShortcutChanged(action, scResult.isEmpty() ? QKeySequence() : scResult.first()); |
339 | } |
340 | } |
341 | |
342 | if (actionFlags & DefaultShortcut) { |
343 | const QList<QKeySequence> defaultShortcut = actionDefaultShortcuts.value(action); |
344 | iface()->setShortcutKeys(actionId, defaultShortcut, setterFlags | IsDefault); |
345 | } |
346 | } |
347 | |
348 | QStringList KGlobalAccelPrivate::makeActionId(const QAction *action) |
349 | { |
350 | QStringList ret(componentUniqueForAction(action)); // Component Unique Id ( see actionIdFields ) |
351 | Q_ASSERT(!ret.at(KGlobalAccel::ComponentUnique).isEmpty()); |
352 | Q_ASSERT(!action->objectName().isEmpty()); |
353 | ret.append(action->objectName()); // Action Unique Name |
354 | ret.append(componentFriendlyForAction(action)); // Component Friendly name |
355 | const QString actionText = action->text().replace(QLatin1Char('&'), QStringLiteral("" )); |
356 | ret.append(actionText); // Action Friendly Name |
357 | return ret; |
358 | } |
359 | |
360 | QList<int> KGlobalAccelPrivate::intListFromShortcut(const QList<QKeySequence> &cut) |
361 | { |
362 | QList<int> ret; |
363 | for (const QKeySequence &sequence : cut) { |
364 | ret.append(sequence[0].toCombined()); |
365 | } |
366 | while (!ret.isEmpty() && ret.last() == 0) { |
367 | ret.removeLast(); |
368 | } |
369 | return ret; |
370 | } |
371 | |
372 | QList<QKeySequence> KGlobalAccelPrivate::shortcutFromIntList(const QList<int> &list) |
373 | { |
374 | QList<QKeySequence> ret; |
375 | ret.reserve(list.size()); |
376 | std::transform(list.begin(), list.end(), std::back_inserter(ret), [](int i) { |
377 | return QKeySequence(i); |
378 | }); |
379 | return ret; |
380 | } |
381 | |
382 | QString KGlobalAccelPrivate::componentUniqueForAction(const QAction *action) |
383 | { |
384 | if (!action->property("componentName" ).isValid()) { |
385 | return QCoreApplication::applicationName(); |
386 | } else { |
387 | return action->property("componentName" ).toString(); |
388 | } |
389 | } |
390 | |
391 | QString KGlobalAccelPrivate::componentFriendlyForAction(const QAction *action) |
392 | { |
393 | QString property = action->property("componentDisplayName" ).toString(); |
394 | if (!property.isEmpty()) { |
395 | return property; |
396 | } |
397 | if (!QGuiApplication::applicationDisplayName().isEmpty()) { |
398 | return QGuiApplication::applicationDisplayName(); |
399 | } |
400 | return QCoreApplication::applicationName(); |
401 | } |
402 | |
403 | #if HAVE_X11 |
404 | int timestampCompare(unsigned long time1_, unsigned long time2_) // like strcmp() |
405 | { |
406 | quint32 time1 = time1_; |
407 | quint32 time2 = time2_; |
408 | if (time1 == time2) { |
409 | return 0; |
410 | } |
411 | return quint32(time1 - time2) < 0x7fffffffU ? 1 : -1; // time1 > time2 -> 1, handle wrapping |
412 | } |
413 | #endif |
414 | |
415 | QAction *KGlobalAccelPrivate::findAction(const QString &componentUnique, const QString &actionUnique) |
416 | { |
417 | QAction *action = nullptr; |
418 | const QList<QAction *> candidates = nameToAction.values(actionUnique); |
419 | for (QAction *const a : candidates) { |
420 | if (componentUniqueForAction(a) == componentUnique) { |
421 | action = a; |
422 | } |
423 | } |
424 | |
425 | // We do not trigger if |
426 | // - there is no action |
427 | // - the action is not enabled |
428 | // - the action is an configuration action |
429 | if (!action || !action->isEnabled() || action->property("isConfigurationAction" ).toBool()) { |
430 | return nullptr; |
431 | } |
432 | return action; |
433 | } |
434 | |
435 | void KGlobalAccelPrivate::invokeAction(const QString &componentUnique, const QString &actionUnique, qlonglong timestamp) |
436 | { |
437 | QAction *action = findAction(componentUnique, actionUnique); |
438 | if (!action) { |
439 | return; |
440 | } |
441 | |
442 | #if HAVE_X11 |
443 | // Update this application's X timestamp if needed. |
444 | // TODO The 100%-correct solution should probably be handling this action |
445 | // in the proper place in relation to the X events queue in order to avoid |
446 | // the possibility of wrong ordering of user events. |
447 | if (QX11Info::isPlatformX11()) { |
448 | if (timestampCompare(timestamp, QX11Info::appTime()) > 0) { |
449 | QX11Info::setAppTime(timestamp); |
450 | } |
451 | if (timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { |
452 | QX11Info::setAppUserTime(timestamp); |
453 | } |
454 | } |
455 | #endif |
456 | action->setProperty("org.kde.kglobalaccel.activationTimestamp" , timestamp); |
457 | |
458 | if (m_lastActivatedAction != action) { |
459 | Q_EMIT q->globalShortcutActiveChanged(action, true); |
460 | m_lastActivatedAction = action; |
461 | } |
462 | action->trigger(); |
463 | } |
464 | |
465 | void KGlobalAccelPrivate::invokeDeactivate(const QString &componentUnique, const QString &actionUnique) |
466 | { |
467 | QAction *action = findAction(componentUnique, actionUnique); |
468 | if (!action) { |
469 | return; |
470 | } |
471 | |
472 | m_lastActivatedAction.clear(); |
473 | |
474 | Q_EMIT q->globalShortcutActiveChanged(action, false); |
475 | } |
476 | |
477 | void KGlobalAccelPrivate::shortcutsChanged(const QStringList &actionId, const QList<QKeySequence> &keys) |
478 | { |
479 | QAction *action = nameToAction.value(actionId.at(KGlobalAccel::ActionUnique)); |
480 | if (!action) { |
481 | return; |
482 | } |
483 | |
484 | actionShortcuts.insert(action, keys); |
485 | Q_EMIT q->globalShortcutChanged(action, keys.isEmpty() ? QKeySequence() : keys.first()); |
486 | } |
487 | |
488 | void KGlobalAccelPrivate::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) |
489 | { |
490 | Q_UNUSED(oldOwner); |
491 | if (name == QLatin1String("org.kde.kglobalaccel" ) && !newOwner.isEmpty()) { |
492 | // kglobalaccel was restarted |
493 | qCDebug(KGLOBALACCEL_LOG) << "detected kglobalaccel restarting, re-registering all shortcut keys" ; |
494 | reRegisterAll(); |
495 | } |
496 | } |
497 | |
498 | void KGlobalAccelPrivate::reRegisterAll() |
499 | { |
500 | // We clear all our data, assume that all data on the other side is clear too, |
501 | // and register each action as if it just was allowed to have global shortcuts. |
502 | // If the kded side still has the data it doesn't matter because of the |
503 | // autoloading mechanism. The worst case I can imagine is that an action's |
504 | // shortcut was changed but the kded side died before it got the message so |
505 | // autoloading will now assign an old shortcut to the action. Particularly |
506 | // picky apps might assert or misbehave. |
507 | const QSet<QAction *> allActions = actions; |
508 | nameToAction.clear(); |
509 | actions.clear(); |
510 | for (QAction *const action : allActions) { |
511 | if (doRegister(action)) { |
512 | updateGlobalShortcut(action, ActiveShortcut, KGlobalAccel::Autoloading); |
513 | } |
514 | } |
515 | } |
516 | |
517 | QList<KGlobalShortcutInfo> KGlobalAccel::globalShortcutsByKey(const QKeySequence &seq, MatchType type) |
518 | { |
519 | return self()->d->iface()->globalShortcutsByKey(seq, type); |
520 | } |
521 | |
522 | bool KGlobalAccel::isGlobalShortcutAvailable(const QKeySequence &seq, const QString &comp) |
523 | { |
524 | return self()->d->iface()->globalShortcutAvailable(seq, comp); |
525 | } |
526 | |
527 | // static |
528 | bool KGlobalAccel::promptStealShortcutSystemwide(QWidget *parent, const QList<KGlobalShortcutInfo> &shortcuts, const QKeySequence &seq) |
529 | { |
530 | if (shortcuts.isEmpty()) { |
531 | // Usage error. Just say no |
532 | return false; |
533 | } |
534 | |
535 | QString component = shortcuts[0].componentFriendlyName(); |
536 | |
537 | QString message; |
538 | if (shortcuts.size() == 1) { |
539 | message = tr("The '%1' key combination is registered by application %2 for action %3." ).arg(seq.toString(), component, shortcuts[0].friendlyName()); |
540 | } else { |
541 | QString actionList; |
542 | for (const KGlobalShortcutInfo &info : shortcuts) { |
543 | actionList += tr("In context '%1' for action '%2'\n" ).arg(info.contextFriendlyName(), info.friendlyName()); |
544 | } |
545 | message = tr("The '%1' key combination is registered by application %2.\n%3" ).arg(seq.toString(), component, actionList); |
546 | } |
547 | |
548 | QString title = tr("Conflict With Registered Global Shortcut" ); |
549 | |
550 | QMessageBox box(parent); |
551 | box.setWindowTitle(title); |
552 | box.setText(message); |
553 | box.addButton(QMessageBox::Ok)->setText(tr("Reassign" )); |
554 | box.addButton(QMessageBox::Cancel); |
555 | |
556 | return box.exec() == QMessageBox::Ok; |
557 | } |
558 | |
559 | // static |
560 | void KGlobalAccel::stealShortcutSystemwide(const QKeySequence &seq) |
561 | { |
562 | // get the shortcut, remove seq, and set the new shortcut |
563 | const QStringList actionId = self()->d->iface()->actionList(seq); |
564 | if (actionId.size() < 4) { // not a global shortcut |
565 | return; |
566 | } |
567 | QList<QKeySequence> sc = self()->d->iface()->shortcutKeys(actionId); |
568 | |
569 | for (int i = 0; i < sc.count(); i++) { |
570 | if (sc[i] == seq) { |
571 | sc[i] = QKeySequence(); |
572 | } |
573 | } |
574 | |
575 | self()->d->iface()->setForeignShortcutKeys(actionId, sc); |
576 | } |
577 | |
578 | bool checkGarbageKeycode(const QList<QKeySequence> &shortcut) |
579 | { |
580 | // protect against garbage keycode -1 that Qt sometimes produces for exotic keys; |
581 | // at the moment (~mid 2008) Multimedia PlayPause is one of those keys. |
582 | for (const QKeySequence &sequence : shortcut) { |
583 | for (int i = 0; i < 4; i++) { |
584 | if (sequence[i].toCombined() == -1) { |
585 | qWarning() << "Encountered garbage keycode (keycode = -1) in input, not doing anything." ; |
586 | return true; |
587 | } |
588 | } |
589 | } |
590 | return false; |
591 | } |
592 | |
593 | bool KGlobalAccel::setDefaultShortcut(QAction *action, const QList<QKeySequence> &shortcut, GlobalShortcutLoading loadFlag) |
594 | { |
595 | if (checkGarbageKeycode(shortcut)) { |
596 | return false; |
597 | } |
598 | |
599 | if (!d->doRegister(action)) { |
600 | return false; |
601 | } |
602 | |
603 | d->actionDefaultShortcuts.insert(action, shortcut); |
604 | d->updateGlobalShortcut(action, KGlobalAccelPrivate::DefaultShortcut, loadFlag); |
605 | return true; |
606 | } |
607 | |
608 | bool KGlobalAccel::setShortcut(QAction *action, const QList<QKeySequence> &shortcut, GlobalShortcutLoading loadFlag) |
609 | { |
610 | if (checkGarbageKeycode(shortcut)) { |
611 | return false; |
612 | } |
613 | |
614 | if (!d->doRegister(action)) { |
615 | return false; |
616 | } |
617 | |
618 | d->actionShortcuts.insert(action, shortcut); |
619 | d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, loadFlag); |
620 | return true; |
621 | } |
622 | |
623 | QList<QKeySequence> KGlobalAccel::defaultShortcut(const QAction *action) const |
624 | { |
625 | return d->actionDefaultShortcuts.value(action); |
626 | } |
627 | |
628 | QList<QKeySequence> KGlobalAccel::shortcut(const QAction *action) const |
629 | { |
630 | return d->actionShortcuts.value(action); |
631 | } |
632 | |
633 | QList<QKeySequence> KGlobalAccel::globalShortcut(const QString &componentName, const QString &actionId) const |
634 | { |
635 | // see also d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, KGlobalAccel::Autoloading); |
636 | |
637 | // how componentName and actionId map to QAction, e.g: |
638 | // action->setProperty("componentName", "kwin"); |
639 | // action->setObjectName("Kill Window"); |
640 | |
641 | const QList<QKeySequence> scResult = self()->d->iface()->shortcutKeys({componentName, actionId, QString(), QString()}); |
642 | return scResult; |
643 | } |
644 | |
645 | void KGlobalAccel::removeAllShortcuts(QAction *action) |
646 | { |
647 | d->remove(action, KGlobalAccelPrivate::UnRegister); |
648 | } |
649 | |
650 | bool KGlobalAccel::hasShortcut(const QAction *action) const |
651 | { |
652 | return d->actionShortcuts.contains(action) || d->actionDefaultShortcuts.contains(action); |
653 | } |
654 | |
655 | bool KGlobalAccel::setGlobalShortcut(QAction *action, const QList<QKeySequence> &shortcut) |
656 | { |
657 | KGlobalAccel *g = KGlobalAccel::self(); |
658 | return g->d->setShortcutWithDefault(action, shortcut, Autoloading); |
659 | } |
660 | |
661 | bool KGlobalAccel::setGlobalShortcut(QAction *action, const QKeySequence &shortcut) |
662 | { |
663 | return KGlobalAccel::setGlobalShortcut(action, QList<QKeySequence>() << shortcut); |
664 | } |
665 | |
666 | bool KGlobalAccelPrivate::setShortcutWithDefault(QAction *action, const QList<QKeySequence> &shortcut, KGlobalAccel::GlobalShortcutLoading loadFlag) |
667 | { |
668 | if (checkGarbageKeycode(shortcut)) { |
669 | return false; |
670 | } |
671 | |
672 | if (!doRegister(action)) { |
673 | return false; |
674 | } |
675 | |
676 | actionDefaultShortcuts.insert(action, shortcut); |
677 | actionShortcuts.insert(action, shortcut); |
678 | updateGlobalShortcut(action, KGlobalAccelPrivate::DefaultShortcut | KGlobalAccelPrivate::ActiveShortcut, loadFlag); |
679 | return true; |
680 | } |
681 | |
682 | QDBusArgument &operator<<(QDBusArgument &argument, const KGlobalAccel::MatchType &type) |
683 | { |
684 | argument.beginStructure(); |
685 | argument << static_cast<int>(type); |
686 | argument.endStructure(); |
687 | return argument; |
688 | } |
689 | |
690 | const QDBusArgument &operator>>(const QDBusArgument &argument, KGlobalAccel::MatchType &type) |
691 | { |
692 | argument.beginStructure(); |
693 | int arg; |
694 | argument >> arg; |
695 | type = static_cast<KGlobalAccel::MatchType>(arg); |
696 | argument.endStructure(); |
697 | return argument; |
698 | } |
699 | |
700 | #include "moc_kglobalaccel.cpp" |
701 | |