diff options
Diffstat (limited to 'src/plugins/generic/bsdkeyboard/qbsdkeyboard.cpp')
-rw-r--r-- | src/plugins/generic/bsdkeyboard/qbsdkeyboard.cpp | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/src/plugins/generic/bsdkeyboard/qbsdkeyboard.cpp b/src/plugins/generic/bsdkeyboard/qbsdkeyboard.cpp new file mode 100644 index 0000000000..3814563eef --- /dev/null +++ b/src/plugins/generic/bsdkeyboard/qbsdkeyboard.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2015-2016 Oleksandr Tymoshenko <gonzo@bluezbox.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbsdkeyboard.h" + +#include <QByteArray> +#include <QFile> +#include <QGuiApplication> +#include <QPoint> +#include <QSocketNotifier> +#include <QString> +#include <QStringList> + +#include <QtCore/qglobal.h> +#include <qpa/qwindowsysteminterface.h> +#include <private/qcore_unix_p.h> + +#include <qdebug.h> +#include <cstdio> + +#include <cerrno> +#include <fcntl.h> +#include <unistd.h> + +#include <termios.h> +#include <sys/kbio.h> + +// #define QT_BSD_KEYBOARD_DEBUG + +#ifdef QT_BSD_KEYBOARD_DEBUG +#include <qdebug.h> +#endif + +QT_BEGIN_NAMESPACE + +enum { + Bsd_KeyCodeMask = 0x7f, + Bsd_KeyPressedMask = 0x80 +}; + +#include "qbsdkeyboard_defaultmap.h" + +QBsdKeyboardHandler::QBsdKeyboardHandler(const QString &key, const QString &specification) +{ + Q_UNUSED(key); + + setObjectName(QLatin1String("BSD Keyboard Handler")); + + QByteArray device; + if (specification.startsWith("/dev/")) + device = QFile::encodeName(specification); + + if (device.isEmpty()) { + device = QByteArrayLiteral("STDIN"); + m_fd = fileno(stdin); + } + else { + m_fd = QT_OPEN(device.constData(), O_RDONLY); + if (!m_fd) { + qErrnoWarning(errno, "open(%s) failed", device.constData()); + return; + } + m_shouldClose = true; + } + + if (ioctl(m_fd, KDGKBMODE, &m_origKbdMode)) { + qErrnoWarning(errno, "ioctl(%s, KDGKBMODE) failed", device.constData()); + revertTTYSettings(); + return; + } + + if (ioctl(m_fd, KDSKBMODE, K_CODE) < 0) { + qErrnoWarning(errno, "ioctl(%s, KDSKBMODE) failed", device.constData()); + revertTTYSettings(); + return; + } + + termios kbdtty; + if (tcgetattr(m_fd, &kbdtty) == 0) { + + m_kbdOrigTty.reset(new termios); + *m_kbdOrigTty = kbdtty; + + kbdtty.c_iflag = IGNPAR | IGNBRK; + kbdtty.c_oflag = 0; + kbdtty.c_cflag = CREAD | CS8; + kbdtty.c_lflag = 0; + kbdtty.c_cc[VTIME] = 0; + kbdtty.c_cc[VMIN] = 0; + cfsetispeed(&kbdtty, 9600); + cfsetospeed(&kbdtty, 9600); + if (tcsetattr(m_fd, TCSANOW, &kbdtty) < 0) { + qErrnoWarning(errno, "tcsetattr(%s) failed", device.constData()); + + // TTY is still at old settings so we can + // dispose of original termios data + m_kbdOrigTty.reset(); + + revertTTYSettings(); + return; + } + } else { + qErrnoWarning(errno, "tcgetattr(%s) failed", device.constData()); + revertTTYSettings(); + return; + } + + if (fcntl(m_fd, F_SETFL, O_NONBLOCK)) { + qErrnoWarning(errno, "fcntl(%s, F_SETFL, O_NONBLOCK) failed", device.constData()); + revertTTYSettings(); + return; + } + + resetKeymap(); + + m_notifier.reset(new QSocketNotifier(m_fd, QSocketNotifier::Read, this)); + connect(m_notifier.data(), &QSocketNotifier::activated, this, &QBsdKeyboardHandler::readKeyboardData); +} + +QBsdKeyboardHandler::~QBsdKeyboardHandler() +{ + revertTTYSettings(); +} + +void QBsdKeyboardHandler::revertTTYSettings() +{ + if (m_fd >= 0) { + if (m_kbdOrigTty) { + tcsetattr(m_fd, TCSANOW, m_kbdOrigTty.data()); + m_kbdOrigTty.reset(); + } + + if (m_origKbdMode != Bsd_NoKeyMode) { + ioctl(m_fd, KDSKBMODE, m_origKbdMode); + m_origKbdMode = Bsd_NoKeyMode; + } + + if (m_shouldClose) + close(m_fd); + m_fd = -1; + } +} + +void QBsdKeyboardHandler::readKeyboardData() +{ + + for (;;) { + uint8_t buffer[32]; + int bytesRead = qt_safe_read(m_fd, buffer, sizeof(buffer)); + + if (!bytesRead) { + qWarning("Got EOF from the input device."); + return; + } else if (bytesRead < 0) { + if (errno != EINTR && errno != EAGAIN) + qWarning("Could not read from input device: %s", strerror(errno)); + return; + } + + for (int i = 0; i < bytesRead; ++i) { + const quint16 code = buffer[i] & Bsd_KeyCodeMask; + const bool pressed = (buffer[i] & Bsd_KeyPressedMask) ? false : true; + + processKeycode(code, pressed, false); + } + } +} + +void QBsdKeyboardHandler::processKeyEvent(int nativecode, int unicode, int qtcode, + Qt::KeyboardModifiers modifiers, bool isPress, + bool autoRepeat) +{ + const QString text = (unicode != 0xffff ) ? QString(unicode) : QString(); + const QEvent::Type eventType = isPress ? QEvent::KeyPress : QEvent::KeyRelease; + + QWindowSystemInterface::handleExtendedKeyEvent(0, eventType, qtcode, modifiers, nativecode, 0, + int(modifiers), text, autoRepeat); +} + +void QBsdKeyboardHandler::processKeycode(quint16 keycode, bool pressed, bool autorepeat) +{ + const bool first_press = pressed && !autorepeat; + + const QBsdKeyboardMap::Mapping *map_plain = nullptr; + const QBsdKeyboardMap::Mapping *map_withmod = nullptr; + + quint8 modifiers = m_modifiers; + + // get a specific and plain mapping for the keycode and the current modifiers + for (const QBsdKeyboardMap::Mapping &m : m_keymap) { + if (m.keycode == keycode) { + if (m.modifiers == 0) + map_plain = &m; + + quint8 testmods = m_modifiers; + if (m_capsLock && (m.flags & QBsdKeyboardMap::IsLetter)) + testmods ^= QBsdKeyboardMap::ModShift; + if (m.modifiers == testmods) + map_withmod = &m; + } + } + + if (m_capsLock && map_withmod && (map_withmod->flags & QBsdKeyboardMap::IsLetter)) + modifiers ^= QBsdKeyboardMap::ModShift; + +#ifdef QT_BSD_KEYBOARD_DEBUG + qWarning("Processing key event: keycode=%3d, modifiers=%02x pressed=%d, autorepeat=%d", \ + keycode, modifiers, pressed ? 1 : 0, autorepeat ? 1 : 0); +#endif + + const QBsdKeyboardMap::Mapping *it = map_withmod ? map_withmod : map_plain; + + if (!it) { +#ifdef QT_BSD_KEYBOARD_DEBUG + // we couldn't even find a plain mapping + qWarning("Could not find a suitable mapping for keycode: %3d, modifiers: %02x", keycode, modifiers); +#endif + return; + } + + bool skip = false; + quint16 unicode = it->unicode; + quint32 qtcode = it->qtcode; + + if ((it->flags & QBsdKeyboardMap::IsModifier) && it->special) { + // this is a modifier, i.e. Shift, Alt, ... + if (pressed) + m_modifiers |= quint8(it->special); + else + m_modifiers &= ~quint8(it->special); + } else if (qtcode >= Qt::Key_CapsLock && qtcode <= Qt::Key_ScrollLock) { + // (Caps|Num|Scroll)Lock + if (first_press) { + switch (qtcode) { + case Qt::Key_CapsLock: + m_capsLock = !m_capsLock; + switchLed(LED_CAP, m_capsLock); + break; + case Qt::Key_NumLock: + m_numLock = !m_numLock; + switchLed(LED_NUM, m_numLock); + break; + case Qt::Key_ScrollLock: + m_scrollLock = !m_scrollLock; + switchLed(LED_SCR, m_scrollLock); + break; + default: + break; + } + } + } + + if (!skip) { + // a normal key was pressed + const int modmask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier + | Qt::MetaModifier | Qt::KeypadModifier; + + // we couldn't find a specific mapping for the current modifiers, + // or that mapping didn't have special modifiers: + // so just report the plain mapping with additional modifiers. + if ((it == map_plain && it != map_withmod) || + (map_withmod && !(map_withmod->qtcode & modmask))) { + qtcode |= QBsdKeyboardHandler::toQtModifiers(modifiers); + } + +#ifdef QT_BSD_KEYBOARD_DEBUG + qWarning("Processing: uni=%04x, qt=%08x, qtmod=%08x", unicode, qtcode & ~modmask, (qtcode & modmask)); +#endif + //If NumLockOff and keypad key pressed remap event sent + if (!m_numLock && + (qtcode & Qt::KeypadModifier)) { + unicode = 0xffff; + const int oldMask = (qtcode & modmask); + switch (qtcode & ~modmask) { + case Qt::Key_7: //7 --> Home + qtcode = Qt::Key_Home; + break; + case Qt::Key_8: //8 --> Up + qtcode = Qt::Key_Up; + break; + case Qt::Key_9: //9 --> PgUp + qtcode = Qt::Key_PageUp; + break; + case Qt::Key_4: //4 --> Left + qtcode = Qt::Key_Left; + break; + case Qt::Key_5: //5 --> Clear + qtcode = Qt::Key_Clear; + break; + case Qt::Key_6: //6 --> right + qtcode = Qt::Key_Right; + break; + case Qt::Key_1: //1 --> End + qtcode = Qt::Key_End; + break; + case Qt::Key_2: //2 --> Down + qtcode = Qt::Key_Down; + break; + case Qt::Key_3: //3 --> PgDn + qtcode = Qt::Key_PageDown; + break; + case Qt::Key_0: //0 --> Ins + qtcode = Qt::Key_Insert; + break; + case Qt::Key_Period: //. --> Del + qtcode = Qt::Key_Delete; + break; + } + qtcode |= oldMask; + } + + // send the result to the server + processKeyEvent(keycode, unicode, qtcode & ~modmask, + Qt::KeyboardModifiers(qtcode & modmask), pressed, autorepeat); + } +} + +void QBsdKeyboardHandler::switchLed(int led, bool state) +{ +#ifdef QT_BSD_KEYBOARD_DEBUG + qWarning() << "switchLed" << led << state; +#endif + int leds = 0; + if (ioctl(m_fd, KDGETLED, &leds) < 0) { + qWarning("switchLed: Failed to query led states."); + return; + } + + if (state) + leds |= led; + else + leds &= ~led; + + if (ioctl(m_fd, KDSETLED, leds) < 0) + qWarning("switchLed: Failed to set led states."); +} + +void QBsdKeyboardHandler::resetKeymap() +{ +#ifdef QT_BSD_KEYBOARD_DEBUG + qWarning() << "Unload current keymap and restore built-in"; +#endif + + m_keymap.clear(); + + const size_t mappingSize = sizeof(keymapDefault) / sizeof(keymapDefault[0]); + m_keymap.resize(mappingSize); + std::copy_n( &keymapDefault[0], mappingSize, m_keymap.begin() ); + + // reset state, so we could switch keymaps at runtime + m_modifiers = 0; + m_capsLock = false; + m_numLock = false; + m_scrollLock = false; + + //Set locks according to keyboard leds + int leds = 0; + if (ioctl(m_fd, KDGETLED, &leds) < 0) { + qWarning("Failed to query led states. Settings numlock & capslock off"); + switchLed(LED_NUM, false); + switchLed(LED_CAP, false); + switchLed(LED_SCR, false); + } else { + if ((leds & LED_CAP) > 0) + m_capsLock = true; + if ((leds & LED_NUM) > 0) + m_numLock = true; + if ((leds & LED_SCR) > 0) + m_scrollLock = true; +#ifdef QT_BSD_KEYBOARD_DEBUG + qWarning("numlock=%d , capslock=%d, scrolllock=%d",m_numLock, m_capsLock, m_scrollLock); +#endif + } +} |