KGlobalAccel

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

KDE's Doxygen guidelines are available online.