1 | /* This file is part of the KDE libraries |
2 | Copyright (C) 2001,2002 Ellis Whitehead <ellis@kde.org> |
3 | |
4 | This library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Library General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2 of the License, or (at your option) any later version. |
8 | |
9 | This library is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | Library General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public License |
15 | along with this library; see the file COPYING.LIB. If not, write to |
16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 | Boston, MA 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "kglobalaccel_x11.h" |
21 | |
22 | #include <QWidgetList> |
23 | |
24 | #include "kaction.h" |
25 | #include "globalshortcutsregistry.h" |
26 | #include "kkeyserver.h" |
27 | |
28 | #include <kapplication.h> |
29 | #include <kdebug.h> |
30 | |
31 | #include <QtCore/QRegExp> |
32 | #include <QWidget> |
33 | #include <QtCore/QMetaClassInfo> |
34 | #include <QMenu> |
35 | |
36 | #include <kxerrorhandler.h> |
37 | |
38 | #include <X11/X.h> |
39 | #include <X11/Xlib.h> |
40 | #include <X11/Xutil.h> |
41 | #include <X11/keysym.h> |
42 | #include <fixx11h.h> |
43 | |
44 | extern "C" { |
45 | static int XGrabErrorHandler( Display *, XErrorEvent *e ) { |
46 | if ( e->error_code != BadAccess ) { |
47 | kWarning() << "grabKey: got X error " << e->type << " instead of BadAccess\n" ; |
48 | } |
49 | return 1; |
50 | } |
51 | } |
52 | |
53 | // g_keyModMaskXAccel |
54 | // mask of modifiers which can be used in shortcuts |
55 | // (meta, alt, ctrl, shift) |
56 | // g_keyModMaskXOnOrOff |
57 | // mask of modifiers where we don't care whether they are on or off |
58 | // (caps lock, num lock, scroll lock) |
59 | static uint g_keyModMaskXAccel = 0; |
60 | static uint g_keyModMaskXOnOrOff = 0; |
61 | |
62 | static void calculateGrabMasks() |
63 | { |
64 | g_keyModMaskXAccel = KKeyServer::accelModMaskX(); |
65 | g_keyModMaskXOnOrOff = |
66 | KKeyServer::modXLock() | |
67 | KKeyServer::modXNumLock() | |
68 | KKeyServer::modXScrollLock() | |
69 | KKeyServer::modXModeSwitch(); |
70 | //kDebug() << "g_keyModMaskXAccel = " << g_keyModMaskXAccel |
71 | // << "g_keyModMaskXOnOrOff = " << g_keyModMaskXOnOrOff << endl; |
72 | } |
73 | |
74 | //---------------------------------------------------- |
75 | |
76 | KGlobalAccelImpl::KGlobalAccelImpl(GlobalShortcutsRegistry *owner) |
77 | : m_owner(owner) |
78 | { |
79 | calculateGrabMasks(); |
80 | } |
81 | |
82 | bool KGlobalAccelImpl::grabKey( int keyQt, bool grab ) |
83 | { |
84 | if( !keyQt ) { |
85 | kDebug() << "Tried to grab key with null code." ; |
86 | return false; |
87 | } |
88 | |
89 | int keyCodeX; |
90 | uint keyModX; |
91 | uint keySymX; |
92 | |
93 | // Resolve the modifier |
94 | if( !KKeyServer::keyQtToModX(keyQt, &keyModX) ) { |
95 | kDebug() << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 modifier" ; |
96 | return false; |
97 | } |
98 | |
99 | // Resolve the X symbol |
100 | if( !KKeyServer::keyQtToSymX(keyQt, (int *)&keySymX) ) { |
101 | kDebug() << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 keycode" ; |
102 | return false; |
103 | } |
104 | |
105 | keyCodeX = XKeysymToKeycode( QX11Info::display(), keySymX ); |
106 | |
107 | // Check if shift needs to be added to the grab since KKeySequenceWidget |
108 | // can remove shift for some keys. (all the %&* and such) |
109 | if( !(keyQt & Qt::SHIFT) && |
110 | !KKeyServer::isShiftAsModifierAllowed( keyQt ) && |
111 | keySymX != XKeycodeToKeysym( QX11Info::display(), keyCodeX, 0 ) && |
112 | keySymX == XKeycodeToKeysym( QX11Info::display(), keyCodeX, 1 ) ) |
113 | { |
114 | kDebug() << "adding shift to the grab" ; |
115 | keyModX |= KKeyServer::modXShift(); |
116 | } |
117 | |
118 | keyModX &= g_keyModMaskXAccel; // Get rid of any non-relevant bits in mod |
119 | |
120 | if( !keyCodeX ) { |
121 | kDebug() << "keyQt (0x" << hex << keyQt << ") was resolved to x11 keycode 0" ; |
122 | return false; |
123 | } |
124 | |
125 | KXErrorHandler handler( XGrabErrorHandler ); |
126 | |
127 | // We'll have to grab 8 key modifier combinations in order to cover all |
128 | // combinations of CapsLock, NumLock, ScrollLock. |
129 | // Does anyone with more X-savvy know how to set a mask on QX11Info::appRootWindow so that |
130 | // the irrelevant bits are always ignored and we can just make one XGrabKey |
131 | // call per accelerator? -- ellis |
132 | #ifndef NDEBUG |
133 | QString sDebug = QString("\tcode: 0x%1 state: 0x%2 | " ).arg(keyCodeX,0,16).arg(keyModX,0,16); |
134 | #endif |
135 | uint keyModMaskX = ~g_keyModMaskXOnOrOff; |
136 | for( uint irrelevantBitsMask = 0; irrelevantBitsMask <= 0xff; irrelevantBitsMask++ ) { |
137 | if( (irrelevantBitsMask & keyModMaskX) == 0 ) { |
138 | #ifndef NDEBUG |
139 | sDebug += QString("0x%3, " ).arg(irrelevantBitsMask, 0, 16); |
140 | #endif |
141 | if( grab ) |
142 | XGrabKey( QX11Info::display(), keyCodeX, keyModX | irrelevantBitsMask, |
143 | QX11Info::appRootWindow(), True, GrabModeAsync, GrabModeSync ); |
144 | else |
145 | XUngrabKey( QX11Info::display(), keyCodeX, keyModX | irrelevantBitsMask, QX11Info::appRootWindow() ); |
146 | } |
147 | } |
148 | |
149 | bool failed = false; |
150 | if( grab ) { |
151 | failed = handler.error( true ); // sync now |
152 | if( failed ) { |
153 | kDebug() << "grab failed!\n" ; |
154 | for( uint m = 0; m <= 0xff; m++ ) { |
155 | if(( m & keyModMaskX ) == 0 ) |
156 | XUngrabKey( QX11Info::display(), keyCodeX, keyModX | m, QX11Info::appRootWindow() ); |
157 | } |
158 | } |
159 | } |
160 | |
161 | return !failed; |
162 | } |
163 | |
164 | bool KGlobalAccelImpl::x11Event( XEvent* event ) |
165 | { |
166 | switch( event->type ) { |
167 | |
168 | case MappingNotify: |
169 | kDebug() << "Got XMappingNotify event" ; |
170 | XRefreshKeyboardMapping(&event->xmapping); |
171 | x11MappingNotify(); |
172 | return true; |
173 | |
174 | case XKeyPress: |
175 | kDebug() << "Got XKeyPress event" ; |
176 | return x11KeyPress(event); |
177 | |
178 | default: |
179 | // We get all XEvents. Just ignore them. |
180 | return false; |
181 | } |
182 | |
183 | Q_ASSERT(false); |
184 | return false; |
185 | } |
186 | |
187 | void KGlobalAccelImpl::x11MappingNotify() |
188 | { |
189 | // Maybe the X modifier map has been changed. |
190 | // uint oldKeyModMaskXAccel = g_keyModMaskXAccel; |
191 | // uint oldKeyModMaskXOnOrOff = g_keyModMaskXOnOrOff; |
192 | |
193 | // First ungrab all currently grabbed keys. This is needed because we |
194 | // store the keys as qt keycodes and use KKeyServer to map them to x11 key |
195 | // codes. After calling KKeyServer::initializeMods() they could map to |
196 | // different keycodes. |
197 | m_owner->ungrabKeys(); |
198 | |
199 | KKeyServer::initializeMods(); |
200 | calculateGrabMasks(); |
201 | |
202 | m_owner->grabKeys(); |
203 | } |
204 | |
205 | |
206 | bool KGlobalAccelImpl::x11KeyPress( const XEvent *pEvent ) |
207 | { |
208 | if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) { |
209 | kWarning() << "kglobalacceld should be popup and keyboard grabbing free!" ; |
210 | } |
211 | |
212 | // Keyboard needs to be ungrabed after XGrabKey() activates the grab, |
213 | // otherwise it becomes frozen. |
214 | XUngrabKeyboard( QX11Info::display(), CurrentTime ); |
215 | XFlush( QX11Info::display()); // avoid X(?) bug |
216 | |
217 | uchar keyCodeX = pEvent->xkey.keycode; |
218 | uint keyModX = pEvent->xkey.state & (g_keyModMaskXAccel | KKeyServer::MODE_SWITCH); |
219 | |
220 | KeySym keySym; |
221 | XLookupString( (XKeyEvent*) pEvent, 0, 0, &keySym, 0 ); |
222 | uint keySymX = (uint)keySym; |
223 | |
224 | // If numlock is active and a keypad key is pressed, XOR the SHIFT state. |
225 | // e.g., KP_4 => Shift+KP_Left, and Shift+KP_4 => KP_Left. |
226 | if( pEvent->xkey.state & KKeyServer::modXNumLock() ) { |
227 | uint sym = XKeycodeToKeysym( QX11Info::display(), keyCodeX, 0 ); |
228 | // If this is a keypad key, |
229 | if( sym >= XK_KP_Space && sym <= XK_KP_9 ) { |
230 | switch( sym ) { |
231 | |
232 | // Leave the following keys unaltered |
233 | // FIXME: The proper solution is to see which keysyms don't change when shifted. |
234 | case XK_KP_Multiply: |
235 | case XK_KP_Add: |
236 | case XK_KP_Subtract: |
237 | case XK_KP_Divide: |
238 | break; |
239 | |
240 | default: |
241 | keyModX ^= KKeyServer::modXShift(); |
242 | } |
243 | } |
244 | } |
245 | |
246 | int keyCodeQt; |
247 | int keyModQt; |
248 | KKeyServer::symXToKeyQt(keySymX, &keyCodeQt); |
249 | KKeyServer::modXToQt(keyModX, &keyModQt); |
250 | |
251 | if( keyModQt & Qt::SHIFT && !KKeyServer::isShiftAsModifierAllowed( keyCodeQt ) ) { |
252 | kDebug() << "removing shift modifier" ; |
253 | keyModQt &= ~Qt::SHIFT; |
254 | } |
255 | |
256 | int keyQt = keyCodeQt | keyModQt; |
257 | |
258 | // All that work for this hey... argh... |
259 | return m_owner->keyPressed(keyQt); |
260 | } |
261 | |
262 | void KGlobalAccelImpl::setEnabled( bool enable ) |
263 | { |
264 | if (enable) { |
265 | kapp->installX11EventFilter( this ); |
266 | } else |
267 | kapp->removeX11EventFilter( this ); |
268 | } |
269 | |
270 | |
271 | #include "kglobalaccel_x11.moc" |
272 | |