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
qvxkeyboardhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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#include <evdevLib.h>
21
22#ifndef input_event_sec
23#define input_event_sec time.tv_sec
24#endif
25
26#ifndef input_event_usec
27#define input_event_usec time.tv_usec
28#endif
29
31
32using namespace Qt::StringLiterals;
33
34Q_LOGGING_CATEGORY(qLcVxKey, "qt.qpa.input")
35Q_STATIC_LOGGING_CATEGORY(qLcVxKeyMap, "qt.qpa.input.keymap")
36
37// simple builtin US keymap
39
40QVxKeyboardHandler::QVxKeyboardHandler(const QString &device, QFdContainer &fd, bool disableZap, bool enableCompose, const QString &keymapFile)
41 : m_device(device), m_fd(fd.release()), m_notify(nullptr),
42 m_modifiers(0), m_composing(0), m_dead_unicode(0xffff),
43 m_langLock(0), m_no_zap(disableZap), m_do_compose(enableCompose),
44 m_keymap(0), m_keymap_size(0), m_keycompose(0), m_keycompose_size(0)
45{
46 qCDebug(qLcVxKey) << "Create keyboard handler with for device" << device;
47
48 setObjectName("VxWorksInput Keyboard Handler"_L1);
49
50 memset(m_locks, 0, sizeof(m_locks));
51
52 if (keymapFile.isEmpty() || !loadKeymap(keymapFile))
54
55 // socket notifier for events on the keyboard device
56 m_notify = new QSocketNotifier(m_fd.get(), QSocketNotifier::Read, this);
57 connect(m_notify, &QSocketNotifier::activated, this, &QVxKeyboardHandler::readKeycode);
58}
59
64
65std::unique_ptr<QVxKeyboardHandler> QVxKeyboardHandler::create(const QString &device,
66 const QString &specification,
67 const QString &defaultKeymapFile)
68{
69 qCDebug(qLcVxKey, "Try to create keyboard handler for \"%ls\" \"%ls\"",
70 qUtf16Printable(device), qUtf16Printable(specification));
71
72 QString keymapFile = defaultKeymapFile;
73 int repeatDelay = 400;
74 int repeatRate = 80;
75 bool disableZap = false;
76 bool enableCompose = false;
77 int grab = 0;
78
79 const auto args = QStringView{specification}.split(u':');
80 for (const auto &arg : args) {
81 if (arg.startsWith("keymap="_L1))
82 keymapFile = arg.mid(7).toString();
83 else if (arg == "disable-zap"_L1)
84 disableZap = true;
85 else if (arg == "enable-compose"_L1)
86 enableCompose = true;
87 else if (arg.startsWith("repeat-delay="_L1))
88 repeatDelay = arg.mid(13).toInt();
89 else if (arg.startsWith("repeat-rate="_L1))
90 repeatRate = arg.mid(12).toInt();
91 else if (arg.startsWith("grab="_L1))
92 grab = arg.mid(5).toInt();
93 }
94
95 qCDebug(qLcVxKey, "Opening keyboard at %ls", qUtf16Printable(device));
96
97 QFdContainer fd(qt_safe_open(device.toLocal8Bit().constData(), O_RDWR | O_NDELAY, 0));
98 if (fd.get() < 0) {
99 qCDebug(qLcVxKey, "Keyboard device could not be opened as read-write, trying read-only");
100 fd.reset(qt_safe_open(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0));
101 }
102 if (fd.get() >= 0) {
103 UINT32 kbdMode = EV_DEV_KBD_KEYCODE_MODE;
104 if (ERROR == ioctl (fd.get(), EV_DEV_IO_SET_KBD_MODE, (char *)&kbdMode)) {
105 qWarning("Cannot set keyboard mapping mode to KEYCODE mode: %s", strerror(errno));
106 return nullptr;
107 }
108
109 return std::unique_ptr<QVxKeyboardHandler>(new QVxKeyboardHandler(device, fd, disableZap, enableCompose, keymapFile));
110 } else {
111 qErrnoWarning("Cannot open keyboard input device '%ls'", qUtf16Printable(device));
112 return nullptr;
113 }
114}
115
117{
118 EV_DEV_EVENT ev;
119 int n = ERROR;
120
121 while (n == ERROR) {
122 n = qt_safe_read(m_fd.get(), (char *)(&ev), sizeof(EV_DEV_EVENT));
123
124 if (n == 0) {
125 qWarning("vxkeyboard: Got EOF from the input device");
126 return;
127 }
128
129 if (n == ERROR) {
130 if (errno == EINTR || errno == EAGAIN)
131 continue;
132
133 qErrnoWarning("vxkeyboard: Could not read from input device");
134 if (errno == ENXIO) {
135 delete m_notify;
136 m_notify = nullptr;
137 m_fd.reset();
138 }
139 return;
140 }
141 }
142
143 if (n < sizeof(EV_DEV_EVENT)) return;
144 if (ev.type != EV_DEV_KEY) return;
145
146 quint16 code = ev.code;
147 qint32 value = ev.value;
148
149 processKeycode(code, value != 0, value == 2);
150}
151
152void QVxKeyboardHandler::processKeyEvent(int nativecode, int unicode, int qtcode,
153 Qt::KeyboardModifiers modifiers, bool isPress, bool autoRepeat)
154{
155 if (!autoRepeat)
156 QGuiApplicationPrivate::inputDeviceManager()->setKeyboardModifiers(QVxKeyboardHandler::toQtModifiers(m_modifiers));
157
158 QWindow *window = nullptr;
159 QWindowSystemInterface::handleExtendedKeyEvent(window, (isPress ? QEvent::KeyPress : QEvent::KeyRelease),
160 qtcode, modifiers, nativecode + 8, 0, int(modifiers),
161 (unicode != 0xffff ) ? QString(QChar(unicode)) : QString(), autoRepeat);
162}
163
164QKeycodeAction QVxKeyboardHandler::processKeycode(quint16 keycode, bool pressed, bool autorepeat)
165{
166 QKeycodeAction result = QKeycodeAction::None;
167 bool first_press = pressed && !autorepeat;
168
169 const QKeyboardMap::Mapping *map_plain = nullptr;
170 const QKeyboardMap::Mapping *map_withmod = nullptr;
171
172 quint8 modifiers = m_modifiers;
173
174 // get a specific and plain mapping for the keycode and the current modifiers
175 for (int i = 0; i < m_keymap_size && !(map_plain && map_withmod); ++i) {
176 const QKeyboardMap::Mapping *m = m_keymap + i;
177 if (m->keycode == keycode) {
178 if (m->modifiers == 0)
179 map_plain = m;
180
181 quint8 testmods = m_modifiers;
182 if (m_locks[0] /*CapsLock*/ && (m->flags & QKeyboardMap::IsLetter))
183 testmods ^= QKeyboardMap::ModShift;
184 if (m_langLock)
185 testmods ^= QKeyboardMap::ModAltGr;
186 if (m->modifiers == testmods)
187 map_withmod = m;
188 }
189 }
190
191 if (m_locks[0] && map_withmod && (map_withmod->flags & QKeyboardMap::IsLetter))
192 modifiers ^= QKeyboardMap::ModShift;
193
194 qCDebug(qLcVxKeyMap, "Processing key event: keycode=%3d, modifiers=%02x pressed=%d, autorepeat=%d | plain=%d, withmod=%d, size=%d",
195 keycode, modifiers, pressed ? 1 : 0, autorepeat ? 1 : 0,
196 int(map_plain ? map_plain - m_keymap : -1),
197 int(map_withmod ? map_withmod - m_keymap : -1),
198 m_keymap_size);
199
200 const QKeyboardMap::Mapping *it = map_withmod ? map_withmod : map_plain;
201
202 if (!it) {
203 // we couldn't even find a plain mapping
204 qCDebug(qLcVxKeyMap, "Could not find a suitable mapping for keycode: %3d, modifiers: %02x", keycode, modifiers);
205 return result;
206 }
207
208 bool skip = false;
209 quint16 unicode = it->unicode;
210 quint32 qtcode = it->qtcode;
211
212 if ((it->flags & QKeyboardMap::IsModifier) && it->special) {
213 // this is a modifier, i.e. Shift, Alt, ...
214 if (pressed)
215 m_modifiers |= quint8(it->special);
216 else
217 m_modifiers &= ~quint8(it->special);
218 } else if (qtcode >= Qt::Key_CapsLock && qtcode <= Qt::Key_ScrollLock) {
219 // (Caps|Num|Scroll)Lock
220 if (first_press) {
221 quint8 &lock = m_locks[qtcode - Qt::Key_CapsLock];
222 lock ^= 1;
223
224 switch (qtcode) {
225 case Qt::Key_CapsLock : result = lock ? QKeycodeAction::CapsLockOn : QKeycodeAction::CapsLockOff; break;
226 case Qt::Key_NumLock : result = lock ? QKeycodeAction::NumLockOn : QKeycodeAction::NumLockOff; break;
227 case Qt::Key_ScrollLock: result = lock ? QKeycodeAction::ScrollLockOn : QKeycodeAction::ScrollLockOff; break;
228 default : break;
229 }
230 }
231 } else if ((it->flags & QKeyboardMap::IsSystem) && it->special && first_press) {
232 switch (it->special) {
233 case QKeyboardMap::SystemReboot:
234 result = QKeycodeAction::Reboot;
235 break;
236
237 case QKeyboardMap::SystemZap:
238 if (!m_no_zap)
239 qApp->quit();
240 break;
241
242 case QKeyboardMap::SystemConsolePrevious:
243 result = QKeycodeAction::PreviousConsole;
244 break;
245
246 case QKeyboardMap::SystemConsoleNext:
247 result = QKeycodeAction::NextConsole;
248 break;
249
250 default:
251 if (it->special >= QKeyboardMap::SystemConsoleFirst &&
252 it->special <= QKeyboardMap::SystemConsoleLast) {
253 result = QKeycodeAction(
254 static_cast<int>(QKeycodeAction::SwitchConsoleFirst)
255 + ((it->special & static_cast<int>(QKeyboardMap::SystemConsoleMask))
256 & static_cast<int>(QKeycodeAction::SwitchConsoleMask)));
257 }
258 break;
259 }
260
261 skip = true; // no need to tell Qt about it
262 } else if ((qtcode == Qt::Key_Multi_key) && m_do_compose) {
263 // the Compose key was pressed
264 if (first_press)
265 m_composing = 2;
266 skip = true;
267 } else if ((it->flags & QKeyboardMap::IsDead) && m_do_compose) {
268 // a Dead key was pressed
269 if (first_press && m_composing == 1 && m_dead_unicode == unicode) { // twice
270 m_composing = 0;
271 qtcode = Qt::Key_unknown; // otherwise it would be Qt::Key_Dead...
272 } else if (first_press && unicode != 0xffff) {
273 m_dead_unicode = unicode;
274 m_composing = 1;
275 skip = true;
276 } else {
277 skip = true;
278 }
279 }
280
281 if (!skip) {
282 // a normal key was pressed
284
285 // we couldn't find a specific mapping for the current modifiers,
286 // or that mapping didn't have special modifiers:
287 // so just report the plain mapping with additional modifiers.
288 if ((it == map_plain && it != map_withmod) ||
289 (map_withmod && !(map_withmod->qtcode & modmask))) {
290 qtcode |= QVxKeyboardHandler::toQtModifiers(modifiers);
291 }
292
293 if (m_composing == 2 && first_press && !(it->flags & QKeyboardMap::IsModifier)) {
294 // the last key press was the Compose key
295 if (unicode != 0xffff) {
296 int idx = 0;
297 // check if this code is in the compose table at all
298 for ( ; idx < m_keycompose_size; ++idx) {
299 if (m_keycompose[idx].first == unicode)
300 break;
301 }
302 if (idx < m_keycompose_size) {
303 // found it -> simulate a Dead key press
304 m_dead_unicode = unicode;
305 unicode = 0xffff;
306 m_composing = 1;
307 skip = true;
308 } else {
309 m_composing = 0;
310 }
311 } else {
312 m_composing = 0;
313 }
314 } else if (m_composing == 1 && first_press && !(it->flags & QKeyboardMap::IsModifier)) {
315 // the last key press was a Dead key
316 bool valid = false;
317 if (unicode != 0xffff) {
318 int idx = 0;
319 // check if this code is in the compose table at all
320 for ( ; idx < m_keycompose_size; ++idx) {
321 if (m_keycompose[idx].first == m_dead_unicode && m_keycompose[idx].second == unicode)
322 break;
323 }
324 if (idx < m_keycompose_size) {
325 quint16 composed = m_keycompose[idx].result;
326 if (composed != 0xffff) {
327 unicode = composed;
328 qtcode = Qt::Key_unknown;
329 valid = true;
330 }
331 }
332 }
333 if (!valid) {
334 unicode = m_dead_unicode;
335 qtcode = Qt::Key_unknown;
336 }
337 m_composing = 0;
338 }
339
340 if (!skip) {
341 // Up until now qtcode contained both the key and modifiers. Split it.
342 Qt::KeyboardModifiers qtmods = Qt::KeyboardModifiers(qtcode & modmask);
343 qtcode &= ~modmask;
344
345 // qtmods here is the modifier state before the event, i.e. not
346 // including the current key in case it is a modifier.
347 qCDebug(qLcVxKeyMap, "Processing: uni=%04x, qt=%08x, qtmod=%08x", unicode, qtcode, int(qtmods));
348
349 // If NumLockOff and keypad key pressed remap event sent
350 if (!m_locks[1] && (qtmods & Qt::KeypadModifier) &&
351 keycode >= 71 &&
352 keycode <= 83 &&
353 keycode != 74 &&
354 keycode != 78) {
355
356 unicode = 0xffff;
357 switch (keycode) {
358 case 71: //7 --> Home
359 qtcode = Qt::Key_Home;
360 break;
361 case 72: //8 --> Up
362 qtcode = Qt::Key_Up;
363 break;
364 case 73: //9 --> PgUp
365 qtcode = Qt::Key_PageUp;
366 break;
367 case 75: //4 --> Left
368 qtcode = Qt::Key_Left;
369 break;
370 case 76: //5 --> Clear
371 qtcode = Qt::Key_Clear;
372 break;
373 case 77: //6 --> right
374 qtcode = Qt::Key_Right;
375 break;
376 case 79: //1 --> End
377 qtcode = Qt::Key_End;
378 break;
379 case 80: //2 --> Down
380 qtcode = Qt::Key_Down;
381 break;
382 case 81: //3 --> PgDn
383 qtcode = Qt::Key_PageDown;
384 break;
385 case 82: //0 --> Ins
386 qtcode = Qt::Key_Insert;
387 break;
388 case 83: //, --> Del
389 qtcode = Qt::Key_Delete;
390 break;
391 }
392 }
393
394 // Map SHIFT + Tab to SHIFT + Backtab, QShortcutMap knows about this translation
395 if (qtcode == Qt::Key_Tab && (qtmods & Qt::ShiftModifier) == Qt::ShiftModifier)
396 qtcode = Qt::Key_Backtab;
397
398 // Generate the QPA event.
399 processKeyEvent(keycode, unicode, qtcode, qtmods, pressed, autorepeat);
400 }
401 }
402 return result;
403}
404
406{
407 qCDebug(qLcVxKey, "Unload current keymap and restore built-in");
408
409 if (m_keymap && m_keymap != s_keymap_default)
410 delete [] m_keymap;
411 if (m_keycompose && m_keycompose != s_keycompose_default)
412 delete [] m_keycompose;
413
414 m_keymap = s_keymap_default;
415 m_keymap_size = sizeof(s_keymap_default) / sizeof(s_keymap_default[0]);
416 m_keycompose = s_keycompose_default;
417 m_keycompose_size = sizeof(s_keycompose_default) / sizeof(s_keycompose_default[0]);
418
419 // reset state, so we could switch keymaps at runtime
420 m_modifiers = 0;
421 memset(m_locks, 0, sizeof(m_locks));
422 m_composing = 0;
423 m_dead_unicode = 0xffff;
424
425 m_langLock = 0;
426}
427
428bool QVxKeyboardHandler::loadKeymap(const QString &file)
429{
430 qCDebug(qLcVxKey, "Loading keymap %ls", qUtf16Printable(file));
431
432 QFile f(file);
433
434 if (!f.open(QIODevice::ReadOnly)) {
435 qWarning("Could not open keymap file '%ls'", qUtf16Printable(file));
436 return false;
437 }
438
439 // .qmap files have a very simple structure:
440 // quint32 magic (QKeyboard::FileMagic)
441 // quint32 version (1)
442 // quint32 keymap_size (# of struct QKeyboard::Mappings)
443 // quint32 keycompose_size (# of struct QKeyboard::Composings)
444 // all QKeyboard::Mappings via QDataStream::operator(<<|>>)
445 // all QKeyboard::Composings via QDataStream::operator(<<|>>)
446
447 quint32 qmap_magic, qmap_version, qmap_keymap_size, qmap_keycompose_size;
448
449 QDataStream ds(&f);
450
451 ds >> qmap_magic >> qmap_version >> qmap_keymap_size >> qmap_keycompose_size;
452
453 if (ds.status() != QDataStream::Ok || qmap_magic != QKeyboardMap::FileMagic || qmap_version != 1 || qmap_keymap_size == 0) {
454 qWarning("'%ls' is not a valid .qmap keymap file", qUtf16Printable(file));
455 return false;
456 }
457
458 QKeyboardMap::Mapping *qmap_keymap = new QKeyboardMap::Mapping[qmap_keymap_size];
459 QKeyboardMap::Composing *qmap_keycompose = qmap_keycompose_size ? new QKeyboardMap::Composing[qmap_keycompose_size] : 0;
460
461 for (quint32 i = 0; i < qmap_keymap_size; ++i)
462 ds >> qmap_keymap[i];
463 for (quint32 i = 0; i < qmap_keycompose_size; ++i)
464 ds >> qmap_keycompose[i];
465
466 if (ds.status() != QDataStream::Ok) {
467 delete [] qmap_keymap;
468 delete [] qmap_keycompose;
469
470 qWarning("Keymap file '%ls' cannot be loaded.", qUtf16Printable(file));
471 return false;
472 }
473
474 // unload currently active and clear state
476
477 m_keymap = qmap_keymap;
478 m_keymap_size = qmap_keymap_size;
479 m_keycompose = qmap_keycompose;
480 m_keycompose_size = qmap_keycompose_size;
481
482 m_do_compose = true;
483
484 return true;
485}
486
488{
489 m_langLock ^= 1;
490}
491
492QT_END_NAMESPACE
QKeycodeAction processKeycode(quint16 keycode, bool pressed, bool autorepeat)
QVxKeyboardHandler(const QString &device, QFdContainer &fd, bool disableZap, bool enableCompose, const QString &keymapFile)
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 Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)