1/* This file is part of the KDE libraries
2 Copyright (C) 2001,2002 Ellis Whitehead <ellis@kde.org>
3 Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "kglobalaccel_x11.h"
22
23#include "logging_p.h"
24#include "kkeyserver.h"
25#include <netwm.h>
26
27#include <QDebug>
28
29#include <QApplication>
30#include <QWidget>
31#include <QX11Info>
32
33#include <X11/keysym.h>
34
35// xcb
36
37// It uses "explicit" as a variable name, which is not allowed in C++
38#define explicit xcb_explicit
39#include <xcb/xcb.h>
40#include <xcb/xcb_keysyms.h>
41#include <xcb/xkb.h>
42#undef explicit
43
44// g_keyModMaskXAccel
45// mask of modifiers which can be used in shortcuts
46// (meta, alt, ctrl, shift)
47// g_keyModMaskXOnOrOff
48// mask of modifiers where we don't care whether they are on or off
49// (caps lock, num lock, scroll lock)
50static uint g_keyModMaskXAccel = 0;
51static uint g_keyModMaskXOnOrOff = 0;
52
53static void calculateGrabMasks()
54{
55 g_keyModMaskXAccel = KKeyServer::accelModMaskX();
56 g_keyModMaskXOnOrOff =
57 KKeyServer::modXLock() |
58 KKeyServer::modXNumLock() |
59 KKeyServer::modXScrollLock() |
60 KKeyServer::modXModeSwitch();
61 //qCDebug(KGLOBALACCELD) << "g_keyModMaskXAccel = " << g_keyModMaskXAccel
62 // << "g_keyModMaskXOnOrOff = " << g_keyModMaskXOnOrOff << endl;
63}
64
65//----------------------------------------------------
66
67KGlobalAccelImpl::KGlobalAccelImpl(QObject *parent)
68 : KGlobalAccelInterface(parent)
69 , m_keySymbols(nullptr)
70 , m_xkb_first_event(0)
71{
72 Q_ASSERT(QX11Info::connection());
73
74 const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_xkb_id);
75 if (reply && reply->present) {
76 m_xkb_first_event = reply->first_event;
77 }
78
79 calculateGrabMasks();
80}
81
82KGlobalAccelImpl::~KGlobalAccelImpl()
83{
84 if (m_keySymbols) {
85 xcb_key_symbols_free(m_keySymbols);
86 }
87}
88
89bool KGlobalAccelImpl::grabKey( int keyQt, bool grab )
90{
91 //grabKey is called during shutdown
92 //shutdown might be due to the X server being killed
93 //if so, fail immediately before trying to make other xcb calls
94 if (!QX11Info::connection() || xcb_connection_has_error(QX11Info::connection())) {
95 return false;
96 }
97
98 if (!m_keySymbols) {
99 m_keySymbols = xcb_key_symbols_alloc(QX11Info::connection());
100 if (!m_keySymbols) {
101 return false;
102 }
103 }
104
105 if( !keyQt ) {
106 qCDebug(KGLOBALACCELD) << "Tried to grab key with null code.";
107 return false;
108 }
109
110 uint keyModX;
111 xcb_keysym_t keySymX;
112
113 // Resolve the modifier
114 if( !KKeyServer::keyQtToModX(keyQt, &keyModX) ) {
115 qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 modifier";
116 return false;
117 }
118
119 // Resolve the X symbol
120 if( !KKeyServer::keyQtToSymX(keyQt, (int *)&keySymX) ) {
121 qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 keycode";
122 return false;
123 }
124
125 xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(m_keySymbols, keySymX);
126 if (!keyCodes) {
127 return false;
128 }
129 int i = 0;
130 bool success = !grab;
131 while (keyCodes[i] != XCB_NO_SYMBOL) {
132 xcb_keycode_t keyCodeX = keyCodes[i++];
133
134 // Check if shift needs to be added to the grab since KKeySequenceWidget
135 // can remove shift for some keys. (all the %&* and such)
136 if( !(keyQt & Qt::SHIFT) &&
137 !KKeyServer::isShiftAsModifierAllowed( keyQt ) &&
138 !(keyQt & Qt::KeypadModifier) &&
139 keySymX != xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 0) &&
140 keySymX == xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 1) )
141 {
142 qCDebug(KGLOBALACCELD) << "adding shift to the grab";
143 keyModX |= KKeyServer::modXShift();
144 }
145
146 keyModX &= g_keyModMaskXAccel; // Get rid of any non-relevant bits in mod
147
148 if( !keyCodeX ) {
149 qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") was resolved to x11 keycode 0";
150 continue;
151 }
152
153 // We'll have to grab 8 key modifier combinations in order to cover all
154 // combinations of CapsLock, NumLock, ScrollLock.
155 // Does anyone with more X-savvy know how to set a mask on QX11Info::appRootWindow so that
156 // the irrelevant bits are always ignored and we can just make one XGrabKey
157 // call per accelerator? -- ellis
158 #ifndef NDEBUG
159 QString sDebug = QString("\tcode: 0x%1 state: 0x%2 | ").arg(keyCodeX,0,16).arg(keyModX,0,16);
160 #endif
161 uint keyModMaskX = ~g_keyModMaskXOnOrOff;
162 QVector<xcb_void_cookie_t> cookies;
163 for( uint irrelevantBitsMask = 0; irrelevantBitsMask <= 0xff; irrelevantBitsMask++ ) {
164 if( (irrelevantBitsMask & keyModMaskX) == 0 ) {
165 #ifndef NDEBUG
166 sDebug += QString("0x%3, ").arg(irrelevantBitsMask, 0, 16);
167 #endif
168 if( grab )
169 cookies << xcb_grab_key_checked(QX11Info::connection(), true,
170 QX11Info::appRootWindow(), keyModX | irrelevantBitsMask,
171 keyCodeX, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_SYNC);
172 else
173 cookies << xcb_ungrab_key_checked(QX11Info::connection(), keyCodeX,
174 QX11Info::appRootWindow(), keyModX | irrelevantBitsMask);
175 }
176 }
177
178 bool failed = false;
179 if( grab ) {
180 for (int i = 0; i < cookies.size(); ++i) {
181 QScopedPointer<xcb_generic_error_t, QScopedPointerPodDeleter> error(xcb_request_check(QX11Info::connection(), cookies.at(i)));
182 if (!error.isNull()) {
183 failed = true;
184 }
185 }
186 if( failed ) {
187 qCDebug(KGLOBALACCELD) << "grab failed!\n";
188 for( uint m = 0; m <= 0xff; m++ ) {
189 if(( m & keyModMaskX ) == 0 )
190 xcb_ungrab_key(QX11Info::connection(), keyCodeX, QX11Info::appRootWindow(), keyModX | m);
191 }
192 } else {
193 success = true;
194 }
195 }
196 }
197 free(keyCodes);
198 return success;
199}
200
201bool KGlobalAccelImpl::nativeEventFilter(const QByteArray &eventType, void *message, long *)
202{
203 if (eventType != "xcb_generic_event_t") {
204 return false;
205 }
206 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t*>(message);
207 const uint8_t responseType = event->response_type & ~0x80;
208 if (responseType == XCB_MAPPING_NOTIFY) {
209 x11MappingNotify();
210
211 // Make sure to let Qt handle it as well
212 return false;
213 } else if (responseType == XCB_KEY_PRESS) {
214#ifdef KDEDGLOBALACCEL_TRACE
215 qCDebug(KGLOBALACCELD) << "Got XKeyPress event";
216#endif
217 return x11KeyPress(reinterpret_cast<xcb_key_press_event_t*>(event));
218 } else if (m_xkb_first_event && responseType == m_xkb_first_event) {
219 const uint8_t xkbEvent = event->pad0;
220 switch (xkbEvent) {
221 case XCB_XKB_MAP_NOTIFY:
222 x11MappingNotify();
223 break;
224 case XCB_XKB_NEW_KEYBOARD_NOTIFY: {
225 const xcb_xkb_new_keyboard_notify_event_t *ev =
226 reinterpret_cast<xcb_xkb_new_keyboard_notify_event_t*>(event);
227 if (ev->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
228 x11MappingNotify();
229 break;
230 }
231 default:
232 break;
233 }
234
235 // Make sure to let Qt handle it as well
236 return false;
237 } else {
238 // We get all XEvents. Just ignore them.
239 return false;
240 }
241}
242
243void KGlobalAccelImpl::x11MappingNotify()
244{
245 qCDebug(KGLOBALACCELD) << "Got XMappingNotify event";
246
247 // Maybe the X modifier map has been changed.
248 // uint oldKeyModMaskXAccel = g_keyModMaskXAccel;
249 // uint oldKeyModMaskXOnOrOff = g_keyModMaskXOnOrOff;
250
251 // First ungrab all currently grabbed keys. This is needed because we
252 // store the keys as qt keycodes and use KKeyServer to map them to x11 key
253 // codes. After calling KKeyServer::initializeMods() they could map to
254 // different keycodes.
255 ungrabKeys();
256
257 if (m_keySymbols) {
258 // Force reloading of the keySym mapping
259 xcb_key_symbols_free(m_keySymbols);
260 m_keySymbols = nullptr;
261 }
262
263 KKeyServer::initializeMods();
264 calculateGrabMasks();
265
266 grabKeys();
267}
268
269bool KGlobalAccelImpl::x11KeyPress(xcb_key_press_event_t *pEvent)
270{
271 if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) {
272 qCWarning(KGLOBALACCELD) << "kglobalacceld should be popup and keyboard grabbing free!";
273 }
274
275 // Keyboard needs to be ungrabed after XGrabKey() activates the grab,
276 // otherwise it becomes frozen.
277 xcb_connection_t *c = QX11Info::connection();
278 xcb_void_cookie_t cookie = xcb_ungrab_keyboard_checked(c, XCB_TIME_CURRENT_TIME);
279 xcb_flush(c);
280 // xcb_flush() only makes sure that the ungrab keyboard request has been
281 // sent, but is not enough to make sure that request has been fulfilled. Use
282 // xcb_request_check() to make sure that the request has been processed.
283 xcb_request_check(c, cookie);
284
285 int keyQt;
286 if (!KKeyServer::xcbKeyPressEventToQt(pEvent, &keyQt)) {
287 qCWarning(KGLOBALACCELD) << "KKeyServer::xcbKeyPressEventToQt failed";
288 return false;
289 }
290 //qDebug() << "keyQt=" << QString::number(keyQt, 16);
291
292 // All that work for this hey... argh...
293 if (NET::timestampCompare(pEvent->time, QX11Info::appTime()) > 0) {
294 QX11Info::setAppTime(pEvent->time);
295 }
296 return keyPressed(keyQt);
297}
298
299void KGlobalAccelImpl::setEnabled( bool enable )
300{
301 if (enable && qApp->platformName() == QLatin1String("xcb")) {
302 qApp->installNativeEventFilter(this);
303 } else {
304 qApp->removeNativeEventFilter(this);
305 }
306}
307
308void KGlobalAccelImpl::syncX()
309{
310 xcb_connection_t *c = QX11Info::connection();
311 auto *value = xcb_get_input_focus_reply(c, xcb_get_input_focus_unchecked(c), nullptr);
312 free(value);
313}
314
315