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 "globalshortcutsregistry.h"
20#include "component.h"
21#include "globalshortcut.h"
22#include "globalshortcutcontext.h"
23
24#include "kdebug.h"
25#include "kglobal.h"
26#include "klocale.h"
27#include "knotification.h"
28
29#include <QKeySequence>
30#include <QDBusConnection>
31
32#ifdef Q_WS_X11
33#include "kglobalaccel_x11.h"
34#include <QX11Info>
35#include <QApplication>
36#elif defined(Q_WS_MACX)
37#include "kglobalaccel_mac.h"
38#elif defined(Q_WS_WIN)
39#include "kglobalaccel_win.h"
40#else
41#include "kglobalaccel_qws.h"
42#endif
43
44GlobalShortcutsRegistry::GlobalShortcutsRegistry()
45 : QObject()
46 ,_active_keys()
47 ,_components()
48 ,_manager(new KGlobalAccelImpl(this))
49 ,_config("kglobalshortcutsrc", KConfig::SimpleConfig)
50 {
51 _manager->setEnabled(true);
52 }
53
54
55GlobalShortcutsRegistry::~GlobalShortcutsRegistry()
56 {
57 _manager->setEnabled(false);
58
59 // Ungrab all keys. We don't go over GlobalShortcuts because
60 // GlobalShortcutsRegistry::self() doesn't work anymore.
61 Q_FOREACH (const int key, _active_keys.keys())
62 {
63 _manager->grabKey(key, false);
64 }
65 _active_keys.clear();
66 }
67
68
69KdeDGlobalAccel::Component *GlobalShortcutsRegistry::addComponent(KdeDGlobalAccel::Component *component)
70 {
71 if (_components.value(component->uniqueName()))
72 {
73 Q_ASSERT_X(false, "GlobalShortcutsRegistry::addComponent", "component already registered?!?!");
74 return _components.value(component->uniqueName());
75 }
76
77 _components.insert(component->uniqueName(), component);
78 QDBusConnection conn(QDBusConnection::sessionBus());
79
80 conn.registerObject(
81 component->dbusPath().path(),
82 component,
83 QDBusConnection::ExportScriptableContents);
84 return component;
85 }
86
87
88void GlobalShortcutsRegistry::activateShortcuts()
89 {
90 Q_FOREACH (KdeDGlobalAccel::Component *component, _components)
91 {
92 component->activateShortcuts();
93 }
94 }
95
96
97QList<KdeDGlobalAccel::Component*> GlobalShortcutsRegistry::allMainComponents() const
98 {
99 return _components.values();
100 }
101
102
103void GlobalShortcutsRegistry::clear()
104 {
105 Q_FOREACH(KdeDGlobalAccel::Component *component, _components)
106 {
107 delete component;
108 }
109 _components.clear();
110
111 // The shortcuts should have deregistered themselves
112 Q_ASSERT(_active_keys.isEmpty());
113 }
114
115
116QDBusObjectPath GlobalShortcutsRegistry::dbusPath() const
117 {
118 return _dbusPath;
119 }
120
121
122void GlobalShortcutsRegistry::deactivateShortcuts(bool temporarily)
123 {
124 Q_FOREACH (KdeDGlobalAccel::Component *component, _components)
125 {
126 component->deactivateShortcuts(temporarily);
127 }
128 }
129
130
131GlobalShortcut *GlobalShortcutsRegistry::getActiveShortcutByKey(int key) const
132 {
133 return _active_keys.value(key);
134 }
135
136
137KdeDGlobalAccel::Component *GlobalShortcutsRegistry::getComponent(const QString &uniqueName)
138 {
139 return _components.value(uniqueName);
140 }
141
142
143GlobalShortcut *GlobalShortcutsRegistry::getShortcutByKey(int key) const
144 {
145 Q_FOREACH (KdeDGlobalAccel::Component *component, _components)
146 {
147 GlobalShortcut *rc = component->getShortcutByKey(key);
148 if (rc) return rc;
149 }
150 return NULL;
151 }
152
153
154QList<GlobalShortcut*> GlobalShortcutsRegistry::getShortcutsByKey(int key) const
155 {
156 QList<GlobalShortcut *> rc;
157
158 Q_FOREACH (KdeDGlobalAccel::Component *component, _components)
159 {
160 rc = component->getShortcutsByKey(key);
161 if (!rc.isEmpty()) return rc;
162 }
163 return rc;
164 }
165
166
167bool GlobalShortcutsRegistry::isShortcutAvailable(
168 int shortcut,
169 const QString &componentName,
170 const QString &contextName) const
171 {
172 Q_FOREACH (KdeDGlobalAccel::Component *component, _components)
173 {
174 if (!component->isShortcutAvailable(shortcut, componentName, contextName))
175 return false;
176 }
177 return true;
178 }
179
180
181GlobalShortcutsRegistry * GlobalShortcutsRegistry::self()
182 {
183 K_GLOBAL_STATIC( GlobalShortcutsRegistry, self );
184 return self;
185 }
186
187
188bool GlobalShortcutsRegistry::keyPressed(int keyQt)
189 {
190 GlobalShortcut *shortcut = getShortcutByKey(keyQt);
191 if (!shortcut)
192 {
193 // This can happen for example with the ALT-Print shortcut of kwin.
194 // ALT+PRINT is SYSREQ on my keyboard. So we grab something we think
195 // is ALT+PRINT but symXToKeyQt and modXToQt make ALT+SYSREQ of it
196 // when pressed (correctly). We can't match that.
197 kDebug() << "Got unknown key" << QKeySequence(keyQt).toString();
198
199 // In production mode just do nothing.
200 return false;
201 }
202 else if (!shortcut->isActive())
203 {
204 kDebug() << "Got inactive key" << QKeySequence(keyQt).toString();
205
206 // In production mode just do nothing.
207 return false;
208 }
209
210 kDebug() << QKeySequence(keyQt).toString() << "=" << shortcut->uniqueName();
211
212 QStringList data(shortcut->context()->component()->uniqueName());
213 data.append(shortcut->uniqueName());
214 data.append(shortcut->context()->component()->friendlyName());
215 data.append(shortcut->friendlyName());
216#ifdef Q_WS_X11
217 // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
218 // keypress, otherwise actions in application that try to grab the
219 // keyboard (e.g. in kwin) may fail to do so. There is still a small race
220 // condition with this being out-of-process.
221 qApp->syncX();
222#endif
223
224 // 1st Invoke the action
225 shortcut->context()->component()->emitGlobalShortcutPressed( *shortcut );
226
227 // Then do anything else
228 KNotification *notification = new KNotification(
229 "globalshortcutpressed",
230 KNotification::CloseOnTimeout);
231
232 notification->setText(
233 i18n("The global shortcut for %1 was issued.", shortcut->friendlyName()));
234
235 notification->addContext( "application", shortcut->context()->component()->friendlyName() );
236
237 notification->sendEvent();
238
239 return true;
240}
241
242
243void GlobalShortcutsRegistry::loadSettings()
244 {
245 foreach (const QString &groupName, _config.groupList())
246 {
247 kDebug() << "Loading group " << groupName;
248
249 Q_ASSERT(groupName.indexOf('\x1d')==-1);
250
251 // loadSettings isn't designed to be called in between. Only at the
252 // beginning.
253 Q_ASSERT(!getComponent(groupName));
254
255 KConfigGroup configGroup(&_config, groupName);
256
257 // We previously stored the friendly name in a separate group. migrate
258 // that
259 QString friendlyName;
260 KConfigGroup friendlyGroup(&configGroup, "Friendly Name");
261 if (friendlyGroup.isValid())
262 {
263 friendlyName = friendlyGroup.readEntry("Friendly Name");
264 friendlyGroup.deleteGroup();
265 }
266 else
267 {
268 friendlyName = configGroup.readEntry("_k_friendly_name");
269 }
270
271 // Create the component
272 KdeDGlobalAccel::Component *component = new KdeDGlobalAccel::Component(
273 groupName,
274 friendlyName,
275 this);
276
277 // Now load the contexts
278 Q_FOREACH(const QString& context, configGroup.groupList())
279 {
280 // Skip the friendly name group
281 if (context=="Friendly Name") continue;
282
283 KConfigGroup contextGroup(&configGroup, context);
284 QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name");
285 component->createGlobalShortcutContext(context, contextFriendlyName);
286 component->activateGlobalShortcutContext(context);
287 component->loadSettings(contextGroup);
288 }
289
290 // Load the default context
291 component->activateGlobalShortcutContext("default");
292 component->loadSettings(configGroup);
293 }
294 }
295
296
297void GlobalShortcutsRegistry::grabKeys()
298 {
299 activateShortcuts();
300 }
301
302
303bool GlobalShortcutsRegistry::registerKey(int key, GlobalShortcut *shortcut)
304 {
305 if (key == 0)
306 {
307 kDebug() << shortcut->uniqueName() << ": Key '" << QKeySequence(key).toString()
308 << "' already taken by " << _active_keys.value(key)->uniqueName() << ".";
309 return false;
310 }
311 else if (_active_keys.value(key))
312 {
313 kDebug() << shortcut->uniqueName() << ": Attempt to register key 0.";
314 return false;
315 }
316
317 kDebug() << "Registering key" << QKeySequence(key).toString() << "for"
318 << shortcut->context()->component()->uniqueName() << ":" << shortcut->uniqueName();
319
320 _active_keys.insert(key, shortcut);
321 return _manager->grabKey(key, true);
322 }
323
324
325void GlobalShortcutsRegistry::setAccelManager(KGlobalAccelImpl *manager)
326 {
327 _manager = manager;
328 }
329
330
331void GlobalShortcutsRegistry::setDBusPath(const QDBusObjectPath &path)
332 {
333 _dbusPath = path;
334 }
335
336
337KdeDGlobalAccel::Component *GlobalShortcutsRegistry::takeComponent(KdeDGlobalAccel::Component *component)
338 {
339 QDBusConnection conn(QDBusConnection::sessionBus());
340 conn.unregisterObject(component->dbusPath().path());
341 return _components.take(component->uniqueName());
342 }
343
344
345void GlobalShortcutsRegistry::ungrabKeys()
346 {
347 deactivateShortcuts();
348 }
349
350
351bool GlobalShortcutsRegistry::unregisterKey(int key, GlobalShortcut *shortcut)
352 {
353 if (_active_keys.value(key)!=shortcut)
354 {
355 // The shortcut doesn't own the key or the key isn't grabbed
356 return false;
357 }
358
359 kDebug() << "Unregistering key" << QKeySequence(key).toString() << "for"
360 << shortcut->context()->component()->uniqueName() << ":" << shortcut->uniqueName();
361
362 _manager->grabKey(key, false);
363 _active_keys.take(key);
364 return true;
365 }
366
367
368void GlobalShortcutsRegistry::writeSettings() const
369 {
370 Q_FOREACH(
371 const KdeDGlobalAccel::Component *component,
372 GlobalShortcutsRegistry::self()->allMainComponents())
373 {
374 KConfigGroup configGroup(&_config, component->uniqueName());
375 if (component->allShortcuts().isEmpty())
376 {
377 configGroup.deleteGroup();
378 delete component;
379 }
380 else
381 {
382 component->writeSettings(configGroup);
383 }
384 }
385
386 _config.sync();
387 }
388
389
390#include "moc_globalshortcutsregistry.cpp"
391