KGlobalAccel

globalshortcutsregistry.cpp
1 /*
2  SPDX-FileCopyrightText: 2008 Michael Jansen <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "globalshortcutsregistry.h"
8 #include "component.h"
9 #include "globalshortcut.h"
10 #include "globalshortcutcontext.h"
11 #include "kglobalaccel_interface.h"
12 #include "kserviceactioncomponent.h"
13 #include "logging_p.h"
14 #include <config-kglobalaccel.h>
15 
16 #include <KDesktopFile>
17 #include <KPluginLoader>
18 #include <KPluginMetaData>
19 #include <QDir>
20 #include <QGuiApplication>
21 #include <QJsonArray>
22 #include <QStandardPaths>
23 
24 #include <QDBusConnection>
25 #include <QKeySequence>
26 
27 static bool checkPlatform(const QJsonObject &metadata, const QString &platformName)
28 {
29  const QJsonArray platforms = metadata.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("platforms")).toArray();
30  return std::any_of(platforms.begin(), platforms.end(), [&platformName](const QJsonValue &value) {
31  return QString::compare(platformName, value.toString(), Qt::CaseInsensitive) == 0;
32  });
33 }
34 
36 {
37  QString platformName = QString::fromLocal8Bit(qgetenv("KGLOBALACCELD_PLATFORM"));
38  if (platformName.isEmpty()) {
39  platformName = QGuiApplication::platformName();
40  }
41 
43  for (const QStaticPlugin &staticPlugin : staticPlugins) {
44  const QJsonObject metadata = staticPlugin.metaData();
45  if (metadata.value(QLatin1String("IID")) != QLatin1String(KGlobalAccelInterface_iid)) {
46  continue;
47  }
48  if (checkPlatform(metadata, platformName)) {
49  KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(staticPlugin.instance());
50  if (interface) {
51  qCDebug(KGLOBALACCELD) << "Loaded a static plugin for platform" << platformName;
52  interface->setRegistry(parent);
53  return interface;
54  }
55  }
56  }
57 
58  const QVector<KPluginMetaData> candidates = KPluginLoader::findPlugins(QStringLiteral("org.kde.kglobalaccel5.platforms"));
59  for (const KPluginMetaData &candidate : candidates) {
60  QPluginLoader loader(candidate.fileName());
61  if (checkPlatform(loader.metaData(), platformName)) {
62  KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(loader.instance());
63  if (interface) {
64  qCDebug(KGLOBALACCELD) << "Loaded plugin" << candidate.fileName() << "for platform" << platformName;
65  interface->setRegistry(parent);
66  return interface;
67  }
68  }
69  }
70 
71  qCWarning(KGLOBALACCELD) << "Could not find any platform plugin";
72  return nullptr;
73 }
74 
76  : QObject()
77  , _active_keys()
78  , _components()
79  , _manager(loadPlugin(this))
80  , _config(qEnvironmentVariableIsSet("KGLOBALACCEL_TEST_MODE") ? QString() : QStringLiteral("kglobalshortcutsrc"), KConfig::SimpleConfig)
81 {
82  if (_manager) {
83  _manager->setEnabled(true);
84  }
85 }
86 
87 GlobalShortcutsRegistry::~GlobalShortcutsRegistry()
88 {
89  if (_manager) {
90  _manager->setEnabled(false);
91 
92  // Ungrab all keys. We don't go over GlobalShortcuts because
93  // GlobalShortcutsRegistry::self() doesn't work anymore.
94  const auto listKeys = _active_keys.keys();
95  for (const int key : listKeys) {
96  _manager->grabKey(key, false);
97  }
98  }
99  _active_keys.clear();
100 }
101 
102 KdeDGlobalAccel::Component *GlobalShortcutsRegistry::addComponent(KdeDGlobalAccel::Component *component)
103 {
104  if (_components.value(component->uniqueName())) {
105  Q_ASSERT_X(false, "GlobalShortcutsRegistry::addComponent", "component already registered?!?!");
106  return _components.value(component->uniqueName());
107  }
108 
109  _components.insert(component->uniqueName(), component);
111 
113  return component;
114 }
115 
117 {
118  for (KdeDGlobalAccel::Component *component : qAsConst(_components)) {
119  component->activateShortcuts();
120  }
121 }
122 
124 {
125  return _components.values();
126 }
127 
128 void GlobalShortcutsRegistry::clear()
129 {
130  for (KdeDGlobalAccel::Component *component : qAsConst(_components)) {
131  delete component;
132  }
133  _components.clear();
134 
135  // The shortcuts should have deregistered themselves
136  Q_ASSERT(_active_keys.isEmpty());
137 }
138 
140 {
141  return _dbusPath;
142 }
143 
145 {
146  for (KdeDGlobalAccel::Component *component : qAsConst(_components)) {
147  component->deactivateShortcuts(temporarily);
148  }
149 }
150 
152 {
153  return _active_keys.value(key);
154 }
155 
156 KdeDGlobalAccel::Component *GlobalShortcutsRegistry::getComponent(const QString &uniqueName)
157 {
158  return _components.value(uniqueName);
159 }
160 
162 {
163  for (KdeDGlobalAccel::Component *component : qAsConst(_components)) {
164  GlobalShortcut *rc = component->getShortcutByKey(key);
165  if (rc)
166  return rc;
167  }
168  return nullptr;
169 }
170 
172 {
174 
175  for (KdeDGlobalAccel::Component *component : qAsConst(_components)) {
176  rc = component->getShortcutsByKey(key);
177  if (!rc.isEmpty())
178  return rc;
179  }
180  return rc;
181 }
182 
183 bool GlobalShortcutsRegistry::isShortcutAvailable(int shortcut, const QString &componentName, const QString &contextName) const
184 {
185  for (KdeDGlobalAccel::Component *component : qAsConst(_components)) {
186  if (!component->isShortcutAvailable(shortcut, componentName, contextName))
187  return false;
188  }
189  return true;
190 }
191 
192 Q_GLOBAL_STATIC(GlobalShortcutsRegistry, _self)
193 GlobalShortcutsRegistry *GlobalShortcutsRegistry::self()
194 {
195  return _self;
196 }
197 
198 bool GlobalShortcutsRegistry::keyPressed(int keyQt)
199 {
200  GlobalShortcut *shortcut = getShortcutByKey(keyQt);
201  if (!shortcut) {
202  // This can happen for example with the ALT-Print shortcut of kwin.
203  // ALT+PRINT is SYSREQ on my keyboard. So we grab something we think
204  // is ALT+PRINT but symXToKeyQt and modXToQt make ALT+SYSREQ of it
205  // when pressed (correctly). We can't match that.
206 #ifdef KDEDGLOBALACCEL_TRACE
207  qCDebug(KGLOBALACCELD) << "Got unknown key" << QKeySequence(keyQt).toString();
208 #endif
209 
210  // In production mode just do nothing.
211  return false;
212  } else if (!shortcut->isActive()) {
213 #ifdef KDEDGLOBALACCEL_TRACE
214  qCDebug(KGLOBALACCELD) << "Got inactive key" << QKeySequence(keyQt).toString();
215 #endif
216 
217  // In production mode just do nothing.
218  return false;
219  }
220 
221 #ifdef KDEDGLOBALACCEL_TRACE
222  qCDebug(KGLOBALACCELD) << QKeySequence(keyQt).toString() << "=" << shortcut->uniqueName();
223 #endif
224 
225  QStringList data(shortcut->context()->component()->uniqueName());
226  data.append(shortcut->uniqueName());
227  data.append(shortcut->context()->component()->friendlyName());
228  data.append(shortcut->friendlyName());
229 
230  // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
231  // keypress, otherwise actions in application that try to grab the
232  // keyboard (e.g. in kwin) may fail to do so. There is still a small race
233  // condition with this being out-of-process.
234  if (_manager) {
235  _manager->syncWindowingSystem();
236  }
237 
238  // 1st Invoke the action
239  shortcut->context()->component()->emitGlobalShortcutPressed(*shortcut);
240 
241  return true;
242 }
243 
244 void GlobalShortcutsRegistry::loadSettings()
245 {
246  const auto groupList = _config.groupList();
247  for (const QString &groupName : groupList) {
248  qCDebug(KGLOBALACCELD) << "Loading group " << groupName;
249 
250  Q_ASSERT(groupName.indexOf('\x1d') == -1);
251 
252  // loadSettings isn't designed to be called in between. Only at the
253  // beginning.
254  Q_ASSERT(!getComponent(groupName));
255 
256  KConfigGroup configGroup(&_config, groupName);
257 
258  // We previously stored the friendly name in a separate group. migrate
259  // that
260  const QString friendlyName = configGroup.readEntry("_k_friendly_name");
261 
262  // Create the component
263  KdeDGlobalAccel::Component *component = nullptr;
264  if (groupName.endsWith(QLatin1String(".desktop"))) {
265  component = new KdeDGlobalAccel::KServiceActionComponent(groupName, friendlyName, this);
266  } else {
267  component = new KdeDGlobalAccel::Component(groupName, friendlyName, this);
268  }
269 
270  // Now load the contexts
271  const auto groupList = configGroup.groupList();
272  for (const QString &context : groupList) {
273  // Skip the friendly name group
274  if (context == QLatin1String("Friendly Name"))
275  continue;
276 
277  KConfigGroup contextGroup(&configGroup, context);
278  QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name");
279  component->createGlobalShortcutContext(context, contextFriendlyName);
280  component->activateGlobalShortcutContext(context);
281  component->loadSettings(contextGroup);
282  }
283 
284  // Load the default context
285  component->activateGlobalShortcutContext("default");
286  component->loadSettings(configGroup);
287  }
288 
289  // Load the configured KServiceActions
290  const QStringList desktopPaths =
292  for (const QString &path : desktopPaths) {
293  QDir dir(path);
294  if (!dir.exists()) {
295  continue;
296  }
297  const QStringList patterns = {QStringLiteral("*.desktop")};
298  const auto lstDesktopFiles = dir.entryList(patterns);
299  for (const QString &desktopFile : lstDesktopFiles) {
300  if (_components.contains(desktopFile)) {
301  continue;
302  }
303 
304  KDesktopFile f(dir.filePath(desktopFile));
305  if (f.noDisplay()) {
306  continue;
307  }
308 
309  KdeDGlobalAccel::KServiceActionComponent *component = new KdeDGlobalAccel::KServiceActionComponent(desktopFile, f.readName(), this);
310  component->activateGlobalShortcutContext(QStringLiteral("default"));
311  component->loadFromService();
312  }
313  }
314 }
315 
316 void GlobalShortcutsRegistry::grabKeys()
317 {
319 }
320 
321 bool GlobalShortcutsRegistry::registerKey(int key, GlobalShortcut *shortcut)
322 {
323  if (!_manager) {
324  return false;
325  }
326  if (key == 0) {
327  qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Key '" << QKeySequence(key).toString() << "' already taken by "
328  << _active_keys.value(key)->uniqueName() << ".";
329  return false;
330  } else if (_active_keys.value(key)) {
331  qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Attempt to register key 0.";
332  return false;
333  }
334 
335  qCDebug(KGLOBALACCELD) << "Registering key" << QKeySequence(key).toString() << "for" << shortcut->context()->component()->uniqueName() << ":"
336  << shortcut->uniqueName();
337 
338  _active_keys.insert(key, shortcut);
339  return _manager->grabKey(key, true);
340 }
341 
342 void GlobalShortcutsRegistry::setAccelManager(KGlobalAccelInterface *manager)
343 {
344  _manager = manager;
345 }
346 
347 void GlobalShortcutsRegistry::setDBusPath(const QDBusObjectPath &path)
348 {
349  _dbusPath = path;
350 }
351 
352 KdeDGlobalAccel::Component *GlobalShortcutsRegistry::takeComponent(KdeDGlobalAccel::Component *component)
353 {
355  conn.unregisterObject(component->dbusPath().path());
356  return _components.take(component->uniqueName());
357 }
358 
359 void GlobalShortcutsRegistry::ungrabKeys()
360 {
362 }
363 
364 bool GlobalShortcutsRegistry::unregisterKey(int key, GlobalShortcut *shortcut)
365 {
366  if (!_manager) {
367  return false;
368  }
369  if (_active_keys.value(key) != shortcut) {
370  // The shortcut doesn't own the key or the key isn't grabbed
371  return false;
372  }
373 
374  qCDebug(KGLOBALACCELD) << "Unregistering key" << QKeySequence(key).toString() << "for" << shortcut->context()->component()->uniqueName() << ":"
375  << shortcut->uniqueName();
376 
377  _manager->grabKey(key, false);
378  _active_keys.take(key);
379  return true;
380 }
381 
382 void GlobalShortcutsRegistry::writeSettings() const
383 {
384  const auto lst = GlobalShortcutsRegistry::self()->allMainComponents();
385  for (const KdeDGlobalAccel::Component *component : lst) {
386  KConfigGroup configGroup(&_config, component->uniqueName());
387  if (component->allShortcuts().isEmpty()) {
388  configGroup.deleteGroup();
389  delete component;
390  } else {
391  component->writeSettings(configGroup);
392  }
393  }
394 
395  _config.sync();
396 }
397 
398 #include "moc_globalshortcutsregistry.cpp"
QList< GlobalShortcut * > getShortcutsByKey(int key) const
Get the shortcuts corresponding to key.
QHash::iterator insert(const Key &key, const T &value)
QJsonArray::iterator begin()
void deactivateShortcuts(bool temporarily=false)
Deactivate all currently active shortcuts.
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
QVector< QStaticPlugin > staticPlugins()
GlobalShortcut * getActiveShortcutByKey(int key) const
Get the shortcut corresponding to key.
bool registerObject(const QString &path, QObject *object, QDBusConnection::RegisterOptions options)
QString filePath(const QString &fileName) const const
virtual bool grabKey(int key, bool grab)=0
This function registers or unregisters a certain key for global capture, depending on grab...
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.
void deleteGroup(WriteConfigFlags pFlags=Normal)
virtual void syncWindowingSystem()
Allows implementing plugins to synchronize with the windowing system.
QString uniqueName() const
Returns the unique name aka id for the shortcuts.
Global Shortcut Registry.
QDBusObjectPath dbusPath() const
Return uniqueName converted to a valid dbus path.
Definition: component.cpp:162
Abstract interface for plugins to implement.
QString fromLocal8Bit(const char *str, int size)
bool exists() const const
bool sync() override
void append(const T &value)
QDBusObjectPath dbusPath() const
Return the root dbus path for the registry.
bool isShortcutAvailable(int key, const QString &component, const QString &context) const
Check if key is available for component component.
Definition: component.cpp:268
QJsonObject toObject() const const
CaseInsensitive
QJsonArray toArray() const const
bool isEmpty() const const
void loadSettings(KConfigGroup &config)
Load the settings from config group config.
Definition: component.cpp:315
bool isEmpty() const const
QList< GlobalShortcut * > allShortcuts(const QString &context=QStringLiteral("default")) const
Returns all shortcuts in context .
Definition: component.cpp:106
Represents a global shortcut.
void unregisterObject(const QString &path, QDBusConnection::UnregisterMode mode)
QList< Key > keys() const const
void activateShortcuts()
Activate all shortcuts having their application present.
QJsonArray::iterator end()
GlobalShortcut * getShortcutByKey(int key) const
Returns the currently active shortcut for key.
Definition: component.cpp:227
GlobalShortcutsRegistry()
Use GlobalShortcutsRegistry::self()
void clear()
const T value(const Key &key) const const
void deactivateShortcuts(bool temporarily=false)
Deactivate all currently active shortcuts.
Definition: component.cpp:176
bool isActive() const
Check if the shortcut is active. It&#39;s keys are grabbed.
QString toString(QKeySequence::SequenceFormat format) const const
T take(const Key &key)
bool createGlobalShortcutContext(const QString &context, const QString &friendlyName=QString())
Creates the new global shortcut context context.
Definition: component.cpp:147
bool isEmpty() const const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
QStringList groupList() const override
QList< T > values() const const
GlobalShortcut * getShortcutByKey(int key) const
Get the shortcut corresponding to key.
QJsonValue value(const QString &key) const const
bool contains(const Key &key) const const
static QVector< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter=std::function< bool(const KPluginMetaData &)>())
QString path() const const
QObject * parent() const const
int compare(const QString &other, Qt::CaseSensitivity cs) const const
T readEntry(const QString &key, const T &aDefault) const
QString platformName()
QStringList groupList() const override
QString friendlyName() const
Return the friendly display name for this shortcut.
QList< GlobalShortcut * > getShortcutsByKey(int key) const
Returns the list of shortcuts (different context) registered with key.
Definition: component.cpp:232
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.