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
44extern "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)
59static uint g_keyModMaskXAccel = 0;
60static uint g_keyModMaskXOnOrOff = 0;
61
62static 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
76KGlobalAccelImpl::KGlobalAccelImpl(GlobalShortcutsRegistry *owner)
77 : m_owner(owner)
78{
79 calculateGrabMasks();
80}
81
82bool 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
164bool 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
187void 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
206bool 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
262void 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