Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qevdevkeyboardhandler.cpp
Go to the documentation of this file.
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
6
7#include <qplatformdefs.h>
8
9#include <QFile>
10#include <QSocketNotifier>
11#include <QStringList>
12#include <QCoreApplication>
13#include <QLoggingCategory>
14#include <qpa/qwindowsysteminterface.h>
15#include <private/qcore_unix_p.h>
16
17#include <QtGui/private/qguiapplication_p.h>
18#include <QtGui/private/qinputdevicemanager_p.h>
19
20#ifdef Q_OS_FREEBSD
21#include <dev/evdev/input.h>
22#else
23#include <linux/input.h>
24#endif
25
26#ifndef input_event_sec
27#define input_event_sec time.tv_sec
28#endif
29
30#ifndef input_event_usec
31#define input_event_usec time.tv_usec
32#endif
33
34QT_BEGIN_NAMESPACE
35
36using namespace Qt::StringLiterals;
37
38Q_LOGGING_CATEGORY(qLcEvdevKey, "qt.qpa.input")
39Q_STATIC_LOGGING_CATEGORY(qLcEvdevKeyMap, "qt.qpa.input.keymap")
40
41// simple builtin US keymap
43
44QEvdevKeyboardHandler::QEvdevKeyboardHandler(const QString &device, QFdContainer &fd, bool disableZap, bool enableCompose, const QString &keymapFile)
45 : m_device(device), m_fd(fd.release()), m_notify(nullptr),
46 m_modifiers(0), m_composing(0), m_dead_unicode(0xffff),
47 m_langLock(0), m_no_zap(disableZap), m_do_compose(enableCompose),
48 m_keymap(0), m_keymap_size(0), m_keycompose(0), m_keycompose_size(0)
49{
50 qCDebug(qLcEvdevKey) << "Create keyboard handler with for device" << device;
51
52 setObjectName("LinuxInput Keyboard Handler"_L1);
53
54 memset(m_locks, 0, sizeof(m_locks));
55
56 if (keymapFile.isEmpty() || !loadKeymap(keymapFile))
58
59 // socket notifier for events on the keyboard device
60 m_notify = new QSocketNotifier(m_fd.get(), QSocketNotifier::Read, this);
61 connect(m_notify, &QSocketNotifier::activated, this, &QEvdevKeyboardHandler::readKeycode);
62}
63
68
69std::unique_ptr<QEvdevKeyboardHandler> QEvdevKeyboardHandler::create(const QString &device,
70 const QString &specification,
71 const QString &defaultKeymapFile)
72{
73 qCDebug(qLcEvdevKey, "Try to create keyboard handler for \"%ls\" \"%ls\"",
74 qUtf16Printable(device), qUtf16Printable(specification));
75
76 QString keymapFile = defaultKeymapFile;
77 int repeatDelay = 400;
78 int repeatRate = 80;
79 bool disableZap = false;
80 bool enableCompose = false;
81 int grab = 0;
82
83 const auto args = QStringView{specification}.split(u':');
84 for (const auto &arg : args) {
85 if (arg.startsWith("keymap="_L1))
86 keymapFile = arg.mid(7).toString();
87 else if (arg == "disable-zap"_L1)
88 disableZap = true;
89 else if (arg == "enable-compose"_L1)
90 enableCompose = true;
91 else if (arg.startsWith("repeat-delay="_L1))
92 repeatDelay = arg.mid(13).toInt();
93 else if (arg.startsWith("repeat-rate="_L1))
94 repeatRate = arg.mid(12).toInt();
95 else if (arg.startsWith("grab="_L1))
96 grab = arg.mid(5).toInt();
97 }
98
99 qCDebug(qLcEvdevKey, "Opening keyboard at %ls", qUtf16Printable(device));
100
101 QFdContainer fd(qt_safe_open(device.toLocal8Bit().constData(), O_RDWR | O_NDELAY, 0));
102 if (fd.get() < 0) {
103 qCDebug(qLcEvdevKey, "Keyboard device could not be opened as read-write, trying read-only");
104 fd.reset(qt_safe_open(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0));
105 }
106 if (fd.get() >= 0) {
107 ::ioctl(fd.get(), EVIOCGRAB, grab);
108 if (repeatDelay > 0 && repeatRate > 0) {
109 int kbdrep[2] = { repeatDelay, repeatRate };
110 ::ioctl(fd.get(), EVIOCSREP, kbdrep);
111 }
112
113 return std::unique_ptr<QEvdevKeyboardHandler>(new QEvdevKeyboardHandler(device, fd, disableZap, enableCompose, keymapFile));
114 } else {
115 qErrnoWarning("Cannot open keyboard input device '%ls'", qUtf16Printable(device));
116 return nullptr;
117 }
118}
119
120void QEvdevKeyboardHandler::switchLed(int led, bool state)
121{
122 qCDebug(qLcEvdevKey, "switchLed %d %d", led, int(state));
123
124 struct timeval tv;
125 ::gettimeofday(&tv, 0);
126 struct ::input_event led_ie;
127 led_ie.input_event_sec = tv.tv_sec;
128 led_ie.input_event_usec = tv.tv_usec;
129 led_ie.type = EV_LED;
130 led_ie.code = led;
131 led_ie.value = state;
132
133 qt_safe_write(m_fd.get(), &led_ie, sizeof(led_ie));
134}
135
137{
138 struct ::input_event buffer[32];
139 int n = 0;
140
141 forever {
142 int result = qt_safe_read(m_fd.get(), reinterpret_cast<char *>(buffer) + n, sizeof(buffer) - n);
143
144 if (result == 0) {
145 qWarning("evdevkeyboard: Got EOF from the input device");
146 return;
147 } else if (result < 0) {
148 if (errno != EINTR && errno != EAGAIN) {
149 qErrnoWarning("evdevkeyboard: Could not read from input device");
150 // If the device got disconnected, stop reading, otherwise we get flooded
151 // by the above error over and over again.
152 if (errno == ENODEV) {
153 delete m_notify;
154 m_notify = nullptr;
155 m_fd.reset();
156 }
157 return;
158 }
159 } else {
160 n += result;
161 if (n % sizeof(buffer[0]) == 0)
162 break;
163 }
164 }
165
166 n /= sizeof(buffer[0]);
167
168 for (int i = 0; i < n; ++i) {
169 if (buffer[i].type != EV_KEY)
170 continue;
171
172 quint16 code = buffer[i].code;
173 qint32 value = buffer[i].value;
174
175 QKeycodeAction ka;
176 ka = processKeycode(code, value != 0, value == 2);
177
178 switch (ka) {
179 case QKeycodeAction::CapsLockOn:
180 case QKeycodeAction::CapsLockOff:
181 switchLed(LED_CAPSL, ka == QKeycodeAction::CapsLockOn);
182 break;
183
184 case QKeycodeAction::NumLockOn:
185 case QKeycodeAction::NumLockOff:
186 switchLed(LED_NUML, ka == QKeycodeAction::NumLockOn);
187 break;
188
189 case QKeycodeAction::ScrollLockOn:
190 case QKeycodeAction::ScrollLockOff:
191 switchLed(LED_SCROLLL, ka == QKeycodeAction::ScrollLockOn);
192 break;
193
194 default:
195 // ignore console switching and reboot
196 break;
197 }
198 }
199}
200
201void QEvdevKeyboardHandler::processKeyEvent(int nativecode, int unicode, int qtcode,
202 Qt::KeyboardModifiers modifiers, bool isPress, bool autoRepeat)
203{
204 if (!autoRepeat)
205 QGuiApplicationPrivate::inputDeviceManager()->setKeyboardModifiers(QKeyboardMap::toQtModifiers(m_modifiers));
206
207 QWindow *window = nullptr;
208#ifdef Q_OS_WEBOS
209 window = QOutputMapping::get()->windowForDeviceNode(m_device);
210#endif
211 QWindowSystemInterface::handleExtendedKeyEvent(window, (isPress ? QEvent::KeyPress : QEvent::KeyRelease),
212 qtcode, modifiers, nativecode + 8, 0, int(modifiers),
213 (unicode != 0xffff ) ? QString(QChar(unicode)) : QString(), autoRepeat);
214}
215
216QKeycodeAction QEvdevKeyboardHandler::processKeycode(quint16 keycode, bool pressed, bool autorepeat)
217{
218 QKeycodeAction result = QKeycodeAction::None;
219 bool first_press = pressed && !autorepeat;
220
221 const QKeyboardMap::Mapping *map_plain = nullptr;
222 const QKeyboardMap::Mapping *map_withmod = nullptr;
223
224 quint8 modifiers = m_modifiers;
225
226 // get a specific and plain mapping for the keycode and the current modifiers
227 for (int i = 0; i < m_keymap_size && !(map_plain && map_withmod); ++i) {
228 const QKeyboardMap::Mapping *m = m_keymap + i;
229 if (m->keycode == keycode) {
230 if (m->modifiers == 0)
231 map_plain = m;
232
233 quint8 testmods = m_modifiers;
234 if (m_locks[0] /*CapsLock*/ && (m->flags & QKeyboardMap::IsLetter))
235 testmods ^= QKeyboardMap::ModShift;
236 if (m_langLock)
237 testmods ^= QKeyboardMap::ModAltGr;
238 if (m->modifiers == testmods)
239 map_withmod = m;
240 }
241 }
242
243 if (m_locks[0] /*CapsLock*/ && map_withmod && (map_withmod->flags & QKeyboardMap::IsLetter))
244 modifiers ^= QKeyboardMap::ModShift;
245
246 qCDebug(qLcEvdevKeyMap, "Processing key event: keycode=%3d, modifiers=%02x pressed=%d, autorepeat=%d | plain=%d, withmod=%d, size=%d",
247 keycode, modifiers, pressed ? 1 : 0, autorepeat ? 1 : 0,
248 int(map_plain ? map_plain - m_keymap : -1),
249 int(map_withmod ? map_withmod - m_keymap : -1),
250 m_keymap_size);
251
252 const QKeyboardMap::Mapping *it = map_withmod ? map_withmod : map_plain;
253
254 if (!it) {
255 // we couldn't even find a plain mapping
256 qCDebug(qLcEvdevKeyMap, "Could not find a suitable mapping for keycode: %3d, modifiers: %02x", keycode, modifiers);
257 return result;
258 }
259
260 bool skip = false;
261 quint16 unicode = it->unicode;
262 quint32 qtcode = it->qtcode;
263
264 if ((it->flags & QKeyboardMap::IsModifier) && it->special) {
265 // this is a modifier, i.e. Shift, Alt, ...
266 if (pressed)
267 m_modifiers |= quint8(it->special);
268 else
269 m_modifiers &= ~quint8(it->special);
270 } else if (qtcode >= Qt::Key_CapsLock && qtcode <= Qt::Key_ScrollLock) {
271 // (Caps|Num|Scroll)Lock
272 if (first_press) {
273 quint8 &lock = m_locks[qtcode - Qt::Key_CapsLock];
274 lock ^= 1;
275
276 switch (qtcode) {
277 case Qt::Key_CapsLock : result = lock ? QKeycodeAction::CapsLockOn : QKeycodeAction::CapsLockOff; break;
278 case Qt::Key_NumLock : result = lock ? QKeycodeAction::NumLockOn : QKeycodeAction::NumLockOff; break;
279 case Qt::Key_ScrollLock: result = lock ? QKeycodeAction::ScrollLockOn : QKeycodeAction::ScrollLockOff; break;
280 default : break;
281 }
282 }
283 } else if ((it->flags & QKeyboardMap::IsSystem) && it->special && first_press) {
284 switch (it->special) {
285 case QKeyboardMap::SystemReboot:
286 result = QKeycodeAction::Reboot;
287 break;
288
289 case QKeyboardMap::SystemZap:
290 if (!m_no_zap)
291 qApp->quit();
292 break;
293
294 case QKeyboardMap::SystemConsolePrevious:
295 result = QKeycodeAction::PreviousConsole;
296 break;
297
298 case QKeyboardMap::SystemConsoleNext:
299 result = QKeycodeAction::NextConsole;
300 break;
301
302 default:
303 if (it->special >= QKeyboardMap::SystemConsoleFirst &&
304 it->special <= QKeyboardMap::SystemConsoleLast) {
305 result = QKeycodeAction(
306 static_cast<int>(QKeycodeAction::SwitchConsoleFirst)
307 + (it->special & static_cast<int>(QKeyboardMap::SystemConsoleMask)
308 & static_cast<int>(QKeycodeAction::SwitchConsoleMask)));
309 }
310 break;
311 }
312
313 skip = true; // no need to tell Qt about it
314 } else if ((qtcode == Qt::Key_Multi_key) && m_do_compose) {
315 // the Compose key was pressed
316 if (first_press)
317 m_composing = 2;
318 skip = true;
319 } else if ((it->flags & QKeyboardMap::IsDead) && m_do_compose) {
320 // a Dead key was pressed
321 if (first_press && m_composing == 1 && m_dead_unicode == unicode) { // twice
322 m_composing = 0;
323 qtcode = Qt::Key_unknown; // otherwise it would be Qt::Key_Dead...
324 } else if (first_press && unicode != 0xffff) {
325 m_dead_unicode = unicode;
326 m_composing = 1;
327 skip = true;
328 } else {
329 skip = true;
330 }
331 }
332
333 if (!skip) {
334 // a normal key was pressed
336
337 // we couldn't find a specific mapping for the current modifiers,
338 // or that mapping didn't have special modifiers:
339 // so just report the plain mapping with additional modifiers.
340 if ((it == map_plain && it != map_withmod) ||
341 (map_withmod && !(map_withmod->qtcode & modmask))) {
342 qtcode |= QKeyboardMap::toQtModifiers(modifiers);
343 }
344
345 if (m_composing == 2 && first_press && !(it->flags & QKeyboardMap::IsModifier)) {
346 // the last key press was the Compose key
347 if (unicode != 0xffff) {
348 int idx = 0;
349 // check if this code is in the compose table at all
350 for ( ; idx < m_keycompose_size; ++idx) {
351 if (m_keycompose[idx].first == unicode)
352 break;
353 }
354 if (idx < m_keycompose_size) {
355 // found it -> simulate a Dead key press
356 m_dead_unicode = unicode;
357 unicode = 0xffff;
358 m_composing = 1;
359 skip = true;
360 } else {
361 m_composing = 0;
362 }
363 } else {
364 m_composing = 0;
365 }
366 } else if (m_composing == 1 && first_press && !(it->flags & QKeyboardMap::IsModifier)) {
367 // the last key press was a Dead key
368 bool valid = false;
369 if (unicode != 0xffff) {
370 int idx = 0;
371 // check if this code is in the compose table at all
372 for ( ; idx < m_keycompose_size; ++idx) {
373 if (m_keycompose[idx].first == m_dead_unicode && m_keycompose[idx].second == unicode)
374 break;
375 }
376 if (idx < m_keycompose_size) {
377 quint16 composed = m_keycompose[idx].result;
378 if (composed != 0xffff) {
379 unicode = composed;
380 qtcode = Qt::Key_unknown;
381 valid = true;
382 }
383 }
384 }
385 if (!valid) {
386 unicode = m_dead_unicode;
387 qtcode = Qt::Key_unknown;
388 }
389 m_composing = 0;
390 }
391
392 if (!skip) {
393 // Up until now qtcode contained both the key and modifiers. Split it.
394 Qt::KeyboardModifiers qtmods = Qt::KeyboardModifiers(qtcode & modmask);
395 qtcode &= ~modmask;
396
397 // qtmods here is the modifier state before the event, i.e. not
398 // including the current key in case it is a modifier.
399 qCDebug(qLcEvdevKeyMap, "Processing: uni=%04x, qt=%08x, qtmod=%08x", unicode, qtcode, int(qtmods));
400
401 // If NumLockOff and keypad key pressed remap event sent
402 if (!m_locks[1] && (qtmods & Qt::KeypadModifier) &&
403 keycode >= 71 &&
404 keycode <= 83 &&
405 keycode != 74 &&
406 keycode != 78) {
407
408 unicode = 0xffff;
409 switch (keycode) {
410 case 71: //7 --> Home
411 qtcode = Qt::Key_Home;
412 break;
413 case 72: //8 --> Up
414 qtcode = Qt::Key_Up;
415 break;
416 case 73: //9 --> PgUp
417 qtcode = Qt::Key_PageUp;
418 break;
419 case 75: //4 --> Left
420 qtcode = Qt::Key_Left;
421 break;
422 case 76: //5 --> Clear
423 qtcode = Qt::Key_Clear;
424 break;
425 case 77: //6 --> right
426 qtcode = Qt::Key_Right;
427 break;
428 case 79: //1 --> End
429 qtcode = Qt::Key_End;
430 break;
431 case 80: //2 --> Down
432 qtcode = Qt::Key_Down;
433 break;
434 case 81: //3 --> PgDn
435 qtcode = Qt::Key_PageDown;
436 break;
437 case 82: //0 --> Ins
438 qtcode = Qt::Key_Insert;
439 break;
440 case 83: //, --> Del
441 qtcode = Qt::Key_Delete;
442 break;
443 }
444 }
445
446 // Map SHIFT + Tab to SHIFT + Backtab, QShortcutMap knows about this translation
447 if (qtcode == Qt::Key_Tab && (qtmods & Qt::ShiftModifier) == Qt::ShiftModifier)
448 qtcode = Qt::Key_Backtab;
449
450 // Generate the QPA event.
451 processKeyEvent(keycode, unicode, qtcode, qtmods, pressed, autorepeat);
452 }
453 }
454 return result;
455}
456
458{
459 qCDebug(qLcEvdevKey, "Unload current keymap and restore built-in");
460
461 if (m_keymap && m_keymap != s_keymap_default)
462 delete [] m_keymap;
463 if (m_keycompose && m_keycompose != s_keycompose_default)
464 delete [] m_keycompose;
465
466 m_keymap = s_keymap_default;
467 m_keymap_size = sizeof(s_keymap_default) / sizeof(s_keymap_default[0]);
468 m_keycompose = s_keycompose_default;
469 m_keycompose_size = sizeof(s_keycompose_default) / sizeof(s_keycompose_default[0]);
470
471 // reset state, so we could switch keymaps at runtime
472 m_modifiers = 0;
473 memset(m_locks, 0, sizeof(m_locks));
474 m_composing = 0;
475 m_dead_unicode = 0xffff;
476
477 //Set locks according to keyboard leds
478 quint16 ledbits[1];
479 memset(ledbits, 0, sizeof(ledbits));
480 if (::ioctl(m_fd.get(), EVIOCGLED(sizeof(ledbits)), ledbits) < 0) {
481 qWarning("evdevkeyboard: Failed to query led states");
482 switchLed(LED_NUML,false);
483 switchLed(LED_CAPSL, false);
484 switchLed(LED_SCROLLL,false);
485 } else {
486 //Capslock
487 if ((ledbits[0]&0x02) > 0)
488 m_locks[0] = 1;
489 //Numlock
490 if ((ledbits[0]&0x01) > 0)
491 m_locks[1] = 1;
492 //Scrollock
493 if ((ledbits[0]&0x04) > 0)
494 m_locks[2] = 1;
495 qCDebug(qLcEvdevKey, "numlock=%d , capslock=%d, scrolllock=%d", m_locks[1], m_locks[0], m_locks[2]);
496 }
497
498 m_langLock = 0;
499}
500
501bool QEvdevKeyboardHandler::loadKeymap(const QString &file)
502{
503 qCDebug(qLcEvdevKey, "Loading keymap %ls", qUtf16Printable(file));
504
505 QFile f(file);
506
507 if (!f.open(QIODevice::ReadOnly)) {
508 qWarning("Could not open keymap file '%ls'", qUtf16Printable(file));
509 return false;
510 }
511
512 // .qmap files have a very simple structure:
513 // quint32 magic (QKeyboard::FileMagic)
514 // quint32 version (1)
515 // quint32 keymap_size (# of struct QKeyboard::Mappings)
516 // quint32 keycompose_size (# of struct QKeyboard::Composings)
517 // all QKeyboard::Mappings via QDataStream::operator(<<|>>)
518 // all QKeyboard::Composings via QDataStream::operator(<<|>>)
519
520 quint32 qmap_magic, qmap_version, qmap_keymap_size, qmap_keycompose_size;
521
522 QDataStream ds(&f);
523
524 ds >> qmap_magic >> qmap_version >> qmap_keymap_size >> qmap_keycompose_size;
525
526 if (ds.status() != QDataStream::Ok || qmap_magic != QKeyboardMap::FileMagic || qmap_version != 1 || qmap_keymap_size == 0) {
527 qWarning("'%ls' is not a valid .qmap keymap file", qUtf16Printable(file));
528 return false;
529 }
530
531 QKeyboardMap::Mapping *qmap_keymap = new QKeyboardMap::Mapping[qmap_keymap_size];
532 QKeyboardMap::Composing *qmap_keycompose = qmap_keycompose_size ? new QKeyboardMap::Composing[qmap_keycompose_size] : 0;
533
534 for (quint32 i = 0; i < qmap_keymap_size; ++i)
535 ds >> qmap_keymap[i];
536 for (quint32 i = 0; i < qmap_keycompose_size; ++i)
537 ds >> qmap_keycompose[i];
538
539 if (ds.status() != QDataStream::Ok) {
540 delete [] qmap_keymap;
541 delete [] qmap_keycompose;
542
543 qWarning("Keymap file '%ls' cannot be loaded.", qUtf16Printable(file));
544 return false;
545 }
546
547 // unload currently active and clear state
549
550 m_keymap = qmap_keymap;
551 m_keymap_size = qmap_keymap_size;
552 m_keycompose = qmap_keycompose;
553 m_keycompose_size = qmap_keycompose_size;
554
555 m_do_compose = true;
556
557 return true;
558}
559
561{
562 m_langLock ^= 1;
563}
564
565QT_END_NAMESPACE
QEvdevKeyboardHandler(const QString &device, QFdContainer &fd, bool disableZap, bool enableCompose, const QString &keymapFile)
QKeycodeAction processKeycode(quint16 keycode, bool pressed, bool autorepeat)
bool loadKeymap(const QString &file)
Definition qcompare.h:76
@ Key_Tab
Definition qnamespace.h:681
@ Key_Right
Definition qnamespace.h:696
@ Key_PageUp
Definition qnamespace.h:698
@ Key_Backtab
Definition qnamespace.h:682
@ Key_Insert
Definition qnamespace.h:686
@ Key_Left
Definition qnamespace.h:694
@ Key_Up
Definition qnamespace.h:695
@ Key_Down
Definition qnamespace.h:697
@ Key_Delete
Definition qnamespace.h:687
@ Key_Multi_key
Definition qnamespace.h:757
@ Key_ScrollLock
Definition qnamespace.h:706
@ Key_PageDown
Definition qnamespace.h:699
@ Key_Home
Definition qnamespace.h:692
@ Key_Clear
Definition qnamespace.h:691
@ Key_CapsLock
Definition qnamespace.h:704
@ Key_unknown
@ Key_End
Definition qnamespace.h:693
@ ShiftModifier
@ ControlModifier
@ MetaModifier
@ KeypadModifier
@ AltModifier
#define input_event_usec
#define input_event_sec
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
#define EV_KEY