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

source code of qtbase/src/platformsupport/input/evdevkeyboard/qevdevkeyboardhandler.cpp