1 /****************************************************************************
2 **
3 ** Copyright (C) 2015-2016 Oleksandr Tymoshenko <gonzo@bluezbox.com>
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qbsdkeyboard.h"
41
42 #include <QByteArray>
43 #include <QFile>
44 #include <QGuiApplication>
45 #include <QPoint>
46 #include <QSocketNotifier>
47 #include <QString>
48 #include <QStringList>
49
50 #include <QtCore/qglobal.h>
51 #include <qpa/qwindowsysteminterface.h>
52 #include <private/qcore_unix_p.h>
53 #include <private/qguiapplication_p.h>
54 #include <private/qinputdevicemanager_p_p.h>
55
56 #include <qdebug.h>
57 #include <cstdio>
58
59 #include <cerrno>
60 #include <fcntl.h>
61 #include <unistd.h>
62
63 #include <termios.h>
64 #include <sys/kbio.h>
65
66 // #define QT_BSD_KEYBOARD_DEBUG
67
68 #ifdef QT_BSD_KEYBOARD_DEBUG
69 #include <qdebug.h>
70 #endif
71
72 QT_BEGIN_NAMESPACE
73
74 enum {
75 Bsd_KeyCodeMask = 0x7f,
76 Bsd_KeyPressedMask = 0x80
77 };
78
79 #include "qbsdkeyboard_defaultmap.h"
80
QBsdKeyboardHandler(const QString & key,const QString & specification)81 QBsdKeyboardHandler::QBsdKeyboardHandler(const QString &key, const QString &specification)
82 {
83 Q_UNUSED(key);
84
85 setObjectName(QLatin1String("BSD Keyboard Handler"));
86
87 QByteArray device;
88 if (specification.startsWith("/dev/"))
89 device = QFile::encodeName(specification);
90
91 if (device.isEmpty()) {
92 device = QByteArrayLiteral("STDIN");
93 m_fd = fileno(stdin);
94 }
95 else {
96 m_fd = QT_OPEN(device.constData(), O_RDONLY);
97 if (!m_fd) {
98 qErrnoWarning(errno, "open(%s) failed", device.constData());
99 return;
100 }
101 m_shouldClose = true;
102 }
103
104 if (ioctl(m_fd, KDGKBMODE, &m_origKbdMode)) {
105 qErrnoWarning(errno, "ioctl(%s, KDGKBMODE) failed", device.constData());
106 revertTTYSettings();
107 return;
108 }
109
110 if (ioctl(m_fd, KDSKBMODE, K_CODE) < 0) {
111 qErrnoWarning(errno, "ioctl(%s, KDSKBMODE) failed", device.constData());
112 revertTTYSettings();
113 return;
114 }
115
116 termios kbdtty;
117 if (tcgetattr(m_fd, &kbdtty) == 0) {
118
119 m_kbdOrigTty.reset(new termios);
120 *m_kbdOrigTty = kbdtty;
121
122 kbdtty.c_iflag = IGNPAR | IGNBRK;
123 kbdtty.c_oflag = 0;
124 kbdtty.c_cflag = CREAD | CS8;
125 kbdtty.c_lflag = 0;
126 kbdtty.c_cc[VTIME] = 0;
127 kbdtty.c_cc[VMIN] = 0;
128 cfsetispeed(&kbdtty, 9600);
129 cfsetospeed(&kbdtty, 9600);
130 if (tcsetattr(m_fd, TCSANOW, &kbdtty) < 0) {
131 qErrnoWarning(errno, "tcsetattr(%s) failed", device.constData());
132
133 // TTY is still at old settings so we can
134 // dispose of original termios data
135 m_kbdOrigTty.reset();
136
137 revertTTYSettings();
138 return;
139 }
140 } else {
141 qErrnoWarning(errno, "tcgetattr(%s) failed", device.constData());
142 revertTTYSettings();
143 return;
144 }
145
146 if (fcntl(m_fd, F_SETFL, O_NONBLOCK)) {
147 qErrnoWarning(errno, "fcntl(%s, F_SETFL, O_NONBLOCK) failed", device.constData());
148 revertTTYSettings();
149 return;
150 }
151
152 resetKeymap();
153
154 m_notifier.reset(new QSocketNotifier(m_fd, QSocketNotifier::Read, this));
155 connect(m_notifier.data(), &QSocketNotifier::activated, this, &QBsdKeyboardHandler::readKeyboardData);
156 QInputDeviceManagerPrivate::get(QGuiApplicationPrivate::inputDeviceManager())->setDeviceCount(
157 QInputDeviceManager::DeviceTypeKeyboard, 1);
158 }
159
~QBsdKeyboardHandler()160 QBsdKeyboardHandler::~QBsdKeyboardHandler()
161 {
162 revertTTYSettings();
163 }
164
revertTTYSettings()165 void QBsdKeyboardHandler::revertTTYSettings()
166 {
167 if (m_fd >= 0) {
168 if (m_kbdOrigTty) {
169 tcsetattr(m_fd, TCSANOW, m_kbdOrigTty.data());
170 m_kbdOrigTty.reset();
171 }
172
173 if (m_origKbdMode != Bsd_NoKeyMode) {
174 ioctl(m_fd, KDSKBMODE, m_origKbdMode);
175 m_origKbdMode = Bsd_NoKeyMode;
176 }
177
178 if (m_shouldClose)
179 close(m_fd);
180 m_fd = -1;
181 }
182 }
183
readKeyboardData()184 void QBsdKeyboardHandler::readKeyboardData()
185 {
186
187 for (;;) {
188 uint8_t buffer[32];
189 int bytesRead = qt_safe_read(m_fd, buffer, sizeof(buffer));
190
191 if (!bytesRead) {
192 qWarning("Got EOF from the input device.");
193 return;
194 } else if (bytesRead < 0) {
195 if (errno != EINTR && errno != EAGAIN)
196 qWarning("Could not read from input device: %s", strerror(errno));
197 return;
198 }
199
200 for (int i = 0; i < bytesRead; ++i) {
201 const quint16 code = buffer[i] & Bsd_KeyCodeMask;
202 const bool pressed = (buffer[i] & Bsd_KeyPressedMask) ? false : true;
203
204 processKeycode(code, pressed, false);
205 }
206 }
207 }
208
processKeyEvent(int nativecode,int unicode,int qtcode,Qt::KeyboardModifiers modifiers,bool isPress,bool autoRepeat)209 void QBsdKeyboardHandler::processKeyEvent(int nativecode, int unicode, int qtcode,
210 Qt::KeyboardModifiers modifiers, bool isPress,
211 bool autoRepeat)
212 {
213 const QString text = (unicode != 0xffff ) ? QString(unicode) : QString();
214 const QEvent::Type eventType = isPress ? QEvent::KeyPress : QEvent::KeyRelease;
215
216 QWindowSystemInterface::handleExtendedKeyEvent(0, eventType, qtcode, modifiers, nativecode, 0,
217 int(modifiers), text, autoRepeat);
218 }
219
processKeycode(quint16 keycode,bool pressed,bool autorepeat)220 void QBsdKeyboardHandler::processKeycode(quint16 keycode, bool pressed, bool autorepeat)
221 {
222 const bool first_press = pressed && !autorepeat;
223
224 const QBsdKeyboardMap::Mapping *map_plain = nullptr;
225 const QBsdKeyboardMap::Mapping *map_withmod = nullptr;
226
227 quint8 modifiers = m_modifiers;
228
229 // get a specific and plain mapping for the keycode and the current modifiers
230 for (const QBsdKeyboardMap::Mapping &m : m_keymap) {
231 if (m.keycode == keycode) {
232 if (m.modifiers == 0)
233 map_plain = &m;
234
235 quint8 testmods = m_modifiers;
236 if (m_capsLock && (m.flags & QBsdKeyboardMap::IsLetter))
237 testmods ^= QBsdKeyboardMap::ModShift;
238 if (m.modifiers == testmods)
239 map_withmod = &m;
240 }
241 }
242
243 if (m_capsLock && map_withmod && (map_withmod->flags & QBsdKeyboardMap::IsLetter))
244 modifiers ^= QBsdKeyboardMap::ModShift;
245
246 #ifdef QT_BSD_KEYBOARD_DEBUG
247 qWarning("Processing key event: keycode=%3d, modifiers=%02x pressed=%d, autorepeat=%d", \
248 keycode, modifiers, pressed ? 1 : 0, autorepeat ? 1 : 0);
249 #endif
250
251 const QBsdKeyboardMap::Mapping *it = map_withmod ? map_withmod : map_plain;
252
253 if (!it) {
254 #ifdef QT_BSD_KEYBOARD_DEBUG
255 // we couldn't even find a plain mapping
256 qWarning("Could not find a suitable mapping for keycode: %3d, modifiers: %02x", keycode, modifiers);
257 #endif
258 return;
259 }
260
261 bool skip = false;
262 quint16 unicode = it->unicode;
263 quint32 qtcode = it->qtcode;
264
265 if ((it->flags & QBsdKeyboardMap::IsModifier) && it->special) {
266 // this is a modifier, i.e. Shift, Alt, ...
267 if (pressed)
268 m_modifiers |= quint8(it->special);
269 else
270 m_modifiers &= ~quint8(it->special);
271 } else if (qtcode >= Qt::Key_CapsLock && qtcode <= Qt::Key_ScrollLock) {
272 // (Caps|Num|Scroll)Lock
273 if (first_press) {
274 switch (qtcode) {
275 case Qt::Key_CapsLock:
276 m_capsLock = !m_capsLock;
277 switchLed(LED_CAP, m_capsLock);
278 break;
279 case Qt::Key_NumLock:
280 m_numLock = !m_numLock;
281 switchLed(LED_NUM, m_numLock);
282 break;
283 case Qt::Key_ScrollLock:
284 m_scrollLock = !m_scrollLock;
285 switchLed(LED_SCR, m_scrollLock);
286 break;
287 default:
288 break;
289 }
290 }
291 }
292
293 if (!skip) {
294 // a normal key was pressed
295 const int modmask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier
296 | Qt::MetaModifier | Qt::KeypadModifier;
297
298 // we couldn't find a specific mapping for the current modifiers,
299 // or that mapping didn't have special modifiers:
300 // so just report the plain mapping with additional modifiers.
301 if ((it == map_plain && it != map_withmod) ||
302 (map_withmod && !(map_withmod->qtcode & modmask))) {
303 qtcode |= QBsdKeyboardHandler::toQtModifiers(modifiers);
304 }
305
306 #ifdef QT_BSD_KEYBOARD_DEBUG
307 qWarning("Processing: uni=%04x, qt=%08x, qtmod=%08x", unicode, qtcode & ~modmask, (qtcode & modmask));
308 #endif
309 //If NumLockOff and keypad key pressed remap event sent
310 if (!m_numLock &&
311 (qtcode & Qt::KeypadModifier)) {
312 unicode = 0xffff;
313 const int oldMask = (qtcode & modmask);
314 switch (qtcode & ~modmask) {
315 case Qt::Key_7: //7 --> Home
316 qtcode = Qt::Key_Home;
317 break;
318 case Qt::Key_8: //8 --> Up
319 qtcode = Qt::Key_Up;
320 break;
321 case Qt::Key_9: //9 --> PgUp
322 qtcode = Qt::Key_PageUp;
323 break;
324 case Qt::Key_4: //4 --> Left
325 qtcode = Qt::Key_Left;
326 break;
327 case Qt::Key_5: //5 --> Clear
328 qtcode = Qt::Key_Clear;
329 break;
330 case Qt::Key_6: //6 --> right
331 qtcode = Qt::Key_Right;
332 break;
333 case Qt::Key_1: //1 --> End
334 qtcode = Qt::Key_End;
335 break;
336 case Qt::Key_2: //2 --> Down
337 qtcode = Qt::Key_Down;
338 break;
339 case Qt::Key_3: //3 --> PgDn
340 qtcode = Qt::Key_PageDown;
341 break;
342 case Qt::Key_0: //0 --> Ins
343 qtcode = Qt::Key_Insert;
344 break;
345 case Qt::Key_Period: //. --> Del
346 qtcode = Qt::Key_Delete;
347 break;
348 }
349 qtcode |= oldMask;
350 }
351
352 // send the result to the server
353 processKeyEvent(keycode, unicode, qtcode & ~modmask,
354 Qt::KeyboardModifiers(qtcode & modmask), pressed, autorepeat);
355 }
356 }
357
switchLed(int led,bool state)358 void QBsdKeyboardHandler::switchLed(int led, bool state)
359 {
360 #ifdef QT_BSD_KEYBOARD_DEBUG
361 qWarning() << "switchLed" << led << state;
362 #endif
363 int leds = 0;
364 if (ioctl(m_fd, KDGETLED, &leds) < 0) {
365 qWarning("switchLed: Failed to query led states.");
366 return;
367 }
368
369 if (state)
370 leds |= led;
371 else
372 leds &= ~led;
373
374 if (ioctl(m_fd, KDSETLED, leds) < 0)
375 qWarning("switchLed: Failed to set led states.");
376 }
377
resetKeymap()378 void QBsdKeyboardHandler::resetKeymap()
379 {
380 #ifdef QT_BSD_KEYBOARD_DEBUG
381 qWarning() << "Unload current keymap and restore built-in";
382 #endif
383
384 m_keymap.clear();
385
386 const size_t mappingSize = sizeof(keymapDefault) / sizeof(keymapDefault[0]);
387 m_keymap.resize(mappingSize);
388 std::copy_n( &keymapDefault[0], mappingSize, m_keymap.begin() );
389
390 // reset state, so we could switch keymaps at runtime
391 m_modifiers = 0;
392 m_capsLock = false;
393 m_numLock = false;
394 m_scrollLock = false;
395
396 //Set locks according to keyboard leds
397 int leds = 0;
398 if (ioctl(m_fd, KDGETLED, &leds) < 0) {
399 qWarning("Failed to query led states. Settings numlock & capslock off");
400 switchLed(LED_NUM, false);
401 switchLed(LED_CAP, false);
402 switchLed(LED_SCR, false);
403 } else {
404 if ((leds & LED_CAP) > 0)
405 m_capsLock = true;
406 if ((leds & LED_NUM) > 0)
407 m_numLock = true;
408 if ((leds & LED_SCR) > 0)
409 m_scrollLock = true;
410 #ifdef QT_BSD_KEYBOARD_DEBUG
411 qWarning("numlock=%d , capslock=%d, scrolllock=%d",m_numLock, m_capsLock, m_scrollLock);
412 #endif
413 }
414 }
415