1/*
2 SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
3 SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "kmodifierkeyinfoprovider_xcb.h"
9#include "kmodifierkeyinfo.h"
10
11#include <QGuiApplication>
12
13#define XK_MISCELLANY
14#define XK_XKB_KEYS
15#include <X11/XKBlib.h>
16#include <X11/keysymdef.h>
17#include <xcb/xcb.h>
18
19struct ModifierDefinition {
20 ModifierDefinition(Qt::Key _key, unsigned int _mask, const char *_name, KeySym _keysym)
21 : key(_key)
22 , mask(_mask)
23 , name(_name)
24 , keysym(_keysym)
25 {
26 }
27 Qt::Key key;
28 unsigned int mask;
29 const char *name; // virtual modifier name
30 KeySym keysym;
31};
32
33/*
34 * Get the real modifiers related to a virtual modifier.
35 */
36unsigned int xkbVirtualModifier(XkbDescPtr xkb, const char *name)
37{
38 Q_ASSERT(xkb != nullptr);
39
40 unsigned int mask = 0;
41 bool nameEqual;
42 for (int i = 0; i < XkbNumVirtualMods; ++i) {
43 char *modStr = XGetAtomName(xkb->dpy, xkb->names->vmods[i]);
44 if (modStr != nullptr) {
45 nameEqual = (strcmp(s1: name, s2: modStr) == 0);
46 XFree(modStr);
47 if (nameEqual) {
48 XkbVirtualModsToReal(xkb, 1 << i, &mask);
49 break;
50 }
51 }
52 }
53 return mask;
54}
55
56static Display *display()
57{
58 return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display();
59}
60
61KModifierKeyInfoProviderXcb::KModifierKeyInfoProviderXcb()
62 : KModifierKeyInfoProvider()
63 , m_xkbEv(0)
64 , m_xkbAvailable(false)
65{
66 if (qGuiApp) {
67 if (qGuiApp->platformName() == QLatin1String("xcb")) {
68 int code;
69 int xkberr;
70 int maj;
71 int min;
72 m_xkbAvailable = XkbQueryExtension(display(), &code, &m_xkbEv, &xkberr, &maj, &min);
73 }
74 }
75 if (m_xkbAvailable) {
76 /* clang-format off */
77 XkbSelectEvents(display(),
78 XkbUseCoreKbd,
79 XkbStateNotifyMask | XkbMapNotifyMask,
80 XkbStateNotifyMask | XkbMapNotifyMask);
81
82 unsigned long int stateMask = XkbModifierStateMask
83 | XkbModifierBaseMask
84 | XkbModifierLatchMask
85 | XkbModifierLockMask
86 | XkbPointerButtonMask;
87 /* clang-format on */
88
89 XkbSelectEventDetails(display(), XkbUseCoreKbd, XkbStateNotifyMask, stateMask, stateMask);
90 }
91
92 xkbUpdateModifierMapping();
93
94 // add known pointer buttons
95 m_xkbButtons.insert(key: Qt::LeftButton, Button1Mask);
96 m_xkbButtons.insert(key: Qt::MiddleButton, Button2Mask);
97 m_xkbButtons.insert(key: Qt::RightButton, Button3Mask);
98 m_xkbButtons.insert(key: Qt::XButton1, Button4Mask);
99 m_xkbButtons.insert(key: Qt::XButton2, Button5Mask);
100
101 // get the initial state
102 if (m_xkbAvailable) {
103 XkbStateRec state;
104 XkbGetState(display(), XkbUseCoreKbd, &state);
105 xkbModifierStateChanged(mods: state.mods, latched_mods: state.latched_mods, locked_mods: state.locked_mods);
106 xkbButtonStateChanged(ptr_buttons: state.ptr_buttons);
107
108 QCoreApplication::instance()->installNativeEventFilter(filterObj: this);
109 }
110}
111
112KModifierKeyInfoProviderXcb::~KModifierKeyInfoProviderXcb()
113{
114 if (m_xkbAvailable) {
115 QCoreApplication::instance()->removeNativeEventFilter(filterObj: this);
116 }
117}
118
119bool KModifierKeyInfoProviderXcb::setKeyLatched(Qt::Key key, bool latched)
120{
121 if (!m_xkbModifiers.contains(key)) {
122 return false;
123 }
124
125 return XkbLatchModifiers(display(), XkbUseCoreKbd, m_xkbModifiers[key], latched ? m_xkbModifiers[key] : 0);
126}
127
128bool KModifierKeyInfoProviderXcb::setKeyLocked(Qt::Key key, bool locked)
129{
130 if (!m_xkbModifiers.contains(key)) {
131 return false;
132 }
133
134 return XkbLockModifiers(display(), XkbUseCoreKbd, m_xkbModifiers[key], locked ? m_xkbModifiers[key] : 0);
135}
136
137// HACK: xcb-xkb is not yet a public part of xcb. Because of that we have to include the event structure.
138namespace
139{
140typedef struct _xcb_xkb_map_notify_event_t {
141 uint8_t response_type;
142 uint8_t xkbType;
143 uint16_t sequence;
144 xcb_timestamp_t time;
145 uint8_t deviceID;
146 uint8_t ptrBtnActions;
147 uint16_t changed;
148 xcb_keycode_t minKeyCode;
149 xcb_keycode_t maxKeyCode;
150 uint8_t firstType;
151 uint8_t nTypes;
152 xcb_keycode_t firstKeySym;
153 uint8_t nKeySyms;
154 xcb_keycode_t firstKeyAct;
155 uint8_t nKeyActs;
156 xcb_keycode_t firstKeyBehavior;
157 uint8_t nKeyBehavior;
158 xcb_keycode_t firstKeyExplicit;
159 uint8_t nKeyExplicit;
160 xcb_keycode_t firstModMapKey;
161 uint8_t nModMapKeys;
162 xcb_keycode_t firstVModMapKey;
163 uint8_t nVModMapKeys;
164 uint16_t virtualMods;
165 uint8_t pad0[2];
166} _xcb_xkb_map_notify_event_t;
167typedef struct _xcb_xkb_state_notify_event_t {
168 uint8_t response_type;
169 uint8_t xkbType;
170 uint16_t sequence;
171 xcb_timestamp_t time;
172 uint8_t deviceID;
173 uint8_t mods;
174 uint8_t baseMods;
175 uint8_t latchedMods;
176 uint8_t lockedMods;
177 uint8_t group;
178 int16_t baseGroup;
179 int16_t latchedGroup;
180 uint8_t lockedGroup;
181 uint8_t compatState;
182 uint8_t grabMods;
183 uint8_t compatGrabMods;
184 uint8_t lookupMods;
185 uint8_t compatLoockupMods;
186 uint16_t ptrBtnState;
187 uint16_t changed;
188 xcb_keycode_t keycode;
189 uint8_t eventType;
190 uint8_t requestMajor;
191 uint8_t requestMinor;
192} _xcb_xkb_state_notify_event_t;
193typedef union {
194 /* All XKB events share these fields. */
195 struct {
196 uint8_t response_type;
197 uint8_t xkbType;
198 uint16_t sequence;
199 xcb_timestamp_t time;
200 uint8_t deviceID;
201 } any;
202 _xcb_xkb_map_notify_event_t map_notify;
203 _xcb_xkb_state_notify_event_t state_notify;
204} _xkb_event;
205}
206
207bool KModifierKeyInfoProviderXcb::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
208{
209 if (!m_xkbAvailable || eventType != "xcb_generic_event_t") {
210 return false;
211 }
212 xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
213 if ((event->response_type & ~0x80) == m_xkbEv + XkbEventCode) {
214 _xkb_event *kbevt = reinterpret_cast<_xkb_event *>(event);
215 unsigned int stateMask = XkbModifierStateMask | XkbModifierBaseMask | XkbModifierLatchMask | XkbModifierLockMask;
216 if (kbevt->any.xkbType == XkbMapNotify) {
217 xkbUpdateModifierMapping();
218 } else if (kbevt->any.xkbType == XkbStateNotify) {
219 if (kbevt->state_notify.changed & stateMask) {
220 xkbModifierStateChanged(mods: kbevt->state_notify.mods, latched_mods: kbevt->state_notify.latchedMods, locked_mods: kbevt->state_notify.lockedMods);
221 } else if (kbevt->state_notify.changed & XkbPointerButtonMask) {
222 xkbButtonStateChanged(ptr_buttons: kbevt->state_notify.ptrBtnState);
223 }
224 }
225 }
226 return false;
227}
228
229void KModifierKeyInfoProviderXcb::xkbModifierStateChanged(unsigned char mods, unsigned char latched_mods, unsigned char locked_mods)
230{
231 // detect keyboard modifiers
232 ModifierStates newState;
233
234 QHash<Qt::Key, unsigned int>::const_iterator it;
235 QHash<Qt::Key, unsigned int>::const_iterator end = m_xkbModifiers.constEnd();
236 for (it = m_xkbModifiers.constBegin(); it != end; ++it) {
237 if (!m_modifierStates.contains(key: it.key())) {
238 continue;
239 }
240 newState = Nothing;
241
242 // determine the new state
243 if (mods & it.value()) {
244 newState |= Pressed;
245 }
246 if (latched_mods & it.value()) {
247 newState |= Latched;
248 }
249 if (locked_mods & it.value()) {
250 newState |= Locked;
251 }
252
253 stateUpdated(key: it.key(), state: newState);
254 }
255}
256
257void KModifierKeyInfoProviderXcb::xkbButtonStateChanged(unsigned short ptr_buttons)
258{
259 // detect mouse button states
260 bool newButtonState;
261
262 QHash<Qt::MouseButton, unsigned short>::const_iterator it;
263 QHash<Qt::MouseButton, unsigned short>::const_iterator end = m_xkbButtons.constEnd();
264 for (it = m_xkbButtons.constBegin(); it != end; ++it) {
265 newButtonState = (ptr_buttons & it.value());
266 if (newButtonState != m_buttonStates[it.key()]) {
267 m_buttonStates[it.key()] = newButtonState;
268 Q_EMIT buttonPressed(button: it.key(), state: newButtonState);
269 }
270 }
271}
272
273void KModifierKeyInfoProviderXcb::xkbUpdateModifierMapping()
274{
275 if (!m_xkbAvailable) {
276 return;
277 }
278 m_xkbModifiers.clear();
279
280 QList<ModifierDefinition> srcModifiers;
281 /* clang-format off */
282 srcModifiers << ModifierDefinition(Qt::Key_Shift, ShiftMask, nullptr, 0)
283 << ModifierDefinition(Qt::Key_Control, ControlMask, nullptr, 0)
284 << ModifierDefinition(Qt::Key_Alt, 0, "Alt", XK_Alt_L)
285 // << { 0, 0, I18N_NOOP("Win"), "superkey", "" }
286 << ModifierDefinition(Qt::Key_Meta, 0, "Meta", XK_Meta_L)
287 << ModifierDefinition(Qt::Key_Super_L, 0, "Super", XK_Super_L)
288 << ModifierDefinition(Qt::Key_Hyper_L, 0, "Hyper", XK_Hyper_L)
289 << ModifierDefinition(Qt::Key_AltGr, 0, "AltGr", 0)
290 << ModifierDefinition(Qt::Key_NumLock, 0, "NumLock", XK_Num_Lock)
291 << ModifierDefinition(Qt::Key_CapsLock, LockMask, nullptr, 0)
292 << ModifierDefinition(Qt::Key_ScrollLock, 0, "ScrollLock", XK_Scroll_Lock);
293 /* clang-format on */
294
295 XkbDescPtr xkb = XkbGetKeyboard(display(), XkbAllComponentsMask, XkbUseCoreKbd);
296
297 QList<ModifierDefinition>::const_iterator it;
298 QList<ModifierDefinition>::const_iterator end = srcModifiers.constEnd();
299 for (it = srcModifiers.constBegin(); it != end; ++it) {
300 unsigned int mask = it->mask;
301 if (mask == 0 && xkb != nullptr) {
302 // try virtual modifier first
303 if (it->name != nullptr) {
304 mask = xkbVirtualModifier(xkb, name: it->name);
305 }
306 if (mask == 0 && it->keysym != 0) {
307 mask = XkbKeysymToModifiers(display(), it->keysym);
308 } else if (mask == 0) {
309 // special case for AltGr
310 /* clang-format off */
311 mask = XkbKeysymToModifiers(display(), XK_Mode_switch)
312 | XkbKeysymToModifiers(display(), XK_ISO_Level3_Shift)
313 | XkbKeysymToModifiers(display(), XK_ISO_Level3_Latch)
314 | XkbKeysymToModifiers(display(), XK_ISO_Level3_Lock);
315 /* clang-format on */
316 }
317 }
318
319 if (mask != 0) {
320 m_xkbModifiers.insert(key: it->key, value: mask);
321 // previously unknown modifier
322 if (!m_modifierStates.contains(key: it->key)) {
323 m_modifierStates.insert(key: it->key, value: Nothing);
324 Q_EMIT keyAdded(key: it->key);
325 }
326 }
327 }
328
329 // remove modifiers which are no longer available
330 QMutableHashIterator<Qt::Key, ModifierStates> i(m_modifierStates);
331 while (i.hasNext()) {
332 i.next();
333 if (!m_xkbModifiers.contains(key: i.key())) {
334 Qt::Key key = i.key();
335 i.remove();
336 Q_EMIT keyRemoved(key);
337 }
338 }
339
340 if (xkb != nullptr) {
341 XkbFreeKeyboard(xkb, 0, true);
342 }
343}
344
345#include "moc_kmodifierkeyinfoprovider_xcb.cpp"
346

source code of kguiaddons/src/util/kmodifierkeyinfoprovider_xcb.cpp