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 | |
19 | struct 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 | */ |
36 | unsigned 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 | |
56 | static Display *display() |
57 | { |
58 | return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display(); |
59 | } |
60 | |
61 | KModifierKeyInfoProviderXcb::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 | |
112 | KModifierKeyInfoProviderXcb::~KModifierKeyInfoProviderXcb() |
113 | { |
114 | if (m_xkbAvailable) { |
115 | QCoreApplication::instance()->removeNativeEventFilter(filterObj: this); |
116 | } |
117 | } |
118 | |
119 | bool 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 | |
128 | bool 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. |
138 | namespace |
139 | { |
140 | typedef 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; |
167 | typedef 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; |
193 | typedef 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 | |
207 | bool 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 | |
229 | void 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 | |
257 | void 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 | |
273 | void 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 | |