KGlobalAccel

kglobalacceld.cpp
1 /*
2  This file is part of the KDE libraries
3 
4  SPDX-FileCopyrightText: 2007 Andreas Hartmetz <[email protected]>
5  SPDX-FileCopyrightText: 2007 Michael Jansen <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "kglobalacceld.h"
11 
12 #include "component.h"
13 #include "globalshortcut.h"
14 #include "globalshortcutcontext.h"
15 #include "globalshortcutsregistry.h"
16 #include "kserviceactioncomponent.h"
17 #include "logging_p.h"
18 
19 #include <QDBusMetaType>
20 #include <QDBusObjectPath>
21 #include <QMetaMethod>
22 #include <QTimer>
23 
24 #include "kglobalaccel.h"
25 
26 struct KGlobalAccelDPrivate {
27  KGlobalAccelDPrivate(KGlobalAccelD *q)
28  : q(q)
29  {
30  }
31 
32  GlobalShortcut *findAction(const QStringList &actionId) const;
33 
34  /**
35  * Find the action @a shortcutUnique in @a componentUnique.
36  *
37  * @return the action or @c nullptr if doesn't exist
38  */
39  GlobalShortcut *findAction(const QString &componentUnique, const QString &shortcutUnique) const;
40 
41  GlobalShortcut *addAction(const QStringList &actionId);
42  KdeDGlobalAccel::Component *component(const QStringList &actionId) const;
43 
44  void splitComponent(QString &component, QString &context) const
45  {
46  context = QStringLiteral("default");
47  if (component.indexOf('|') != -1) {
48  QStringList tmp = component.split('|');
49  Q_ASSERT(tmp.size() == 2);
50  component = tmp.at(0);
51  context = tmp.at(1);
52  }
53  }
54 
55  //! Timer for delayed writing to kglobalshortcutsrc
56  QTimer writeoutTimer;
57 
58  //! Our holder
59  KGlobalAccelD *q;
60 };
61 
62 GlobalShortcut *KGlobalAccelDPrivate::findAction(const QStringList &actionId) const
63 {
64  // Check if actionId is valid
65  if (actionId.size() != 4) {
66  qCDebug(KGLOBALACCELD) << "Invalid! '" << actionId << "'";
67  return nullptr;
68  }
69 
70  return findAction(actionId.at(KGlobalAccel::ComponentUnique), actionId.at(KGlobalAccel::ActionUnique));
71 }
72 
73 GlobalShortcut *KGlobalAccelDPrivate::findAction(const QString &_componentUnique, const QString &shortcutUnique) const
74 {
75  QString componentUnique = _componentUnique;
76 
77  KdeDGlobalAccel::Component *component;
78  QString contextUnique;
79  if (componentUnique.indexOf('|') == -1) {
80  component = GlobalShortcutsRegistry::self()->getComponent(componentUnique);
81  if (component)
82  contextUnique = component->currentContext()->uniqueName();
83  } else {
84  splitComponent(componentUnique, contextUnique);
85  component = GlobalShortcutsRegistry::self()->getComponent(componentUnique);
86  }
87 
88  if (!component) {
89 #ifdef KDEDGLOBALACCEL_TRACE
90  qCDebug(KGLOBALACCELD) << componentUnique << "not found";
91 #endif
92  return nullptr;
93  }
94 
95  GlobalShortcut *shortcut = component->getShortcutByName(shortcutUnique, contextUnique);
96 
97 #ifdef KDEDGLOBALACCEL_TRACE
98  if (shortcut) {
99  qCDebug(KGLOBALACCELD) << componentUnique << contextUnique << shortcut->uniqueName();
100  } else {
101  qCDebug(KGLOBALACCELD) << "No match for" << shortcutUnique;
102  }
103 #endif
104  return shortcut;
105 }
106 
107 KdeDGlobalAccel::Component *KGlobalAccelDPrivate::component(const QStringList &actionId) const
108 {
109  // Get the component for the action. If we have none create a new one
110  KdeDGlobalAccel::Component *component = GlobalShortcutsRegistry::self()->getComponent(actionId.at(KGlobalAccel::ComponentUnique));
111  if (!component) {
112  if (actionId.at(KGlobalAccel::ComponentUnique).endsWith(QLatin1String(".desktop"))) {
115  GlobalShortcutsRegistry::self());
116  component->activateGlobalShortcutContext(QStringLiteral("default"));
117  static_cast<KdeDGlobalAccel::KServiceActionComponent *>(component)->loadFromService();
118  } else {
121  GlobalShortcutsRegistry::self());
122  }
123  Q_ASSERT(component);
124  }
125  return component;
126 }
127 
128 GlobalShortcut *KGlobalAccelDPrivate::addAction(const QStringList &actionId)
129 {
130  Q_ASSERT(actionId.size() >= 4);
131 
132  QString componentUnique = actionId.at(KGlobalAccel::ComponentUnique);
133 
134  QString contextUnique = QStringLiteral("default");
135 
136  if (componentUnique.indexOf('|') != -1) {
137  QStringList tmp = componentUnique.split('|');
138  Q_ASSERT(tmp.size() == 2);
139  componentUnique = tmp.at(0);
140  contextUnique = tmp.at(1);
141  }
142 
143  QStringList actionIdTmp = actionId;
144  actionIdTmp.replace(KGlobalAccel::ComponentUnique, componentUnique);
145 
146  // Create the component if necessary
147  KdeDGlobalAccel::Component *component = this->component(actionIdTmp);
148  Q_ASSERT(component);
149 
150  // Create the context if necessary
151  if (component->getShortcutContexts().count(contextUnique) == 0) {
152  component->createGlobalShortcutContext(contextUnique);
153  }
154 
155  Q_ASSERT(!component->getShortcutByName(componentUnique, contextUnique));
156 
157  return new GlobalShortcut(actionId.at(KGlobalAccel::ActionUnique), actionId.at(KGlobalAccel::ActionFriendly), component->shortcutContext(contextUnique));
158 }
159 
160 Q_DECLARE_METATYPE(QStringList)
161 
163  : QObject(parent)
164  , d(new KGlobalAccelDPrivate(this))
165 {
166 }
167 
168 bool KGlobalAccelD::init()
169 {
170  qDBusRegisterMetaType<QList<int>>();
171  qDBusRegisterMetaType<QList<QDBusObjectPath>>();
172  qDBusRegisterMetaType<QList<QStringList>>();
173  qDBusRegisterMetaType<QStringList>();
174  qDBusRegisterMetaType<KGlobalShortcutInfo>();
175  qDBusRegisterMetaType<QList<KGlobalShortcutInfo>>();
176 
177  GlobalShortcutsRegistry *reg = GlobalShortcutsRegistry::self();
178  Q_ASSERT(reg);
179 
180  d->writeoutTimer.setSingleShot(true);
181  connect(&d->writeoutTimer, &QTimer::timeout, reg, &GlobalShortcutsRegistry::writeSettings);
182 
183  if (!QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.kglobalaccel"))) {
184  qCWarning(KGLOBALACCELD) << "Failed to register service org.kde.kglobalaccel";
185  return false;
186  }
187 
188  if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/kglobalaccel"), this, QDBusConnection::ExportScriptableContents)) {
189  qCWarning(KGLOBALACCELD) << "Failed to register object kglobalaccel in org.kde.kglobalaccel";
190  return false;
191  }
192 
193  GlobalShortcutsRegistry::self()->setDBusPath(QDBusObjectPath("/"));
194  GlobalShortcutsRegistry::self()->loadSettings();
195 
196  return true;
197 }
198 
199 KGlobalAccelD::~KGlobalAccelD()
200 {
201  GlobalShortcutsRegistry *const reg = GlobalShortcutsRegistry::self();
202  if (d->writeoutTimer.isActive()) {
203  d->writeoutTimer.stop();
204  reg->writeSettings();
205  }
206  reg->deactivateShortcuts();
207  delete d;
208 }
209 
210 QList<QStringList> KGlobalAccelD::allMainComponents() const
211 {
212  QList<QStringList> ret;
213  QStringList emptyList;
214  emptyList.reserve(4);
215  for (int i = 0; i < 4; i++) {
216  emptyList.append(QString());
217  }
218 
219  const auto components = GlobalShortcutsRegistry::self()->allMainComponents();
220  ret.reserve(components.size() * 4);
221  for (const KdeDGlobalAccel::Component *component : components) {
222  QStringList actionId(emptyList);
223  actionId[KGlobalAccel::ComponentUnique] = component->uniqueName();
224  actionId[KGlobalAccel::ComponentFriendly] = component->friendlyName();
225  ret.append(actionId);
226  }
227 
228  return ret;
229 }
230 
231 QList<QStringList> KGlobalAccelD::allActionsForComponent(const QStringList &actionId) const
232 {
233  //### Would it be advantageous to sort the actions by unique name?
234  QList<QStringList> ret;
235 
236  KdeDGlobalAccel::Component *const component = GlobalShortcutsRegistry::self()->getComponent(actionId[KGlobalAccel::ComponentUnique]);
237  if (!component) {
238  return ret;
239  }
240 
241  QStringList partialId(actionId[KGlobalAccel::ComponentUnique]); // ComponentUnique
242  partialId.append(QString()); // ActionUnique
243  // Use our internal friendlyName, not the one passed in. We should have the latest data.
244  partialId.append(component->friendlyName()); // ComponentFriendly
245  partialId.append(QString()); // ActionFriendly
246 
247  const auto listShortcuts = component->allShortcuts();
248  for (const GlobalShortcut *const shortcut : listShortcuts) {
249  if (shortcut->isFresh()) {
250  // isFresh is only an intermediate state, not to be reported outside.
251  continue;
252  }
253  QStringList actionId(partialId);
254  actionId[KGlobalAccel::ActionUnique] = shortcut->uniqueName();
255  actionId[KGlobalAccel::ActionFriendly] = shortcut->friendlyName();
256  ret.append(actionId);
257  }
258  return ret;
259 }
260 
261 QStringList KGlobalAccelD::action(int key) const
262 {
263  GlobalShortcut *shortcut = GlobalShortcutsRegistry::self()->getShortcutByKey(key);
264  QStringList ret;
265  if (shortcut) {
266  ret.append(shortcut->context()->component()->uniqueName());
267  ret.append(shortcut->uniqueName());
268  ret.append(shortcut->context()->component()->friendlyName());
269  ret.append(shortcut->friendlyName());
270  }
271  return ret;
272 }
273 
274 void KGlobalAccelD::activateGlobalShortcutContext(const QString &component, const QString &uniqueName)
275 {
276  KdeDGlobalAccel::Component *const comp = GlobalShortcutsRegistry::self()->getComponent(component);
277  if (comp)
278  comp->activateGlobalShortcutContext(uniqueName);
279 }
280 
282 {
283  QList<QDBusObjectPath> allComp;
284 
285  const auto lstMainComponents = GlobalShortcutsRegistry::self()->allMainComponents();
286  for (const KdeDGlobalAccel::Component *component : lstMainComponents) {
287  allComp.append(component->dbusPath());
288  }
289 
290  return allComp;
291 }
292 
293 void KGlobalAccelD::blockGlobalShortcuts(bool block)
294 {
295 #ifdef KDEDGLOBALACCEL_TRACE
296  qCDebug(KGLOBALACCELD) << block;
297 #endif
298  block ? GlobalShortcutsRegistry::self()->deactivateShortcuts(true) : GlobalShortcutsRegistry::self()->activateShortcuts();
299 }
300 
301 QList<int> KGlobalAccelD::shortcut(const QStringList &action) const
302 {
303  GlobalShortcut *shortcut = d->findAction(action);
304  if (shortcut)
305  return shortcut->keys();
306  return QList<int>();
307 }
308 
309 QList<int> KGlobalAccelD::defaultShortcut(const QStringList &action) const
310 {
311  GlobalShortcut *shortcut = d->findAction(action);
312  if (shortcut)
313  return shortcut->defaultKeys();
314  return QList<int>();
315 }
316 
317 // This method just registers the action. Nothing else. Shortcut has to be set
318 // later.
319 void KGlobalAccelD::doRegister(const QStringList &actionId)
320 {
321 #ifdef KDEDGLOBALACCEL_TRACE
322  qCDebug(KGLOBALACCELD) << actionId;
323 #endif
324 
325  // Check because we would not want to add a action for an invalid
326  // actionId. findAction returns nullptr in that case.
327  if (actionId.size() < 4) {
328  return;
329  }
330 
331  GlobalShortcut *shortcut = d->findAction(actionId);
332  if (!shortcut) {
333  shortcut = d->addAction(actionId);
334  } else {
335  // a switch of locales is one common reason for a changing friendlyName
336  if ((!actionId[KGlobalAccel::ActionFriendly].isEmpty()) && shortcut->friendlyName() != actionId[KGlobalAccel::ActionFriendly]) {
337  shortcut->setFriendlyName(actionId[KGlobalAccel::ActionFriendly]);
338  scheduleWriteSettings();
339  }
340  if ((!actionId[KGlobalAccel::ComponentFriendly].isEmpty())
341  && shortcut->context()->component()->friendlyName() != actionId[KGlobalAccel::ComponentFriendly]) {
342  shortcut->context()->component()->setFriendlyName(actionId[KGlobalAccel::ComponentFriendly]);
343  scheduleWriteSettings();
344  }
345  }
346 }
347 
349 {
350 #ifdef KDEDGLOBALACCEL_TRACE
351  qCDebug(KGLOBALACCELD) << componentUnique;
352 #endif
353 
354  KdeDGlobalAccel::Component *component = GlobalShortcutsRegistry::self()->getComponent(componentUnique);
355 
356  if (component) {
357  return component->dbusPath();
358  } else {
359  sendErrorReply("org.kde.kglobalaccel.NoSuchComponent", QString("The component '%1' doesn't exist.").arg(componentUnique));
360  return QDBusObjectPath("/");
361  }
362 }
363 
365 {
366 #ifdef KDEDGLOBALACCEL_TRACE
367  qCDebug(KGLOBALACCELD) << key;
368 #endif
369  const QList<GlobalShortcut *> shortcuts = GlobalShortcutsRegistry::self()->getShortcutsByKey(key);
370 
372  for (const GlobalShortcut *sc : shortcuts) {
373 #ifdef KDEDGLOBALACCEL_TRACE
374  qCDebug(KGLOBALACCELD) << sc->context()->uniqueName() << sc->uniqueName();
375 #endif
376  rc.append(static_cast<KGlobalShortcutInfo>(*sc));
377  }
378 
379  return rc;
380 }
381 
382 bool KGlobalAccelD::isGlobalShortcutAvailable(int shortcut, const QString &component) const
383 {
384  QString realComponent = component;
385  QString context;
386  d->splitComponent(realComponent, context);
387  return GlobalShortcutsRegistry::self()->isShortcutAvailable(shortcut, realComponent, context);
388 }
389 
390 void KGlobalAccelD::setInactive(const QStringList &actionId)
391 {
392 #ifdef KDEDGLOBALACCEL_TRACE
393  qCDebug(KGLOBALACCELD) << actionId;
394 #endif
395 
396  GlobalShortcut *shortcut = d->findAction(actionId);
397  if (shortcut)
398  shortcut->setIsPresent(false);
399 }
400 
401 bool KGlobalAccelD::unregister(const QString &componentUnique, const QString &shortcutUnique)
402 {
403 #ifdef KDEDGLOBALACCEL_TRACE
404  qCDebug(KGLOBALACCELD) << componentUnique << shortcutUnique;
405 #endif
406 
407  // Stop grabbing the key
408  GlobalShortcut *shortcut = d->findAction(componentUnique, shortcutUnique);
409  if (shortcut) {
410  shortcut->unRegister();
411  scheduleWriteSettings();
412  }
413 
414  return shortcut;
415 }
416 
417 #if KGLOBALACCELPRIVATE_BUILD_DEPRECATED_SINCE(4, 3)
418 void KGlobalAccelD::unRegister(const QStringList &actionId)
419 {
420 #ifdef KDEDGLOBALACCEL_TRACE
421  qCDebug(KGLOBALACCELD) << actionId;
422 #endif
423 
424  // Stop grabbing the key
425  GlobalShortcut *shortcut = d->findAction(actionId);
426  if (shortcut) {
427  shortcut->unRegister();
428  scheduleWriteSettings();
429  }
430 }
431 #endif
432 
433 QList<int> KGlobalAccelD::setShortcut(const QStringList &actionId, const QList<int> &keys, uint flags)
434 {
435  // spare the DBus framework some work
436  const bool setPresent = (flags & SetPresent);
437  const bool isAutoloading = !(flags & NoAutoloading);
438  const bool isDefault = (flags & IsDefault);
439 
440  GlobalShortcut *shortcut = d->findAction(actionId);
441  if (!shortcut) {
442  return QList<int>();
443  }
444 
445  // default shortcuts cannot clash because they don't do anything
446  if (isDefault) {
447  if (shortcut->defaultKeys() != keys) {
448  shortcut->setDefaultKeys(keys);
449  scheduleWriteSettings();
450  }
451  return keys; // doesn't matter
452  }
453 
454  if (isAutoloading && !shortcut->isFresh()) {
455  // the trivial and common case - synchronize the action from our data
456  // and exit.
457  if (!shortcut->isPresent() && setPresent) {
458  shortcut->setIsPresent(true);
459  }
460  // We are finished here. Return the list of current active keys.
461  return shortcut->keys();
462  }
463 
464  // now we are actually changing the shortcut of the action
465  shortcut->setKeys(keys);
466 
467  if (setPresent) {
468  shortcut->setIsPresent(true);
469  }
470 
471  // maybe isFresh should really only be set if setPresent, but only two things should use !setPresent:
472  //- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state
473  //- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts
474  // which can never be fresh if created the usual way
475  shortcut->setIsFresh(false);
476 
477  scheduleWriteSettings();
478 
479  return shortcut->keys();
480 }
481 
482 void KGlobalAccelD::setForeignShortcut(const QStringList &actionId, const QList<int> &keys)
483 {
484 #ifdef KDEDGLOBALACCEL_TRACE
485  qCDebug(KGLOBALACCELD) << actionId;
486 #endif
487 
488  GlobalShortcut *shortcut = d->findAction(actionId);
489  if (!shortcut)
490  return;
491 
492  QList<int> newKeys = setShortcut(actionId, keys, NoAutoloading);
493 
494  Q_EMIT yourShortcutGotChanged(actionId, newKeys);
495 }
496 
497 void KGlobalAccelD::scheduleWriteSettings() const
498 {
499  if (!d->writeoutTimer.isActive())
500  d->writeoutTimer.start(500);
501 }
502 
503 #include "moc_kglobalacceld.cpp"
QList< GlobalShortcut * > getShortcutsByKey(int key) const
Get the shortcuts corresponding to key.
bool isFresh() const
Check if the shortcut is fresh/new. Is an internal state.
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
Q_SCRIPTABLE QList< QDBusObjectPath > allComponents() const
Get the dbus path for all known components.
void deactivateShortcuts(bool temporarily=false)
Deactivate all currently active shortcuts.
void reserve(int alloc)
const T & at(int i) const const
void setFriendlyName(const QString &)
Sets the human readable name for this component.
Definition: component.cpp:332
GlobalShortcut * getShortcutByName(const QString &uniqueName, const QString &context=QStringLiteral("default")) const
Returns the shortcut by unique name.
Definition: component.cpp:243
bool isPresent() const
Check if the shortcut is present. It application is running.
QDBusConnection sessionBus()
GlobalShortcutContext * context()
Returns the context the shortcuts belongs to.
bool isShortcutAvailable(int shortcut, const QString &component, const QString &context) const
Checks if shortcut is available for component.
QList< KdeDGlobalAccel::Component * > allMainComponents() const
Return a list of all main components.
GlobalShortcutContext * shortcutContext(const QString &name)
Returns the shortcut context name or nullptr.
Definition: component.cpp:337
Components Unique Name (ID)
Definition: kglobalaccel.h:54
int size() const const
QString uniqueName() const
Returns the unique name aka id for the shortcuts.
Global Shortcut Registry.
void timeout()
QDBusObjectPath dbusPath() const
Return uniqueName converted to a valid dbus path.
Definition: component.cpp:162
int count(const T &value) const const
void append(const T &value)
void setFriendlyName(const QString &)
Sets the friendly name for the shortcut. For display.
void unRegister()
Remove this shortcut and it&#39;s siblings.
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
Q_SCRIPTABLE QList< KGlobalShortcutInfo > getGlobalShortcutsByKey(int key) const
Returns the shortcuts registered for key.
QList< GlobalShortcut * > allShortcuts(const QString &context=QStringLiteral("default")) const
Returns all shortcuts in context .
Definition: component.cpp:106
Q_SCRIPTABLE bool isGlobalShortcutAvailable(int key, const QString &component) const
Return true if the shortcut is available for component.
Q_SCRIPTABLE QDBusObjectPath getComponent(const QString &componentUnique) const
Get the dbus path for @ componentUnique.
Represents a global shortcut.
Actions Unique Name(ID)
Definition: kglobalaccel.h:55
void activateShortcuts()
Activate all shortcuts having their application present.
QList< int > keys() const
Returns a list of keys associated with this shortcut.
GlobalShortcutContext * currentContext()
Return the current context.
Definition: component.cpp:157
bool createGlobalShortcutContext(const QString &context, const QString &friendlyName=QString())
Creates the new global shortcut context context.
Definition: component.cpp:147
Actions Friendly Translated Name.
Definition: kglobalaccel.h:57
QList< int > defaultKeys() const
Returns the default keys for this shortcut.
Q_SCRIPTABLE QStringList getShortcutContexts() const
Returns the shortcut contexts available for the component.
Definition: component.cpp:252
GlobalShortcut * getShortcutByKey(int key) const
Get the shortcut corresponding to key.
void setKeys(const QList< int >)
Sets the keys activated with this shortcut. The old keys are freed.
const QList< QKeySequence > & shortcut(StandardShortcut id)
void setDefaultKeys(const QList< int >)
Sets the default keys for this shortcut.
Components Friendly Translated Name.
Definition: kglobalaccel.h:56
QString uniqueName() const
Get the name for the context.
QString friendlyName() const
Return the friendly display name for this shortcut.
void replace(int i, const T &value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun May 16 2021 22:53:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.