KGlobalAccel

component.cpp
1 /*
2  SPDX-FileCopyrightText: 2008 Michael Jansen <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "component.h"
8 
9 #include "globalshortcutcontext.h"
10 #include "globalshortcutsregistry.h"
11 #include "kglobalaccel_interface.h"
12 #include "logging_p.h"
13 #include <config-kglobalaccel.h>
14 
15 #include <QKeySequence>
16 #include <QStringList>
17 
18 #if HAVE_X11
19 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
20 #include <private/qtx11extras_p.h>
21 #else
22 #include <QX11Info>
23 #endif
24 #endif
25 
26 static QList<QKeySequence> keysFromString(const QString &str)
27 {
29  if (str == QLatin1String("none")) {
30  return ret;
31  }
32  const QStringList strList = str.split(QLatin1Char('\t'));
33  for (const QString &s : strList) {
35  if (!key.isEmpty()) { // sanity check just in case
36  ret.append(key);
37  }
38  }
39  return ret;
40 }
41 
42 static QString stringFromKeys(const QList<QKeySequence> &keys)
43 {
44  if (keys.isEmpty()) {
45  return QStringLiteral("none");
46  }
47  QString ret;
48  for (const QKeySequence &key : keys) {
50  ret.append(QLatin1Char('\t'));
51  }
52  ret.chop(1);
53  return ret;
54 }
55 
56 Component::Component(const QString &uniqueName, const QString &friendlyName)
57  : _uniqueName(uniqueName)
58  , _friendlyName(friendlyName)
59  , _registry(GlobalShortcutsRegistry::self())
60 {
61  // Make sure we do no get uniquenames still containing the context
62  Q_ASSERT(uniqueName.indexOf(QLatin1Char('|')) == -1);
63 
64  const QString DEFAULT(QStringLiteral("default"));
65  createGlobalShortcutContext(DEFAULT, QStringLiteral("Default Context"));
66  _current = _contexts.value(DEFAULT);
67 }
68 
69 Component::~Component()
70 {
71  // We delete all shortcuts from all contexts
72  qDeleteAll(_contexts);
73 }
74 
75 bool Component::activateGlobalShortcutContext(const QString &uniqueName)
76 {
77  if (!_contexts.value(uniqueName)) {
78  createGlobalShortcutContext(uniqueName, QStringLiteral("TODO4"));
79  return false;
80  }
81 
82  // Deactivate the current contexts shortcuts
84 
85  // Switch the context
86  _current = _contexts.value(uniqueName);
87 
88  return true;
89 }
90 
91 void Component::activateShortcuts()
92 {
93  for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) {
94  shortcut->setActive();
95  }
96 }
97 
99 {
100  GlobalShortcutContext *context = _contexts.value(contextName);
101  return context ? context->_actionsMap.values() : QList<GlobalShortcut *>{};
102 }
103 
105 {
106  GlobalShortcutContext *context = _contexts.value(contextName);
107  return context ? context->allShortcutInfos() : QList<KGlobalShortcutInfo>{};
108 }
109 
111 {
112  bool changed = false;
113 
114  const auto actions = _current->_actionsMap;
115  for (GlobalShortcut *shortcut : actions) {
116  qCDebug(KGLOBALACCELD) << _current->_actionsMap.size();
117  if (!shortcut->isPresent()) {
118  changed = true;
119  shortcut->unRegister();
120  }
121  }
122 
123  if (changed) {
124  _registry->writeSettings();
125  // We could be destroyed after this call!
126  }
127 
128  return changed;
129 }
130 
131 bool Component::createGlobalShortcutContext(const QString &uniqueName, const QString &friendlyName)
132 {
133  if (_contexts.value(uniqueName)) {
134  qCDebug(KGLOBALACCELD) << "Shortcut Context " << uniqueName << "already exists for component " << _uniqueName;
135  return false;
136  }
137  _contexts.insert(uniqueName, new GlobalShortcutContext(uniqueName, friendlyName, this));
138  return true;
139 }
140 
142 {
143  return _current;
144 }
145 
147 {
148  auto isNonAscii = [](QChar ch) {
149  const char c = ch.unicode();
150  const bool isAscii = c == '_' //
151  || (c >= 'A' && c <= 'Z') //
152  || (c >= 'a' && c <= 'z') //
153  || (c >= '0' && c <= '9');
154  return !isAscii;
155  };
156 
157  QString dbusPath = _uniqueName;
158  // DBus path can only contain ASCII characters, any non-alphanumeric char should
159  // be turned into '_'
160  std::replace_if(dbusPath.begin(), dbusPath.end(), isNonAscii, QLatin1Char('_'));
161 
162  // QDBusObjectPath could be a little bit easier to handle :-)
163  return QDBusObjectPath(_registry->dbusPath().path() + QLatin1String("component/") + dbusPath);
164 }
165 
166 void Component::deactivateShortcuts(bool temporarily)
167 {
168  for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) {
169  if (temporarily //
170  && _uniqueName == QLatin1String("kwin") //
171  && shortcut->uniqueName() == QLatin1String("Block Global Shortcuts")) {
172  continue;
173  }
174  shortcut->setInactive();
175  }
176 }
177 
178 void Component::emitGlobalShortcutPressed(const GlobalShortcut &shortcut)
179 {
180 #if HAVE_X11
181  // pass X11 timestamp
182  const long timestamp = QX11Info::appTime();
183  // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
184  // keypress, otherwise actions in application that try to grab the
185  // keyboard (e.g. in kwin) may fail to do so. There is still a small race
186  // condition with this being out-of-process.
187  if (_registry->_manager) {
188  _registry->_manager->syncWindowingSystem();
189  }
190 #else
191  const long timestamp = 0;
192 #endif
193 
194  if (shortcut.context()->component() != this) {
195  return;
196  }
197 
198  Q_EMIT globalShortcutPressed(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp);
199 }
200 
201 void Component::emitGlobalShortcutReleased(const GlobalShortcut &shortcut)
202 {
203 #if HAVE_X11
204  // pass X11 timestamp
205  const long timestamp = QX11Info::appTime();
206  // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
207  // keypress, otherwise actions in application that try to grab the
208  // keyboard (e.g. in kwin) may fail to do so. There is still a small race
209  // condition with this being out-of-process.
210  if (_registry->_manager) {
211  _registry->_manager->syncWindowingSystem();
212  }
213 #else
214  const long timestamp = 0;
215 #endif
216 
217  if (shortcut.context()->component() != this) {
218  return;
219  }
220 
221  Q_EMIT globalShortcutReleased(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp);
222 }
223 
224 void Component::invokeShortcut(const QString &shortcutName, const QString &context)
225 {
226  GlobalShortcut *shortcut = getShortcutByName(shortcutName, context);
227  if (shortcut) {
228  emitGlobalShortcutPressed(*shortcut);
229  }
230 }
231 
232 QString Component::friendlyName() const
233 {
234  return !_friendlyName.isEmpty() ? _friendlyName : _uniqueName;
235 }
236 
238 {
239  return _current->getShortcutByKey(key, type);
240 }
241 
243 {
245  for (GlobalShortcutContext *context : std::as_const(_contexts)) {
246  GlobalShortcut *sc = context->getShortcutByKey(key, type);
247  if (sc) {
248  rc.append(sc);
249  }
250  }
251  return rc;
252 }
253 
254 GlobalShortcut *Component::getShortcutByName(const QString &uniqueName, const QString &context) const
255 {
256  const GlobalShortcutContext *shortcutContext = _contexts.value(context);
257  return shortcutContext ? shortcutContext->_actionsMap.value(uniqueName) : nullptr;
258 }
259 
261 {
262  return _contexts.keys();
263 }
264 
266 {
267  // The component is active if at least one of it's global shortcuts is
268  // present.
269  for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) {
270  if (shortcut->isPresent()) {
271  return true;
272  }
273  }
274  return false;
275 }
276 
277 bool Component::isShortcutAvailable(const QKeySequence &key, const QString &component, const QString &context) const
278 {
279  qCDebug(KGLOBALACCELD) << key.toString() << component;
280 
281  // if this component asks for the key. only check the keys in the same
282  // context
283  if (component == uniqueName()) {
284  return shortcutContext(context)->isShortcutAvailable(key);
285  } else {
286  for (auto it = _contexts.cbegin(), endIt = _contexts.cend(); it != endIt; ++it) {
287  const GlobalShortcutContext *ctx = it.value();
288  if (!ctx->isShortcutAvailable(key)) {
289  return false;
290  }
291  }
292  }
293  return true;
294 }
295 
297 Component::registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString)
298 {
299  // The shortcut will register itself with us
300  GlobalShortcut *shortcut = new GlobalShortcut(uniqueName, friendlyName, currentContext());
301 
302  const QList<QKeySequence> keys = keysFromString(shortcutString);
303  shortcut->setDefaultKeys(keysFromString(defaultShortcutString));
304  shortcut->setIsFresh(false);
305  QList<QKeySequence> newKeys = keys;
306  for (const QKeySequence &key : keys) {
307  if (!key.isEmpty()) {
308  if (_registry->getShortcutByKey(key)) {
309  // The shortcut is already used. The config file is
310  // broken. Ignore the request.
311  newKeys.removeAll(key);
312  qCWarning(KGLOBALACCELD) << "Shortcut found twice in kglobalshortcutsrc." << key;
313  }
314  }
315  }
316  shortcut->setKeys(keys);
317  return shortcut;
318 }
319 
321 {
322  // GlobalShortcutsRegistry::loadSettings handles contexts.
323  const auto listKeys = configGroup.keyList();
324  for (const QString &confKey : listKeys) {
325  const QStringList entry = configGroup.readEntry(confKey, QStringList());
326  if (entry.size() != 3) {
327  continue;
328  }
329 
330  GlobalShortcut *shortcut = registerShortcut(confKey, entry[2], entry[0], entry[1]);
331  if (configGroup.name().endsWith(QLatin1String(".desktop"))) {
332  shortcut->setIsPresent(true);
333  }
334  }
335 }
336 
338 {
339  _friendlyName = name;
340 }
341 
343 {
344  return _contexts.value(contextName);
345 }
346 
347 GlobalShortcutContext const *Component::shortcutContext(const QString &contextName) const
348 {
349  return _contexts.value(contextName);
350 }
351 
353 {
354  const GlobalShortcutContext *context = _contexts.value(contextName);
355  return context ? context->_actionsMap.keys() : QStringList{};
356 }
357 
358 QString Component::uniqueName() const
359 {
360  return _uniqueName;
361 }
362 
363 void Component::unregisterShortcut(const QString &uniqueName)
364 {
365  // Now wrote all contexts
366  for (GlobalShortcutContext *context : std::as_const(_contexts)) {
367  if (context->_actionsMap.value(uniqueName)) {
368  delete context->takeShortcut(context->_actionsMap.value(uniqueName));
369  }
370  }
371 }
372 
373 void Component::writeSettings(KConfigGroup &configGroup) const
374 {
375  // If we don't delete the current content global shortcut
376  // registrations will never not deleted after forgetGlobalShortcut()
377  configGroup.deleteGroup();
378 
379  // Now write all contexts
380  for (GlobalShortcutContext *context : std::as_const(_contexts)) {
381  KConfigGroup contextGroup;
382 
383  if (context->uniqueName() == QLatin1String("default")) {
384  contextGroup = configGroup;
385  // Write the friendly name
386  contextGroup.writeEntry("_k_friendly_name", friendlyName());
387  } else {
388  contextGroup = KConfigGroup(&configGroup, context->uniqueName());
389  // Write the friendly name
390  contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
391  }
392 
393  // qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName();
394 
395  for (const GlobalShortcut *shortcut : std::as_const(context->_actionsMap)) {
396  // qCDebug(KGLOBALACCELD) << "writing" << shortcut->uniqueName();
397 
398  // We do not write fresh shortcuts.
399  // We do not write session shortcuts
400  if (shortcut->isFresh() || shortcut->isSessionShortcut()) {
401  continue;
402  }
403  // qCDebug(KGLOBALACCELD) << "really writing" << shortcut->uniqueName();
404 
405  QStringList entry(stringFromKeys(shortcut->keys()));
406  entry.append(stringFromKeys(shortcut->defaultKeys()));
407  entry.append(shortcut->friendlyName());
408 
409  contextGroup.writeEntry(shortcut->uniqueName(), entry);
410  }
411  }
412 }
void append(const T &value)
bool isShortcutAvailable(const QKeySequence &key, const QString &component, const QString &context) const
Check if key is available for component component.
Definition: component.cpp:277
virtual Q_SCRIPTABLE bool cleanUp()
Remove all currently not used global shortcuts registrations for this component and if nothing is lef...
Definition: component.cpp:110
QString readEntry(const char *key, const char *aDefault=nullptr) const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
const T value(const Key &key) const const
Q_SCRIPTABLE QStringList shortcutNames(const QString &context=QStringLiteral("default")) const
Get all shortcutnames living in context.
Definition: component.cpp:352
Q_SCRIPTABLE bool isActive() const
Check if the component is currently active.
Definition: component.cpp:265
const QList< QKeySequence > & shortcut(StandardShortcut id)
virtual void syncWindowingSystem()
Allows implementing plugins to synchronize with the windowing system.
GlobalShortcutContext * shortcutContext(const QString &name)
Returns the shortcut context name or nullptr.
Definition: component.cpp:342
GlobalShortcut * getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type=KGlobalAccel::MatchType::Equal) const
Get the shortcut corresponding to key.
Q_EMITQ_EMIT
int removeAll(const T &value)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
Q_SCRIPTABLE void globalShortcutPressed(const QString &componentUnique, const QString &shortcutUnique, qlonglong timestamp)
Signals that a action for this component was triggered.
QList< Key > keys() const const
QList< GlobalShortcut * > getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
Returns the list of shortcuts (different context) registered with key.
Definition: component.cpp:242
Q_SCRIPTABLE QStringList getShortcutContexts() const
Returns the shortcut contexts available for the component.
Definition: component.cpp:260
void chop(int n)
QDBusObjectPath dbusPath() const
Return uniqueName converted to a valid dbus path.
Definition: component.cpp:146
void deleteGroup(const char *group, WriteConfigFlags flags=Normal)
QList< GlobalShortcut * > allShortcuts(const QString &context=QStringLiteral("default")) const
Returns all shortcuts in context @context.
Definition: component.cpp:98
Represents a global shortcut.
QHash::const_iterator cend() const const
GlobalShortcutContext * currentContext()
Return the current context.
Definition: component.cpp:141
QHash::iterator insert(const Key &key, const T &value)
void deactivateShortcuts(bool temporarily=false)
Deactivate all currently active shortcuts.
Definition: component.cpp:166
int size() const const
int size() const const
void unregisterShortcut(const QString &uniqueName)
Unregister shortcut. This will remove its siblings from all contexts.
Definition: component.cpp:363
bool isEmpty() const const
QString toString(QKeySequence::SequenceFormat format) const const
bool isEmpty() const const
bool createGlobalShortcutContext(const QString &context, const QString &friendlyName=QString())
Creates the new global shortcut context context.
Definition: component.cpp:131
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
Global Shortcut Registry.
QList< KGlobalShortcutInfo > allShortcutInfos() const
Return KGlobalShortcutInfos for all shortcuts.
QString path() const const
QKeySequence fromString(const QString &str, QKeySequence::SequenceFormat format)
QDBusObjectPath dbusPath() const
Return the root dbus path for the registry.
void loadSettings(KConfigGroup &config)
Load the settings from config group config.
Definition: component.cpp:320
QList< T > values() const const
QStringList keyList() const
GlobalShortcut * getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
Get shortcut for key or nullptr.
GlobalShortcut * getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
Returns the currently active shortcut for key.
Definition: component.cpp:237
Q_SCRIPTABLE void globalShortcutReleased(const QString &componentUnique, const QString &shortcutUnique, qlonglong timestamp)
Signals that a action for this component is not triggered anymore.
Component(const QString &uniqueName, const QString &friendlyName)
Constructs a component.
Definition: component.cpp:56
void setFriendlyName(const QString &)
Sets the human readable name for this component.
Definition: component.cpp:337
GlobalShortcut * getShortcutByName(const QString &uniqueName, const QString &context=QStringLiteral("default")) const
Returns the shortcut by unique name.
Definition: component.cpp:254
QString name() const
QHash::const_iterator cbegin() const const
GlobalShortcut * registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString)
Create a new globalShortcut by its name.
Definition: component.cpp:297
Q_SCRIPTABLE QList< KGlobalShortcutInfo > allShortcutInfos(const QString &context=QStringLiteral("default")) const
Returns all shortcut in context.
Definition: component.cpp:104
bool isEmpty() const const
MatchType
Keysequence match semantics.
Definition: kglobalaccel.h:71
QString & append(QChar ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Mar 26 2023 03:54:51 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.