1/* Copyright (C) 2008 Michael Jansen <kde@michael-jansen.biz>
2
3 This library is free software; you can redistribute it and/or
4 modify it under the terms of the GNU Library General Public
5 License as published by the Free Software Foundation; either
6 version 2 of the License, or (at your option) any later version.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*/
18
19#include "component.h"
20
21#include "globalshortcut.h"
22#include "globalshortcutcontext.h"
23#include "globalshortcutsregistry.h"
24
25#include <KDE/KDebug>
26#include <KDE/KRun>
27
28#include <QtCore/QStringList>
29#include <QKeySequence>
30
31#ifdef Q_WS_X11
32#include <QApplication>
33#include <QX11Info>
34#endif
35
36static QList<int> keysFromString(const QString &str)
37{
38 QList<int> ret;
39 if (str == "none") {
40 return ret;
41 }
42 const QStringList strList = str.split('\t');
43 foreach (const QString &s, strList) {
44 int key = QKeySequence(s)[0];
45 if (key != -1) { //sanity check just in case
46 ret.append(key);
47 }
48 }
49 return ret;
50}
51
52
53static QString stringFromKeys(const QList<int> &keys)
54{
55 if (keys.isEmpty()) {
56 return "none";
57 }
58 QString ret;
59 foreach (int key, keys) {
60 ret.append(QKeySequence(key).toString());
61 ret.append('\t');
62 }
63 ret.chop(1);
64 return ret;
65}
66
67namespace KdeDGlobalAccel {
68
69Component::Component(
70 const QString &uniqueName,
71 const QString &friendlyName,
72 GlobalShortcutsRegistry *registry)
73 : _uniqueName(uniqueName)
74 ,_friendlyName(friendlyName)
75 ,_registry(registry)
76 {
77 // Make sure we do no get uniquenames still containing the context
78 Q_ASSERT(uniqueName.indexOf("|")==-1);
79
80 // Register ourselve with the registry
81 if (_registry)
82 {
83 _registry->addComponent(this);
84 }
85
86 createGlobalShortcutContext("default", "Default Context");
87 _current = _contexts.value("default");
88 }
89
90
91Component::~Component()
92 {
93 // Remove ourselve from the registry
94 if (_registry)
95 {
96 _registry->takeComponent(this);
97 }
98
99 // We delete all shortcuts from all contexts
100 qDeleteAll(_contexts);
101 }
102
103
104bool Component::activateGlobalShortcutContext(
105 const QString &uniqueName)
106 {
107 if (!_contexts.value(uniqueName))
108 {
109 createGlobalShortcutContext(uniqueName, "TODO4");
110 return false;
111 }
112
113 // Deactivate the current contexts shortcuts
114 deactivateShortcuts();
115
116 // Switch the context
117 _current = _contexts.value(uniqueName);
118
119 return true;
120 }
121
122
123void Component::activateShortcuts()
124 {
125 Q_FOREACH (GlobalShortcut *shortcut, _current->_actions)
126 {
127 shortcut->setActive();
128 }
129 }
130
131
132QList<GlobalShortcut*> Component::allShortcuts(const QString &contextName) const
133 {
134 GlobalShortcutContext *context = _contexts.value(contextName);
135 if (context)
136 {
137 return context->_actions.values();
138 }
139 else
140 {
141 Q_ASSERT(false); // Unknown context
142 return QList<GlobalShortcut*> ();
143 }
144 }
145
146
147QList<KGlobalShortcutInfo> Component::allShortcutInfos(const QString &contextName) const
148 {
149 QList<KGlobalShortcutInfo> rc;
150
151 GlobalShortcutContext *context = _contexts.value(contextName);
152 if (!context)
153 {
154 Q_ASSERT(false); // Unknown context
155 return rc;
156 }
157
158 return context->allShortcutInfos();
159 }
160
161
162bool Component::cleanUp()
163 {
164 bool changed = false;;
165
166 Q_FOREACH (GlobalShortcut *shortcut, _current->_actions)
167 {
168 kDebug() << _current->_actions.size();
169 if (!shortcut->isPresent())
170 {
171 changed = true;
172 shortcut->unRegister();
173 }
174 }
175
176 if (changed)
177 {
178 _registry->writeSettings();
179 // We could be destroyed after this call!
180 }
181
182 return changed;
183 }
184
185
186bool Component::createGlobalShortcutContext(
187 const QString &uniqueName,
188 const QString &friendlyName)
189 {
190 if (_contexts.value(uniqueName))
191 {
192 kDebug() << "Shortcut Context " << uniqueName << "already exists for component " << _uniqueName;
193 return false;
194 }
195 _contexts.insert(uniqueName, new GlobalShortcutContext(uniqueName, friendlyName, this));
196 return true;
197 }
198
199
200GlobalShortcutContext *Component::currentContext()
201 {
202 return _current;
203 }
204
205
206QDBusObjectPath Component::dbusPath() const
207 {
208 QString dbusPath = _uniqueName;
209 // Clean up for dbus usage: any non-alphanumeric char should be turned into '_'
210 const int len = dbusPath.length();
211 for ( int i = 0; i < len; ++i )
212 {
213 if ( !dbusPath[i].isLetterOrNumber() )
214 dbusPath[i] = QLatin1Char('_');
215 }
216 // QDBusObjectPath could be a little bit easier to handle :-)
217 return QDBusObjectPath( _registry->dbusPath().path() + "component/" + dbusPath);
218 }
219
220
221void Component::deactivateShortcuts(bool temporarily)
222 {
223 Q_FOREACH (GlobalShortcut *shortcut, _current->_actions)
224 {
225 if (temporarily
226 && uniqueName() == "kwin"
227 && shortcut->uniqueName() == "Block Global Shortcuts")
228 {
229 continue;
230 }
231 shortcut->setInactive();
232 }
233 }
234
235
236void Component::emitGlobalShortcutPressed( const GlobalShortcut &shortcut )
237 {
238#ifdef Q_WS_X11
239 // pass X11 timestamp
240 long timestamp = QX11Info::appTime();
241 // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
242 // keypress, otherwise actions in application that try to grab the
243 // keyboard (e.g. in kwin) may fail to do so. There is still a small race
244 // condition with this being out-of-process.
245 qApp->syncX();
246#else
247 long timestamp = 0;
248#endif
249
250 // Make sure it is one of ours
251 if (shortcut.context()->component() != this)
252 {
253 Q_ASSERT(shortcut.context()->component() == this);
254 // In production mode do nothing
255 return;
256 }
257
258 emit globalShortcutPressed(
259 shortcut.context()->component()->uniqueName(),
260 shortcut.uniqueName(),
261 timestamp);
262 }
263
264void Component::invokeShortcut(const QString &shortcutName, const QString &context)
265 {
266 GlobalShortcut *shortcut = getShortcutByName(shortcutName, context);
267 if (shortcut) emitGlobalShortcutPressed(*shortcut);
268 }
269
270QString Component::friendlyName() const
271 {
272 if (_friendlyName.isEmpty())
273 return _uniqueName;
274 return _friendlyName;
275 }
276
277
278GlobalShortcut *Component::getShortcutByKey(int key) const
279 {
280 return _current->getShortcutByKey(key);
281 }
282
283
284QList<GlobalShortcut *> Component::getShortcutsByKey(int key) const
285 {
286 QList <GlobalShortcut *> rc;
287 Q_FOREACH(GlobalShortcutContext *context, _contexts)
288 {
289 GlobalShortcut *sc = context->getShortcutByKey(key);
290 if (sc) rc.append(sc);
291 }
292 return rc;
293 }
294
295
296GlobalShortcut *Component::getShortcutByName(const QString &uniqueName, const QString &context) const
297 {
298 if (!_contexts.value(context))
299 {
300 return NULL;
301 }
302
303 return _contexts.value(context)->_actions.value(uniqueName);
304 }
305
306
307QStringList Component::getShortcutContexts() const
308 {
309 return _contexts.keys();
310 }
311
312
313bool Component::isActive() const
314 {
315 // The component is active if at least one of it's global shortcuts is
316 // present.
317 Q_FOREACH (GlobalShortcut *shortcut, _current->_actions)
318 {
319 if (shortcut->isPresent()) return true;
320 }
321 return false;
322 }
323
324
325bool Component::isShortcutAvailable(
326 int key,
327 const QString &component,
328 const QString &context) const
329 {
330 kDebug() << QKeySequence(key).toString() << component;
331
332 // if this component asks for the key. only check the keys in the same
333 // context
334 if (component==uniqueName())
335 {
336 Q_FOREACH(GlobalShortcut *sc, shortcutContext(context)->_actions)
337 {
338 if (sc->keys().contains(key)) return false;
339 }
340 }
341 else
342 {
343 Q_FOREACH(GlobalShortcutContext *ctx, _contexts)
344 {
345 Q_FOREACH(GlobalShortcut *sc, ctx->_actions)
346 {
347 if (sc->keys().contains(key)) return false;
348 }
349 }
350 }
351 return true;
352 }
353
354
355void Component::loadSettings(KConfigGroup &configGroup)
356 {
357 // GlobalShortcutsRegistry::loadSettings handles contexts.
358 Q_FOREACH (const QString &confKey, configGroup.keyList())
359 {
360 const QStringList entry = configGroup.readEntry(confKey, QStringList());
361 if (entry.size() != 3)
362 {
363 continue;
364 }
365
366 // The shortcut will register itself with us
367 GlobalShortcut *shortcut = new GlobalShortcut(
368 confKey,
369 entry[2],
370 _current);
371
372 QList<int> keys = keysFromString(entry[0]);
373 shortcut->setDefaultKeys(keysFromString(entry[1]));
374 shortcut->setIsFresh(false);
375
376 Q_FOREACH (int key, keys)
377 {
378 if (key != 0)
379 {
380 if (GlobalShortcutsRegistry::self()->getShortcutByKey(key))
381 {
382 // The shortcut is already used. The config file is
383 // broken. Ignore the request.
384 keys.removeAll(key);
385 kWarning() << "Shortcut found twice in kglobalshortcutsrc.";
386 }
387 }
388 }
389 shortcut->setKeys(keys);
390 }
391 }
392
393
394void Component::setFriendlyName(const QString &name)
395 {
396 _friendlyName = name;
397 }
398
399
400GlobalShortcutContext *Component::shortcutContext( const QString &contextName )
401 {
402 return _contexts.value(contextName);
403 }
404
405
406GlobalShortcutContext const *Component::shortcutContext( const QString &contextName ) const
407 {
408 return _contexts.value(contextName);
409 }
410
411
412QStringList Component::shortcutNames( const QString &contextName) const
413 {
414 GlobalShortcutContext *context = _contexts.value(contextName);
415 if (!context)
416 {
417 Q_ASSERT(false); // Unknown context
418 return QStringList();
419 }
420
421 return context->_actions.keys();
422 }
423
424
425bool Component::showKCM()
426 {
427 return KRun::runCommand("kcmshell4 keys", NULL);
428 }
429
430
431QString Component::uniqueName() const
432 {
433 return _uniqueName;
434 }
435
436
437void Component::unregisterShortcut(const QString &uniqueName)
438 {
439 // Now wrote all contexts
440 Q_FOREACH( GlobalShortcutContext *context, _contexts)
441 {
442 if (context->_actions.value(uniqueName))
443 {
444 delete context->takeShortcut(context->_actions.value(uniqueName));
445 }
446 }
447 }
448
449
450void Component::writeSettings(KConfigGroup& configGroup) const
451 {
452 // If we don't delete the current content global shortcut
453 // registrations will never not deleted after forgetGlobalShortcut()
454 configGroup.deleteGroup();
455
456
457 // Now write all contexts
458 Q_FOREACH( GlobalShortcutContext *context, _contexts)
459 {
460 KConfigGroup contextGroup;
461
462 if (context->uniqueName() == "default")
463 {
464 contextGroup = configGroup;
465 // Write the friendly name
466 contextGroup.writeEntry("_k_friendly_name", friendlyName());
467 }
468 else
469 {
470 contextGroup = KConfigGroup(&configGroup, context->uniqueName());
471 // Write the friendly name
472 contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
473 }
474
475 // kDebug() << "writing group " << _uniqueName << ":" << context->uniqueName();
476
477 Q_FOREACH(const GlobalShortcut *shortcut, context->_actions)
478 {
479 // kDebug() << "writing" << shortcut->uniqueName();
480
481 // We do not write fresh shortcuts.
482 // We do not write session shortcuts
483 if (shortcut->isFresh() || shortcut->isSessionShortcut())
484 {
485 continue;
486 }
487 // kDebug() << "really writing" << shortcut->uniqueName();
488
489 QStringList entry(stringFromKeys(shortcut->keys()));
490 entry.append(stringFromKeys(shortcut->defaultKeys()));
491 entry.append(shortcut->friendlyName());
492
493 contextGroup.writeEntry(shortcut->uniqueName(), entry);
494 }
495 }
496 }
497
498#include "moc_component.cpp"
499
500} // namespace KdeDGlobalAccel
501
502