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