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