1 | // Copyright (C) 2016 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | #include "qxcbkeyboard.h" |
4 | #include "qxcbwindow.h" |
5 | #include "qxcbscreen.h" |
6 | #include "qxcbcursor.h" |
7 | |
8 | #include <qpa/qwindowsysteminterface.h> |
9 | #include <qpa/qplatforminputcontext.h> |
10 | #include <qpa/qplatformintegration.h> |
11 | #include <qpa/qplatformcursor.h> |
12 | |
13 | #include <QtCore/QMetaEnum> |
14 | |
15 | #include <private/qguiapplication_p.h> |
16 | |
17 | #include <xcb/xinput.h> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Qt::KeyboardModifiers QXcbKeyboard::translateModifiers(int s) const |
22 | { |
23 | Qt::KeyboardModifiers ret = Qt::NoModifier; |
24 | if (s & XCB_MOD_MASK_SHIFT) |
25 | ret |= Qt::ShiftModifier; |
26 | if (s & XCB_MOD_MASK_CONTROL) |
27 | ret |= Qt::ControlModifier; |
28 | if (s & rmod_masks.alt) |
29 | ret |= Qt::AltModifier; |
30 | if (s & rmod_masks.meta) |
31 | ret |= Qt::MetaModifier; |
32 | if (s & rmod_masks.altgr) |
33 | ret |= Qt::GroupSwitchModifier; |
34 | return ret; |
35 | } |
36 | |
37 | /* Look at a pair of unshifted and shifted key symbols. |
38 | * If the 'unshifted' symbol is uppercase and there is no shifted symbol, |
39 | * return the matching lowercase symbol; otherwise return 0. |
40 | * The caller can then use the previously 'unshifted' symbol as the new |
41 | * 'shifted' (uppercase) symbol and the symbol returned by the function |
42 | * as the new 'unshifted' (lowercase) symbol.) */ |
43 | static xcb_keysym_t getUnshiftedXKey(xcb_keysym_t unshifted, xcb_keysym_t shifted) |
44 | { |
45 | if (shifted != XKB_KEY_NoSymbol) // Has a shifted symbol |
46 | return 0; |
47 | |
48 | xcb_keysym_t xlower; |
49 | xcb_keysym_t xupper; |
50 | QXkbCommon::xkbcommon_XConvertCase(sym: unshifted, lower: &xlower, upper: &xupper); |
51 | |
52 | if (xlower != xupper // Check if symbol is cased |
53 | && unshifted == xupper) { // Unshifted must be upper case |
54 | return xlower; |
55 | } |
56 | |
57 | return 0; |
58 | } |
59 | |
60 | static QByteArray symbolsGroupString(const xcb_keysym_t *symbols, int count) |
61 | { |
62 | // Don't output trailing NoSymbols |
63 | while (count > 0 && symbols[count - 1] == XKB_KEY_NoSymbol) |
64 | count--; |
65 | |
66 | QByteArray groupString; |
67 | for (int symIndex = 0; symIndex < count; symIndex++) { |
68 | xcb_keysym_t sym = symbols[symIndex]; |
69 | char symString[64]; |
70 | if (sym == XKB_KEY_NoSymbol) |
71 | strcpy(dest: symString, src: "NoSymbol"); |
72 | else |
73 | xkb_keysym_get_name(keysym: sym, buffer: symString, size: sizeof(symString)); |
74 | |
75 | if (!groupString.isEmpty()) |
76 | groupString += ", "; |
77 | groupString += symString; |
78 | } |
79 | return groupString; |
80 | } |
81 | |
82 | struct xkb_keymap *QXcbKeyboard::keymapFromCore(const KeysymModifierMap &keysymMods) |
83 | { |
84 | /* Construct an XKB keymap string from information queried from |
85 | * the X server */ |
86 | QByteArray keymap; |
87 | keymap += "xkb_keymap {\n"; |
88 | |
89 | const xcb_keycode_t minKeycode = connection()->setup()->min_keycode; |
90 | const xcb_keycode_t maxKeycode = connection()->setup()->max_keycode; |
91 | |
92 | // Generate symbolic names from keycodes |
93 | { |
94 | keymap += |
95 | "xkb_keycodes \"core\" {\n" |
96 | "\tminimum = "+ QByteArray::number(minKeycode) + ";\n" |
97 | "\tmaximum = "+ QByteArray::number(maxKeycode) + ";\n"; |
98 | for (int code = minKeycode; code <= maxKeycode; code++) { |
99 | auto codeStr = QByteArray::number(code); |
100 | keymap += "<K"+ codeStr + "> = "+ codeStr + ";\n"; |
101 | } |
102 | /* TODO: indicators? |
103 | */ |
104 | keymap += "};\n"; // xkb_keycodes |
105 | } |
106 | |
107 | /* Set up default types (xkbcommon automatically assigns these to |
108 | * symbols, but doesn't have shift info) */ |
109 | keymap += |
110 | "xkb_types \"core\" {\n" |
111 | "virtual_modifiers NumLock,Alt,LevelThree;\n" |
112 | "type \"ONE_LEVEL\" {\n" |
113 | "modifiers= none;\n" |
114 | "level_name[Level1] = \"Any\";\n" |
115 | "};\n" |
116 | "type \"TWO_LEVEL\" {\n" |
117 | "modifiers= Shift;\n" |
118 | "map[Shift]= Level2;\n" |
119 | "level_name[Level1] = \"Base\";\n" |
120 | "level_name[Level2] = \"Shift\";\n" |
121 | "};\n" |
122 | "type \"ALPHABETIC\" {\n" |
123 | "modifiers= Shift+Lock;\n" |
124 | "map[Shift]= Level2;\n" |
125 | "map[Lock]= Level2;\n" |
126 | "level_name[Level1] = \"Base\";\n" |
127 | "level_name[Level2] = \"Caps\";\n" |
128 | "};\n" |
129 | "type \"KEYPAD\" {\n" |
130 | "modifiers= Shift+NumLock;\n" |
131 | "map[Shift]= Level2;\n" |
132 | "map[NumLock]= Level2;\n" |
133 | "level_name[Level1] = \"Base\";\n" |
134 | "level_name[Level2] = \"Number\";\n" |
135 | "};\n" |
136 | "type \"FOUR_LEVEL\" {\n" |
137 | "modifiers= Shift+LevelThree;\n" |
138 | "map[Shift]= Level2;\n" |
139 | "map[LevelThree]= Level3;\n" |
140 | "map[Shift+LevelThree]= Level4;\n" |
141 | "level_name[Level1] = \"Base\";\n" |
142 | "level_name[Level2] = \"Shift\";\n" |
143 | "level_name[Level3] = \"Alt Base\";\n" |
144 | "level_name[Level4] = \"Shift Alt\";\n" |
145 | "};\n" |
146 | "type \"FOUR_LEVEL_ALPHABETIC\" {\n" |
147 | "modifiers= Shift+Lock+LevelThree;\n" |
148 | "map[Shift]= Level2;\n" |
149 | "map[Lock]= Level2;\n" |
150 | "map[LevelThree]= Level3;\n" |
151 | "map[Shift+LevelThree]= Level4;\n" |
152 | "map[Lock+LevelThree]= Level4;\n" |
153 | "map[Shift+Lock+LevelThree]= Level3;\n" |
154 | "level_name[Level1] = \"Base\";\n" |
155 | "level_name[Level2] = \"Shift\";\n" |
156 | "level_name[Level3] = \"Alt Base\";\n" |
157 | "level_name[Level4] = \"Shift Alt\";\n" |
158 | "};\n" |
159 | "type \"FOUR_LEVEL_SEMIALPHABETIC\" {\n" |
160 | "modifiers= Shift+Lock+LevelThree;\n" |
161 | "map[Shift]= Level2;\n" |
162 | "map[Lock]= Level2;\n" |
163 | "map[LevelThree]= Level3;\n" |
164 | "map[Shift+LevelThree]= Level4;\n" |
165 | "map[Lock+LevelThree]= Level3;\n" |
166 | "preserve[Lock+LevelThree]= Lock;\n" |
167 | "map[Shift+Lock+LevelThree]= Level4;\n" |
168 | "preserve[Shift+Lock+LevelThree]= Lock;\n" |
169 | "level_name[Level1] = \"Base\";\n" |
170 | "level_name[Level2] = \"Shift\";\n" |
171 | "level_name[Level3] = \"Alt Base\";\n" |
172 | "level_name[Level4] = \"Shift Alt\";\n" |
173 | "};\n" |
174 | "type \"FOUR_LEVEL_KEYPAD\" {\n" |
175 | "modifiers= Shift+NumLock+LevelThree;\n" |
176 | "map[Shift]= Level2;\n" |
177 | "map[NumLock]= Level2;\n" |
178 | "map[LevelThree]= Level3;\n" |
179 | "map[Shift+LevelThree]= Level4;\n" |
180 | "map[NumLock+LevelThree]= Level4;\n" |
181 | "map[Shift+NumLock+LevelThree]= Level3;\n" |
182 | "level_name[Level1] = \"Base\";\n" |
183 | "level_name[Level2] = \"Number\";\n" |
184 | "level_name[Level3] = \"Alt Base\";\n" |
185 | "level_name[Level4] = \"Alt Number\";\n" |
186 | "};\n" |
187 | "};\n"; // xkb_types |
188 | |
189 | // Generate mapping between symbolic names and keysyms |
190 | { |
191 | QList<xcb_keysym_t> xkeymap; |
192 | int keysymsPerKeycode = 0; |
193 | { |
194 | int keycodeCount = maxKeycode - minKeycode + 1; |
195 | if (auto keymapReply = Q_XCB_REPLY(xcb_get_keyboard_mapping, xcb_connection(), |
196 | minKeycode, keycodeCount)) { |
197 | keysymsPerKeycode = keymapReply->keysyms_per_keycode; |
198 | int numSyms = keycodeCount * keysymsPerKeycode; |
199 | auto keymapPtr = xcb_get_keyboard_mapping_keysyms(R: keymapReply.get()); |
200 | xkeymap.resize(size: numSyms); |
201 | for (int i = 0; i < numSyms; i++) |
202 | xkeymap[i] = keymapPtr[i]; |
203 | } |
204 | } |
205 | if (xkeymap.isEmpty()) |
206 | return nullptr; |
207 | |
208 | static const char *const builtinModifiers[] = |
209 | { "Shift", "Lock", "Control", "Mod1", "Mod2", "Mod3", "Mod4", "Mod5"}; |
210 | |
211 | /* Level 3 symbols (e.g. AltGr+something) seem to come in two flavors: |
212 | * - as a proper level 3 in group 1, at least on recent X.org versions |
213 | * - 'disguised' as group 2, on 'legacy' X servers |
214 | * In the 2nd case, remap group 2 to level 3, that seems to work better |
215 | * in practice */ |
216 | bool mapGroup2ToLevel3 = keysymsPerKeycode < 5; |
217 | |
218 | keymap += "xkb_symbols \"core\" {\n"; |
219 | for (int code = minKeycode; code <= maxKeycode; code++) { |
220 | auto codeMap = xkeymap.constData() + (code - minKeycode) * keysymsPerKeycode; |
221 | |
222 | const int maxGroup1 = 4; // We only support 4 shift states anyway |
223 | const int maxGroup2 = 2; // Only 3rd and 4th keysym are group 2 |
224 | xcb_keysym_t symbolsGroup1[maxGroup1]; |
225 | xcb_keysym_t symbolsGroup2[maxGroup2] = { XKB_KEY_NoSymbol, XKB_KEY_NoSymbol }; |
226 | for (int i = 0; i < maxGroup1 + maxGroup2; i++) { |
227 | xcb_keysym_t sym = i < keysymsPerKeycode ? codeMap[i] : XKB_KEY_NoSymbol; |
228 | if (mapGroup2ToLevel3) { |
229 | // Merge into single group |
230 | if (i < maxGroup1) |
231 | symbolsGroup1[i] = sym; |
232 | } else { |
233 | // Preserve groups |
234 | if (i < 2) |
235 | symbolsGroup1[i] = sym; |
236 | else if (i < 4) |
237 | symbolsGroup2[i - 2] = sym; |
238 | else |
239 | symbolsGroup1[i - 2] = sym; |
240 | } |
241 | } |
242 | |
243 | /* Fix symbols so the unshifted and shifted symbols have |
244 | * lower resp. upper case */ |
245 | if (auto lowered = getUnshiftedXKey(unshifted: symbolsGroup1[0], shifted: symbolsGroup1[1])) { |
246 | symbolsGroup1[1] = symbolsGroup1[0]; |
247 | symbolsGroup1[0] = lowered; |
248 | } |
249 | if (auto lowered = getUnshiftedXKey(unshifted: symbolsGroup2[0], shifted: symbolsGroup2[1])) { |
250 | symbolsGroup2[1] = symbolsGroup2[0]; |
251 | symbolsGroup2[0] = lowered; |
252 | } |
253 | |
254 | QByteArray groupStr1 = symbolsGroupString(symbols: symbolsGroup1, count: maxGroup1); |
255 | if (groupStr1.isEmpty()) |
256 | continue; |
257 | |
258 | keymap += "key <K"+ QByteArray::number(code) + "> { "; |
259 | keymap += "symbols[Group1] = [ "+ groupStr1 + " ]"; |
260 | QByteArray groupStr2 = symbolsGroupString(symbols: symbolsGroup2, count: maxGroup2); |
261 | if (!groupStr2.isEmpty()) |
262 | keymap += ", symbols[Group2] = [ "+ groupStr2 + " ]"; |
263 | |
264 | // See if this key code is for a modifier |
265 | xcb_keysym_t modifierSym = XKB_KEY_NoSymbol; |
266 | for (int symIndex = 0; symIndex < keysymsPerKeycode; symIndex++) { |
267 | xcb_keysym_t sym = codeMap[symIndex]; |
268 | |
269 | if (sym == XKB_KEY_Alt_L |
270 | || sym == XKB_KEY_Meta_L |
271 | || sym == XKB_KEY_Mode_switch |
272 | || sym == XKB_KEY_Super_L |
273 | || sym == XKB_KEY_Super_R |
274 | || sym == XKB_KEY_Hyper_L |
275 | || sym == XKB_KEY_Hyper_R) { |
276 | modifierSym = sym; |
277 | break; |
278 | } |
279 | } |
280 | |
281 | // AltGr |
282 | if (modifierSym == XKB_KEY_Mode_switch) |
283 | keymap += ", virtualMods=LevelThree"; |
284 | keymap += " };\n"; // key |
285 | |
286 | // Generate modifier mappings |
287 | int modNum = keysymMods.value(key: modifierSym, defaultValue: -1); |
288 | if (modNum != -1) { |
289 | // Here modNum is always < 8 (see keysymsToModifiers()) |
290 | keymap += QByteArray("modifier_map ") + builtinModifiers[modNum] |
291 | + " { <K"+ QByteArray::number(code) + "> };\n"; |
292 | } |
293 | } |
294 | // TODO: indicators? |
295 | keymap += "};\n"; // xkb_symbols |
296 | } |
297 | |
298 | // We need an "Alt" modifier, provide via the xkb_compatibility section |
299 | keymap += |
300 | "xkb_compatibility \"core\" {\n" |
301 | "virtual_modifiers NumLock,Alt,LevelThree;\n" |
302 | "interpret Alt_L+AnyOf(all) {\n" |
303 | "virtualModifier= Alt;\n" |
304 | "action= SetMods(modifiers=modMapMods,clearLocks);\n" |
305 | "};\n" |
306 | "interpret Alt_R+AnyOf(all) {\n" |
307 | "virtualModifier= Alt;\n" |
308 | "action= SetMods(modifiers=modMapMods,clearLocks);\n" |
309 | "};\n" |
310 | "};\n"; |
311 | |
312 | /* TODO: There is an issue with modifier state not being handled |
313 | * correctly if using Xming with XKEYBOARD disabled. */ |
314 | |
315 | keymap += "};\n"; // xkb_keymap |
316 | |
317 | return xkb_keymap_new_from_buffer(context: m_xkbContext.get(), |
318 | buffer: keymap.constData(), |
319 | length: keymap.size(), |
320 | format: XKB_KEYMAP_FORMAT_TEXT_V1, |
321 | flags: XKB_KEYMAP_COMPILE_NO_FLAGS); |
322 | } |
323 | |
324 | void QXcbKeyboard::updateKeymap(xcb_mapping_notify_event_t *event) |
325 | { |
326 | if (connection()->hasXKB() || event->request == XCB_MAPPING_POINTER) |
327 | return; |
328 | |
329 | xcb_refresh_keyboard_mapping(syms: m_key_symbols, event); |
330 | updateKeymap(); |
331 | } |
332 | |
333 | void QXcbKeyboard::updateKeymap() |
334 | { |
335 | KeysymModifierMap keysymMods; |
336 | if (!connection()->hasXKB()) |
337 | keysymMods = keysymsToModifiers(); |
338 | updateModifiers(keysymMods); |
339 | |
340 | m_config = true; |
341 | |
342 | if (!m_xkbContext) { |
343 | m_xkbContext.reset(p: xkb_context_new(flags: XKB_CONTEXT_NO_DEFAULT_INCLUDES)); |
344 | if (!m_xkbContext) { |
345 | qCWarning(lcQpaKeyboard, "failed to create XKB context"); |
346 | m_config = false; |
347 | return; |
348 | } |
349 | xkb_log_level logLevel = lcQpaKeyboard().isDebugEnabled() ? |
350 | XKB_LOG_LEVEL_DEBUG : XKB_LOG_LEVEL_CRITICAL; |
351 | xkb_context_set_log_level(context: m_xkbContext.get(), level: logLevel); |
352 | } |
353 | |
354 | if (connection()->hasXKB()) { |
355 | m_xkbKeymap.reset(p: xkb_x11_keymap_new_from_device(context: m_xkbContext.get(), connection: xcb_connection(), |
356 | device_id: core_device_id, flags: XKB_KEYMAP_COMPILE_NO_FLAGS)); |
357 | if (m_xkbKeymap) |
358 | m_xkbState.reset(p: xkb_x11_state_new_from_device(keymap: m_xkbKeymap.get(), connection: xcb_connection(), device_id: core_device_id)); |
359 | } else { |
360 | m_xkbKeymap.reset(p: keymapFromCore(keysymMods)); |
361 | if (m_xkbKeymap) |
362 | m_xkbState.reset(p: xkb_state_new(keymap: m_xkbKeymap.get())); |
363 | } |
364 | |
365 | if (!m_xkbKeymap) { |
366 | qCWarning(lcQpaKeyboard, "failed to compile a keymap"); |
367 | m_config = false; |
368 | return; |
369 | } |
370 | if (!m_xkbState) { |
371 | qCWarning(lcQpaKeyboard, "failed to create XKB state"); |
372 | m_config = false; |
373 | return; |
374 | } |
375 | |
376 | updateXKBMods(); |
377 | |
378 | QXkbCommon::verifyHasLatinLayout(keymap: m_xkbKeymap.get()); |
379 | } |
380 | |
381 | QList<QKeyCombination> QXcbKeyboard::possibleKeyCombinations(const QKeyEvent *event) const |
382 | { |
383 | return QXkbCommon::possibleKeyCombinations( |
384 | state: m_xkbState.get(), event, superAsMeta: m_superAsMeta, hyperAsMeta: m_hyperAsMeta); |
385 | } |
386 | |
387 | Qt::KeyboardModifiers QXcbKeyboard::queryKeyboardModifiers() const |
388 | { |
389 | // FIXME: Should we base this on m_xkbState? |
390 | int stateMask = 0; |
391 | QXcbCursor::queryPointer(c: connection(), virtualDesktop: nullptr, pos: nullptr, keybMask: &stateMask); |
392 | return translateModifiers(s: stateMask); |
393 | } |
394 | |
395 | void QXcbKeyboard::updateXKBState(xcb_xkb_state_notify_event_t *state) |
396 | { |
397 | if (m_config && connection()->hasXKB()) { |
398 | const xkb_state_component changedComponents |
399 | = xkb_state_update_mask(state: m_xkbState.get(), |
400 | depressed_mods: state->baseMods, |
401 | latched_mods: state->latchedMods, |
402 | locked_mods: state->lockedMods, |
403 | depressed_layout: state->baseGroup, |
404 | latched_layout: state->latchedGroup, |
405 | locked_layout: state->lockedGroup); |
406 | |
407 | handleStateChanges(changedComponents); |
408 | } |
409 | } |
410 | |
411 | static xkb_layout_index_t lockedGroup(quint16 state) |
412 | { |
413 | return (state >> 13) & 3; // bits 13 and 14 report the state keyboard group |
414 | } |
415 | |
416 | void QXcbKeyboard::updateXKBStateFromCore(quint16 state) |
417 | { |
418 | if (m_config) { |
419 | struct xkb_state *xkbState = m_xkbState.get(); |
420 | xkb_mod_mask_t modsDepressed = xkb_state_serialize_mods(state: xkbState, components: XKB_STATE_MODS_DEPRESSED); |
421 | xkb_mod_mask_t modsLatched = xkb_state_serialize_mods(state: xkbState, components: XKB_STATE_MODS_LATCHED); |
422 | xkb_mod_mask_t modsLocked = xkb_state_serialize_mods(state: xkbState, components: XKB_STATE_MODS_LOCKED); |
423 | xkb_mod_mask_t xkbMask = xkbModMask(state); |
424 | |
425 | xkb_mod_mask_t latched = modsLatched & xkbMask; |
426 | xkb_mod_mask_t locked = modsLocked & xkbMask; |
427 | xkb_mod_mask_t depressed = modsDepressed & xkbMask; |
428 | // set modifiers in depressed if they don't appear in any of the final masks |
429 | depressed |= ~(depressed | latched | locked) & xkbMask; |
430 | |
431 | xkb_state_component changedComponents = xkb_state_update_mask( |
432 | state: xkbState, depressed_mods: depressed, latched_mods: latched, locked_mods: locked, depressed_layout: 0, latched_layout: 0, locked_layout: lockedGroup(state)); |
433 | |
434 | handleStateChanges(changedComponents); |
435 | } |
436 | } |
437 | |
438 | void QXcbKeyboard::updateXKBStateFromXI(void *modInfo, void *groupInfo) |
439 | { |
440 | if (m_config) { |
441 | auto *mods = static_cast<xcb_input_modifier_info_t *>(modInfo); |
442 | auto *group = static_cast<xcb_input_group_info_t *>(groupInfo); |
443 | const xkb_state_component changedComponents |
444 | = xkb_state_update_mask(state: m_xkbState.get(), |
445 | depressed_mods: mods->base, |
446 | latched_mods: mods->latched, |
447 | locked_mods: mods->locked, |
448 | depressed_layout: group->base, |
449 | latched_layout: group->latched, |
450 | locked_layout: group->locked); |
451 | |
452 | handleStateChanges(changedComponents); |
453 | } |
454 | } |
455 | |
456 | void QXcbKeyboard::handleStateChanges(xkb_state_component changedComponents) |
457 | { |
458 | // Note: Ubuntu (with Unity) always creates a new keymap when layout is changed |
459 | // via system settings, which means that the layout change would not be detected |
460 | // by this code. That can be solved by emitting KeyboardLayoutChange also from updateKeymap(). |
461 | if ((changedComponents & XKB_STATE_LAYOUT_EFFECTIVE) == XKB_STATE_LAYOUT_EFFECTIVE) |
462 | qCDebug(lcQpaKeyboard, "TODO: Support KeyboardLayoutChange on QPA (QTBUG-27681)"); |
463 | } |
464 | |
465 | xkb_mod_mask_t QXcbKeyboard::xkbModMask(quint16 state) |
466 | { |
467 | xkb_mod_mask_t xkb_mask = 0; |
468 | |
469 | if ((state & XCB_MOD_MASK_SHIFT) && xkb_mods.shift != XKB_MOD_INVALID) |
470 | xkb_mask |= (1 << xkb_mods.shift); |
471 | if ((state & XCB_MOD_MASK_LOCK) && xkb_mods.lock != XKB_MOD_INVALID) |
472 | xkb_mask |= (1 << xkb_mods.lock); |
473 | if ((state & XCB_MOD_MASK_CONTROL) && xkb_mods.control != XKB_MOD_INVALID) |
474 | xkb_mask |= (1 << xkb_mods.control); |
475 | if ((state & XCB_MOD_MASK_1) && xkb_mods.mod1 != XKB_MOD_INVALID) |
476 | xkb_mask |= (1 << xkb_mods.mod1); |
477 | if ((state & XCB_MOD_MASK_2) && xkb_mods.mod2 != XKB_MOD_INVALID) |
478 | xkb_mask |= (1 << xkb_mods.mod2); |
479 | if ((state & XCB_MOD_MASK_3) && xkb_mods.mod3 != XKB_MOD_INVALID) |
480 | xkb_mask |= (1 << xkb_mods.mod3); |
481 | if ((state & XCB_MOD_MASK_4) && xkb_mods.mod4 != XKB_MOD_INVALID) |
482 | xkb_mask |= (1 << xkb_mods.mod4); |
483 | if ((state & XCB_MOD_MASK_5) && xkb_mods.mod5 != XKB_MOD_INVALID) |
484 | xkb_mask |= (1 << xkb_mods.mod5); |
485 | |
486 | return xkb_mask; |
487 | } |
488 | |
489 | void QXcbKeyboard::updateXKBMods() |
490 | { |
491 | xkb_mods.shift = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), XKB_MOD_NAME_SHIFT); |
492 | xkb_mods.lock = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), XKB_MOD_NAME_CAPS); |
493 | xkb_mods.control = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), XKB_MOD_NAME_CTRL); |
494 | xkb_mods.mod1 = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), name: "Mod1"); |
495 | xkb_mods.mod2 = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), name: "Mod2"); |
496 | xkb_mods.mod3 = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), name: "Mod3"); |
497 | xkb_mods.mod4 = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), name: "Mod4"); |
498 | xkb_mods.mod5 = xkb_keymap_mod_get_index(keymap: m_xkbKeymap.get(), name: "Mod5"); |
499 | } |
500 | |
501 | QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection) |
502 | : QXcbObject(connection) |
503 | { |
504 | core_device_id = 0; |
505 | if (connection->hasXKB()) { |
506 | selectEvents(); |
507 | core_device_id = xkb_x11_get_core_keyboard_device_id(connection: xcb_connection()); |
508 | if (core_device_id == -1) { |
509 | qCWarning(lcQpaXcb, "failed to get core keyboard device info"); |
510 | return; |
511 | } |
512 | } else { |
513 | m_key_symbols = xcb_key_symbols_alloc(c: xcb_connection()); |
514 | } |
515 | |
516 | updateKeymap(); |
517 | } |
518 | |
519 | QXcbKeyboard::~QXcbKeyboard() |
520 | { |
521 | if (m_key_symbols) |
522 | xcb_key_symbols_free(syms: m_key_symbols); |
523 | } |
524 | |
525 | void QXcbKeyboard::initialize() |
526 | { |
527 | auto inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); |
528 | QXkbCommon::setXkbContext(inputContext, context: m_xkbContext.get()); |
529 | } |
530 | |
531 | void QXcbKeyboard::selectEvents() |
532 | { |
533 | const uint16_t required_map_parts = (XCB_XKB_MAP_PART_KEY_TYPES | |
534 | XCB_XKB_MAP_PART_KEY_SYMS | |
535 | XCB_XKB_MAP_PART_MODIFIER_MAP | |
536 | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | |
537 | XCB_XKB_MAP_PART_KEY_ACTIONS | |
538 | XCB_XKB_MAP_PART_KEY_BEHAVIORS | |
539 | XCB_XKB_MAP_PART_VIRTUAL_MODS | |
540 | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP); |
541 | |
542 | const uint16_t required_events = (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | |
543 | XCB_XKB_EVENT_TYPE_MAP_NOTIFY | |
544 | XCB_XKB_EVENT_TYPE_STATE_NOTIFY); |
545 | |
546 | // XKB events are reported to all interested clients without regard |
547 | // to the current keyboard input focus or grab state |
548 | xcb_void_cookie_t select = xcb_xkb_select_events_checked( |
549 | c: xcb_connection(), |
550 | deviceSpec: XCB_XKB_ID_USE_CORE_KBD, |
551 | affectWhich: required_events, |
552 | clear: 0, |
553 | selectAll: required_events, |
554 | affectMap: required_map_parts, |
555 | map: required_map_parts, |
556 | details: nullptr); |
557 | |
558 | xcb_generic_error_t *error = xcb_request_check(c: xcb_connection(), cookie: select); |
559 | if (error) { |
560 | free(ptr: error); |
561 | qCWarning(lcQpaXcb, "failed to select notify events from XKB"); |
562 | } |
563 | } |
564 | |
565 | void QXcbKeyboard::updateVModMapping() |
566 | { |
567 | xcb_xkb_get_names_value_list_t names_list; |
568 | |
569 | memset(s: &vmod_masks, c: 0, n: sizeof(vmod_masks)); |
570 | |
571 | auto name_reply = Q_XCB_REPLY(xcb_xkb_get_names, xcb_connection(), |
572 | XCB_XKB_ID_USE_CORE_KBD, |
573 | XCB_XKB_NAME_DETAIL_VIRTUAL_MOD_NAMES); |
574 | if (!name_reply) { |
575 | qWarning(msg: "Qt: failed to retrieve the virtual modifier names from XKB"); |
576 | return; |
577 | } |
578 | |
579 | const void *buffer = xcb_xkb_get_names_value_list(R: name_reply.get()); |
580 | xcb_xkb_get_names_value_list_unpack(buffer: buffer, |
581 | nTypes: name_reply->nTypes, |
582 | indicators: name_reply->indicators, |
583 | virtualMods: name_reply->virtualMods, |
584 | groupNames: name_reply->groupNames, |
585 | nKeys: name_reply->nKeys, |
586 | nKeyAliases: name_reply->nKeyAliases, |
587 | nRadioGroups: name_reply->nRadioGroups, |
588 | which: name_reply->which, |
589 | aux: &names_list); |
590 | |
591 | int count = 0; |
592 | uint vmod_mask, bit; |
593 | char *vmod_name; |
594 | vmod_mask = name_reply->virtualMods; |
595 | // find the virtual modifiers for which names are defined. |
596 | for (bit = 1; vmod_mask; bit <<= 1) { |
597 | vmod_name = nullptr; |
598 | |
599 | if (!(vmod_mask & bit)) |
600 | continue; |
601 | |
602 | vmod_mask &= ~bit; |
603 | // virtualModNames - the list of virtual modifier atoms beginning with the lowest-numbered |
604 | // virtual modifier for which a name is defined and proceeding to the highest. |
605 | QByteArray atomName = connection()->atomName(atom: names_list.virtualModNames[count]); |
606 | vmod_name = atomName.data(); |
607 | count++; |
608 | |
609 | if (!vmod_name) |
610 | continue; |
611 | |
612 | // similarly we could retrieve NumLock, Super, Hyper modifiers if needed. |
613 | if (qstrcmp(str1: vmod_name, str2: "Alt") == 0) |
614 | vmod_masks.alt = bit; |
615 | else if (qstrcmp(str1: vmod_name, str2: "Meta") == 0) |
616 | vmod_masks.meta = bit; |
617 | else if (qstrcmp(str1: vmod_name, str2: "AltGr") == 0) |
618 | vmod_masks.altgr = bit; |
619 | else if (qstrcmp(str1: vmod_name, str2: "Super") == 0) |
620 | vmod_masks.super = bit; |
621 | else if (qstrcmp(str1: vmod_name, str2: "Hyper") == 0) |
622 | vmod_masks.hyper = bit; |
623 | } |
624 | } |
625 | |
626 | void QXcbKeyboard::updateVModToRModMapping() |
627 | { |
628 | xcb_xkb_get_map_map_t map; |
629 | |
630 | memset(s: &rmod_masks, c: 0, n: sizeof(rmod_masks)); |
631 | |
632 | auto map_reply = Q_XCB_REPLY(xcb_xkb_get_map, |
633 | xcb_connection(), |
634 | XCB_XKB_ID_USE_CORE_KBD, |
635 | XCB_XKB_MAP_PART_VIRTUAL_MODS, |
636 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); |
637 | if (!map_reply) { |
638 | qWarning(msg: "Qt: failed to retrieve the virtual modifier map from XKB"); |
639 | return; |
640 | } |
641 | |
642 | const void *buffer = xcb_xkb_get_map_map(R: map_reply.get()); |
643 | xcb_xkb_get_map_map_unpack(buffer: buffer, |
644 | nTypes: map_reply->nTypes, |
645 | nKeySyms: map_reply->nKeySyms, |
646 | nKeyActions: map_reply->nKeyActions, |
647 | totalActions: map_reply->totalActions, |
648 | totalKeyBehaviors: map_reply->totalKeyBehaviors, |
649 | virtualMods: map_reply->nVModMapKeys, |
650 | totalKeyExplicit: map_reply->totalKeyExplicit, |
651 | totalModMapKeys: map_reply->totalModMapKeys, |
652 | totalVModMapKeys: map_reply->totalVModMapKeys, |
653 | present: map_reply->present, |
654 | aux: &map); |
655 | |
656 | uint vmod_mask, bit; |
657 | // the virtual modifiers mask for which a set of corresponding |
658 | // real modifiers is to be returned |
659 | vmod_mask = map_reply->virtualMods; |
660 | int count = 0; |
661 | |
662 | for (bit = 1; vmod_mask; bit <<= 1) { |
663 | uint modmap; |
664 | |
665 | if (!(vmod_mask & bit)) |
666 | continue; |
667 | |
668 | vmod_mask &= ~bit; |
669 | // real modifier bindings for the specified virtual modifiers |
670 | modmap = map.vmods_rtrn[count]; |
671 | count++; |
672 | |
673 | if (vmod_masks.alt == bit) |
674 | rmod_masks.alt = modmap; |
675 | else if (vmod_masks.meta == bit) |
676 | rmod_masks.meta = modmap; |
677 | else if (vmod_masks.altgr == bit) |
678 | rmod_masks.altgr = modmap; |
679 | else if (vmod_masks.super == bit) |
680 | rmod_masks.super = modmap; |
681 | else if (vmod_masks.hyper == bit) |
682 | rmod_masks.hyper = modmap; |
683 | } |
684 | } |
685 | |
686 | // Small helper: set modifier bit, if modifier position is valid |
687 | static inline void applyModifier(uint *mask, int modifierBit) |
688 | { |
689 | if (modifierBit >= 0 && modifierBit < 8) |
690 | *mask |= 1 << modifierBit; |
691 | } |
692 | |
693 | void QXcbKeyboard::updateModifiers(const KeysymModifierMap &keysymMods) |
694 | { |
695 | if (connection()->hasXKB()) { |
696 | updateVModMapping(); |
697 | updateVModToRModMapping(); |
698 | } else { |
699 | memset(s: &rmod_masks, c: 0, n: sizeof(rmod_masks)); |
700 | // Compute X modifier bits for Qt modifiers |
701 | applyModifier(mask: &rmod_masks.alt, modifierBit: keysymMods.value(XKB_KEY_Alt_L, defaultValue: -1)); |
702 | applyModifier(mask: &rmod_masks.alt, modifierBit: keysymMods.value(XKB_KEY_Alt_R, defaultValue: -1)); |
703 | applyModifier(mask: &rmod_masks.meta, modifierBit: keysymMods.value(XKB_KEY_Meta_L, defaultValue: -1)); |
704 | applyModifier(mask: &rmod_masks.meta, modifierBit: keysymMods.value(XKB_KEY_Meta_R, defaultValue: -1)); |
705 | applyModifier(mask: &rmod_masks.altgr, modifierBit: keysymMods.value(XKB_KEY_Mode_switch, defaultValue: -1)); |
706 | applyModifier(mask: &rmod_masks.super, modifierBit: keysymMods.value(XKB_KEY_Super_L, defaultValue: -1)); |
707 | applyModifier(mask: &rmod_masks.super, modifierBit: keysymMods.value(XKB_KEY_Super_R, defaultValue: -1)); |
708 | applyModifier(mask: &rmod_masks.hyper, modifierBit: keysymMods.value(XKB_KEY_Hyper_L, defaultValue: -1)); |
709 | applyModifier(mask: &rmod_masks.hyper, modifierBit: keysymMods.value(XKB_KEY_Hyper_R, defaultValue: -1)); |
710 | } |
711 | |
712 | resolveMaskConflicts(); |
713 | } |
714 | |
715 | // Small helper: check if an array of xcb_keycode_t contains a certain code |
716 | static inline bool keycodes_contains(xcb_keycode_t *codes, xcb_keycode_t which) |
717 | { |
718 | while (*codes != XCB_NO_SYMBOL) { |
719 | if (*codes == which) return true; |
720 | codes++; |
721 | } |
722 | return false; |
723 | } |
724 | |
725 | QXcbKeyboard::KeysymModifierMap QXcbKeyboard::keysymsToModifiers() |
726 | { |
727 | // The core protocol does not provide a convenient way to determine the mapping |
728 | // of modifier bits. Clients must retrieve and search the modifier map to determine |
729 | // the keycodes bound to each modifier, and then retrieve and search the keyboard |
730 | // mapping to determine the keysyms bound to the keycodes. They must repeat this |
731 | // process for all modifiers whenever any part of the modifier mapping is changed. |
732 | |
733 | KeysymModifierMap map; |
734 | |
735 | auto modMapReply = Q_XCB_REPLY(xcb_get_modifier_mapping, xcb_connection()); |
736 | if (!modMapReply) { |
737 | qWarning(msg: "Qt: failed to get modifier mapping"); |
738 | return map; |
739 | } |
740 | |
741 | // for Alt and Meta L and R are the same |
742 | static const xcb_keysym_t symbols[] = { |
743 | XKB_KEY_Alt_L, XKB_KEY_Meta_L, XKB_KEY_Mode_switch, XKB_KEY_Super_L, XKB_KEY_Super_R, |
744 | XKB_KEY_Hyper_L, XKB_KEY_Hyper_R |
745 | }; |
746 | static const size_t numSymbols = sizeof symbols / sizeof *symbols; |
747 | |
748 | // Figure out the modifier mapping, ICCCM 6.6 |
749 | xcb_keycode_t* modKeyCodes[numSymbols]; |
750 | for (size_t i = 0; i < numSymbols; ++i) |
751 | modKeyCodes[i] = xcb_key_symbols_get_keycode(syms: m_key_symbols, keysym: symbols[i]); |
752 | |
753 | xcb_keycode_t *modMap = xcb_get_modifier_mapping_keycodes(R: modMapReply.get()); |
754 | const int modMapLength = xcb_get_modifier_mapping_keycodes_length(R: modMapReply.get()); |
755 | /* For each modifier of "Shift, Lock, Control, Mod1, Mod2, Mod3, |
756 | * Mod4, and Mod5" the modifier map contains keycodes_per_modifier |
757 | * key codes that are associated with a modifier. |
758 | * |
759 | * As an example, take this 'xmodmap' output: |
760 | * xmodmap: up to 4 keys per modifier, (keycodes in parentheses): |
761 | * |
762 | * shift Shift_L (0x32), Shift_R (0x3e) |
763 | * lock Caps_Lock (0x42) |
764 | * control Control_L (0x25), Control_R (0x69) |
765 | * mod1 Alt_L (0x40), Alt_R (0x6c), Meta_L (0xcd) |
766 | * mod2 Num_Lock (0x4d) |
767 | * mod3 |
768 | * mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf) |
769 | * mod5 ISO_Level3_Shift (0x5c), Mode_switch (0xcb) |
770 | * |
771 | * The corresponding raw modifier map would contain keycodes for: |
772 | * Shift_L (0x32), Shift_R (0x3e), 0, 0, |
773 | * Caps_Lock (0x42), 0, 0, 0, |
774 | * Control_L (0x25), Control_R (0x69), 0, 0, |
775 | * Alt_L (0x40), Alt_R (0x6c), Meta_L (0xcd), 0, |
776 | * Num_Lock (0x4d), 0, 0, 0, |
777 | * 0,0,0,0, |
778 | * Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf), |
779 | * ISO_Level3_Shift (0x5c), Mode_switch (0xcb), 0, 0 |
780 | */ |
781 | |
782 | /* Create a map between a modifier keysym (as per the symbols array) |
783 | * and the modifier bit it's associated with (if any). |
784 | * As modMap contains key codes, search modKeyCodes for a match; |
785 | * if one is found we can look up the associated keysym. |
786 | * Together with the modifier index this will be used |
787 | * to compute a mapping between X modifier bits and Qt's |
788 | * modifiers (Alt, Ctrl etc). */ |
789 | for (int i = 0; i < modMapLength; i++) { |
790 | if (modMap[i] == XCB_NO_SYMBOL) |
791 | continue; |
792 | // Get key symbol for key code |
793 | for (size_t k = 0; k < numSymbols; k++) { |
794 | if (modKeyCodes[k] && keycodes_contains(codes: modKeyCodes[k], which: modMap[i])) { |
795 | // Key code is for modifier. Record mapping |
796 | xcb_keysym_t sym = symbols[k]; |
797 | /* As per modMap layout explanation above, dividing |
798 | * by keycodes_per_modifier gives the 'row' in the |
799 | * modifier map, which in turn is the modifier bit. */ |
800 | map[sym] = i / modMapReply->keycodes_per_modifier; |
801 | break; |
802 | } |
803 | } |
804 | } |
805 | |
806 | for (size_t i = 0; i < numSymbols; ++i) |
807 | free(ptr: modKeyCodes[i]); |
808 | |
809 | return map; |
810 | } |
811 | |
812 | void QXcbKeyboard::resolveMaskConflicts() |
813 | { |
814 | // if we don't have a meta key (or it's hidden behind alt), use super or hyper to generate |
815 | // Qt::Key_Meta and Qt::MetaModifier, since most newer XFree86/Xorg installations map the Windows |
816 | // key to Super |
817 | if (rmod_masks.alt == rmod_masks.meta) |
818 | rmod_masks.meta = 0; |
819 | |
820 | if (rmod_masks.meta == 0) { |
821 | // no meta keys... s/meta/super, |
822 | rmod_masks.meta = rmod_masks.super; |
823 | if (rmod_masks.meta == 0) { |
824 | // no super keys either? guess we'll use hyper then |
825 | rmod_masks.meta = rmod_masks.hyper; |
826 | } |
827 | } |
828 | |
829 | // translate Super/Hyper keys to Meta if we're using them as the MetaModifier |
830 | if (rmod_masks.meta && rmod_masks.meta == rmod_masks.super) |
831 | m_superAsMeta = true; |
832 | if (rmod_masks.meta && rmod_masks.meta == rmod_masks.hyper) |
833 | m_hyperAsMeta = true; |
834 | } |
835 | |
836 | void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, xcb_keycode_t code, |
837 | quint16 state, xcb_timestamp_t time, bool fromSendEvent) |
838 | { |
839 | if (!m_config) |
840 | return; |
841 | |
842 | QXcbWindow *source = connection()->platformWindowFromId(id: sourceWindow); |
843 | QXcbWindow *targetWindow = connection()->focusWindow() ? connection()->focusWindow() : source; |
844 | if (!targetWindow || !source) |
845 | return; |
846 | if (type == QEvent::KeyPress) |
847 | targetWindow->updateNetWmUserTime(timestamp: time); |
848 | |
849 | QXkbCommon::ScopedXKBState sendEventState; |
850 | if (fromSendEvent) { |
851 | // Have a temporary keyboard state filled in from state |
852 | // this way we allow for synthetic events to have different state |
853 | // from the current state i.e. you can have Alt+Ctrl pressed |
854 | // and receive a synthetic key event that has neither Alt nor Ctrl pressed |
855 | sendEventState.reset(p: xkb_state_new(keymap: m_xkbKeymap.get())); |
856 | if (!sendEventState) |
857 | return; |
858 | |
859 | xkb_mod_mask_t depressed = xkbModMask(state); |
860 | xkb_state_update_mask(state: sendEventState.get(), depressed_mods: depressed, latched_mods: 0, locked_mods: 0, depressed_layout: 0, latched_layout: 0, locked_layout: lockedGroup(state)); |
861 | } |
862 | |
863 | struct xkb_state *xkbState = fromSendEvent ? sendEventState.get() : m_xkbState.get(); |
864 | |
865 | xcb_keysym_t sym = xkb_state_key_get_one_sym(state: xkbState, key: code); |
866 | QString text = QXkbCommon::lookupString(state: xkbState, code); |
867 | |
868 | Qt::KeyboardModifiers modifiers = translateModifiers(s: state); |
869 | if (QXkbCommon::isKeypad(sym)) |
870 | modifiers |= Qt::KeypadModifier; |
871 | |
872 | int qtcode = QXkbCommon::keysymToQtKey(keysym: sym, modifiers, state: xkbState, code, superAsMeta: m_superAsMeta, hyperAsMeta: m_hyperAsMeta); |
873 | |
874 | if (type == QEvent::KeyPress) { |
875 | if (m_isAutoRepeat && m_autoRepeatCode != code) |
876 | // Some other key was pressed while we are auto-repeating on a different key. |
877 | m_isAutoRepeat = false; |
878 | } else { |
879 | m_isAutoRepeat = false; |
880 | // Look at the next event in the queue to see if we are auto-repeating. |
881 | connection()->eventQueue()->peek(option: QXcbEventQueue::PeekRetainMatch, |
882 | peeker: [this, time, code](xcb_generic_event_t *event, int type) { |
883 | if (type == XCB_KEY_PRESS) { |
884 | auto keyPress = reinterpret_cast<xcb_key_press_event_t *>(event); |
885 | m_isAutoRepeat = keyPress->time == time && keyPress->detail == code; |
886 | if (m_isAutoRepeat) |
887 | m_autoRepeatCode = code; |
888 | } |
889 | return true; |
890 | }); |
891 | } |
892 | |
893 | bool filtered = false; |
894 | if (auto inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext()) { |
895 | QKeyEvent event(type, qtcode, modifiers, code, sym, state, text, m_isAutoRepeat, text.size()); |
896 | event.setTimestamp(time); |
897 | filtered = inputContext->filterEvent(event: &event); |
898 | } |
899 | |
900 | if (!filtered) { |
901 | QWindow *window = targetWindow->window(); |
902 | #ifndef QT_NO_CONTEXTMENU |
903 | if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu) { |
904 | const QPoint globalPos = window->screen()->handle()->cursor()->pos(); |
905 | const QPoint pos = window->mapFromGlobal(pos: globalPos); |
906 | QWindowSystemInterface::handleContextMenuEvent(window, mouseTriggered: false, pos, globalPos, modifiers); |
907 | } |
908 | #endif |
909 | QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp: time, type, key: qtcode, modifiers, |
910 | nativeScanCode: code, nativeVirtualKey: sym, nativeModifiers: state, text, autorep: m_isAutoRepeat); |
911 | } |
912 | } |
913 | |
914 | static bool fromSendEvent(const void *event) |
915 | { |
916 | // From X11 protocol: Every event contains an 8-bit type code. The most |
917 | // significant bit in this code is set if the event was generated from |
918 | // a SendEvent request. |
919 | const xcb_generic_event_t *e = reinterpret_cast<const xcb_generic_event_t *>(event); |
920 | return (e->response_type & 0x80) != 0; |
921 | } |
922 | |
923 | void QXcbKeyboard::handleKeyPressEvent(const xcb_key_press_event_t *e) |
924 | { |
925 | handleKeyEvent(sourceWindow: e->event, type: QEvent::KeyPress, code: e->detail, state: e->state, time: e->time, fromSendEvent: fromSendEvent(event: e)); |
926 | } |
927 | |
928 | void QXcbKeyboard::handleKeyReleaseEvent(const xcb_key_release_event_t *e) |
929 | { |
930 | handleKeyEvent(sourceWindow: e->event, type: QEvent::KeyRelease, code: e->detail, state: e->state, time: e->time, fromSendEvent: fromSendEvent(event: e)); |
931 | } |
932 | |
933 | QT_END_NAMESPACE |
934 |
Definitions
- translateModifiers
- getUnshiftedXKey
- symbolsGroupString
- keymapFromCore
- updateKeymap
- updateKeymap
- possibleKeyCombinations
- queryKeyboardModifiers
- updateXKBState
- lockedGroup
- updateXKBStateFromCore
- updateXKBStateFromXI
- handleStateChanges
- xkbModMask
- updateXKBMods
- QXcbKeyboard
- ~QXcbKeyboard
- initialize
- selectEvents
- updateVModMapping
- updateVModToRModMapping
- applyModifier
- updateModifiers
- keycodes_contains
- keysymsToModifiers
- resolveMaskConflicts
- handleKeyEvent
- fromSendEvent
- handleKeyPressEvent
Learn to use CMake with our Intro Training
Find out more