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 | |
36 | static 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 | |
53 | static 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 | |
67 | namespace KdeDGlobalAccel { |
68 | |
69 | Component::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 | |
91 | Component::~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 | |
104 | bool 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 | |
123 | void Component::activateShortcuts() |
124 | { |
125 | Q_FOREACH (GlobalShortcut *shortcut, _current->_actions) |
126 | { |
127 | shortcut->setActive(); |
128 | } |
129 | } |
130 | |
131 | |
132 | QList<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 | |
147 | QList<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 | |
162 | bool 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 | |
186 | bool 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 | |
200 | GlobalShortcutContext *Component::currentContext() |
201 | { |
202 | return _current; |
203 | } |
204 | |
205 | |
206 | QDBusObjectPath 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 | |
221 | void 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 | |
236 | void 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 | |
264 | void Component::invokeShortcut(const QString &shortcutName, const QString &context) |
265 | { |
266 | GlobalShortcut *shortcut = getShortcutByName(shortcutName, context); |
267 | if (shortcut) emitGlobalShortcutPressed(*shortcut); |
268 | } |
269 | |
270 | QString Component::friendlyName() const |
271 | { |
272 | if (_friendlyName.isEmpty()) |
273 | return _uniqueName; |
274 | return _friendlyName; |
275 | } |
276 | |
277 | |
278 | GlobalShortcut *Component::getShortcutByKey(int key) const |
279 | { |
280 | return _current->getShortcutByKey(key); |
281 | } |
282 | |
283 | |
284 | QList<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 | |
296 | GlobalShortcut *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 | |
307 | QStringList Component::getShortcutContexts() const |
308 | { |
309 | return _contexts.keys(); |
310 | } |
311 | |
312 | |
313 | bool 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 | |
325 | bool 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 | |
355 | void 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 | |
394 | void Component::setFriendlyName(const QString &name) |
395 | { |
396 | _friendlyName = name; |
397 | } |
398 | |
399 | |
400 | GlobalShortcutContext *Component::shortcutContext( const QString &contextName ) |
401 | { |
402 | return _contexts.value(contextName); |
403 | } |
404 | |
405 | |
406 | GlobalShortcutContext const *Component::shortcutContext( const QString &contextName ) const |
407 | { |
408 | return _contexts.value(contextName); |
409 | } |
410 | |
411 | |
412 | QStringList 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 | |
425 | bool Component::showKCM() |
426 | { |
427 | return KRun::runCommand("kcmshell4 keys" , NULL); |
428 | } |
429 | |
430 | |
431 | QString Component::uniqueName() const |
432 | { |
433 | return _uniqueName; |
434 | } |
435 | |
436 | |
437 | void 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 | |
450 | void 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 | |