From 66bcea8e20562ee3422d01cfb564615e61ad00f4 Mon Sep 17 00:00:00 2001 From: Roland Winklmeier Date: Tue, 10 Jun 2014 11:11:50 +0200 Subject: [PATCH] refs #265 Linux raw input handling --- src/blackinput/linux/keyboard_linux.cpp | 168 ++++++++++++++++++++++++ src/blackinput/linux/keyboard_linux.h | 27 ++++ 2 files changed, 195 insertions(+) diff --git a/src/blackinput/linux/keyboard_linux.cpp b/src/blackinput/linux/keyboard_linux.cpp index 0eca67192..e7d229fc1 100644 --- a/src/blackinput/linux/keyboard_linux.cpp +++ b/src/blackinput/linux/keyboard_linux.cpp @@ -4,7 +4,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "keyboard_linux.h" +#include "keymapping_linux.h" #include +#include +#include +#include +#include +#include +#include using namespace BlackMisc::Hardware; @@ -12,6 +19,7 @@ namespace BlackInput { CKeyboardLinux::CKeyboardLinux(QObject *parent) : IKeyboard(parent), + m_ignoreNextKey(false), m_mode(Mode_Nominal) { } @@ -22,6 +30,11 @@ namespace BlackInput bool CKeyboardLinux::init() { + QString dir = QLatin1String("/dev/input"); + m_devInputWatcher = new QFileSystemWatcher(QStringList(dir), this); + connect(m_devInputWatcher, &QFileSystemWatcher::directoryChanged, this, &CKeyboardLinux::deviceDirectoryChanged); + deviceDirectoryChanged(dir); + return true; } @@ -63,6 +76,10 @@ namespace BlackInput if (receiver == nullptr) return handle; + // FIXME. Remove virtual key code, because the one we receive from Qt is different to the linux native code. + // If we don't remove it, the hash lookup fails later. + key.setNativeVirtualKey(0); + handle.m_key = key; handle.m_receiver = receiver; handle.function = function; @@ -87,6 +104,60 @@ namespace BlackInput m_registeredFunctions.clear(); } + void CKeyboardLinux::deviceDirectoryChanged(const QString &dir) + { + QDir eventFiles(dir, QLatin1String("event*"), 0, QDir::System); + + foreach (QFileInfo fileInfo, eventFiles.entryInfoList()) + { + QString path = fileInfo.absoluteFilePath(); + if (!m_hashInputDevices.contains(path)) + addRawInputDevice(path); + } + } + + void CKeyboardLinux::inputReadyRead(int) + { + struct input_event eventInput; + + QFile *fileInput=qobject_cast(sender()->parent()); + if (!fileInput) + return; + + bool found = false; + + while (fileInput->read(reinterpret_cast(&eventInput), sizeof(eventInput)) == sizeof(eventInput)) + { + found = true; + if (eventInput.type != EV_KEY) + continue; + bool isPressed = false; + switch (eventInput.value) + { + case 0: + isPressed = false; + break; + case 1: + isPressed = true; + break; + default: + continue; + } + int keyCode = eventInput.code; + keyEvent(keyCode, isPressed); + } + + if (!found) { + int fd = fileInput->handle(); + int version = 0; + if ((ioctl(fd, EVIOCGVERSION, &version) < 0) || (((version >> 16) & 0xFF) < 1)) { + qWarning("CKeyboardLinux: Removing dead input device %s", qPrintable(fileInput->fileName())); + m_hashInputDevices.remove(fileInput->fileName()); + fileInput->deleteLater(); + } + } + } + void CKeyboardLinux::sendCaptureNotification(const CKeyboardKey &key, bool isFinished) { if (isFinished) @@ -107,4 +178,101 @@ namespace BlackInput functionHandle.function(isPressed); } } + + #define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) + + void CKeyboardLinux::addRawInputDevice(const QString &filePath) + { + QFile *inputFile = new QFile(filePath, this); + if (inputFile->open(QIODevice::ReadOnly)) + { + int fd = inputFile->handle(); + int version; + char name[256]; + quint8 events[EV_MAX/8 + 1]; + memset(events, 0, sizeof(events)); + + if ((ioctl(fd, EVIOCGVERSION, &version) >= 0) && (ioctl(fd, EVIOCGNAME(sizeof(name)), name)>=0) && (ioctl(fd, EVIOCGBIT(0,sizeof(events)), &events) >= 0) && test_bit(EV_KEY, events) && (((version >> 16) & 0xFF) > 0)) { + name[255]=0; + qDebug() << "CKeyboardLinux: " << qPrintable(inputFile->fileName()) << " " << name; + // Is it grabbed by someone else? + if ((ioctl(fd, EVIOCGRAB, 1) < 0)) { + qWarning("CKeyboardLinux: Device exclusively grabbed by someone else (X11 using exclusive-mode evdev?)"); + inputFile->deleteLater(); + } else { + ioctl(fd, EVIOCGRAB, 0); + + fcntl(inputFile->handle(), F_SETFL, O_NONBLOCK); + connect(new QSocketNotifier(inputFile->handle(), QSocketNotifier::Read, inputFile), SIGNAL(activated(int)), this, SLOT(inputReadyRead(int))); + + m_hashInputDevices.insert(inputFile->fileName(), inputFile); + } + } + else + inputFile->deleteLater(); + } + else + inputFile->deleteLater(); + } + + void CKeyboardLinux::keyEvent(int virtualKeyCode, bool isPressed) + { + if (CKeyMappingLinux::isMouseButton(virtualKeyCode)) + return; + + BlackMisc::Hardware::CKeyboardKey lastPressedKey = m_pressedKey; + if (m_ignoreNextKey) + { + m_ignoreNextKey = false; + return; + } + + bool isFinished = false; + if (isPressed) + { + if (CKeyMappingLinux::isModifier(virtualKeyCode)) + m_pressedKey.addModifier(CKeyMappingLinux::convertToModifier(virtualKeyCode)); + else + { + m_pressedKey.setKey(CKeyMappingLinux::convertToKey(virtualKeyCode)); + m_pressedKey.setNativeVirtualKey(0); + } + } + else + { + if (CKeyMappingLinux::isModifier(virtualKeyCode)) + m_pressedKey.removeModifier(CKeyMappingLinux::convertToModifier(virtualKeyCode)); + else + { + m_pressedKey.setKey(Qt::Key_unknown); + m_pressedKey.setNativeVirtualKey(0); + } + + isFinished = true; + } + + if (lastPressedKey == m_pressedKey) + return; + +#ifdef DEBUG_KEYBOARD + qDebug() << "Virtual key: " << virtualKeyCode; +#endif + if (m_mode == Mode_Capture) + { + if (isFinished) + { + sendCaptureNotification(lastPressedKey, true); + m_mode = Mode_Nominal; + } + else + { + sendCaptureNotification(m_pressedKey, false); + } + } + else + { + callFunctionsBy(lastPressedKey, false); + callFunctionsBy(m_pressedKey, true); + } + } } diff --git a/src/blackinput/linux/keyboard_linux.h b/src/blackinput/linux/keyboard_linux.h index 3f533cf1f..5683ec9bb 100644 --- a/src/blackinput/linux/keyboard_linux.h +++ b/src/blackinput/linux/keyboard_linux.h @@ -14,6 +14,9 @@ #include "blackmisc/hwkeyboardkey.h" #include +class QFileSystemWatcher; +class QFile; + namespace BlackInput { //! \brief Linux implemenation of IKeyboard using hook procedure @@ -60,6 +63,14 @@ namespace BlackInput //! \copydoc IKeyboard::unregisterHotkeyImpl() virtual void unregisterAllHotkeysImpl() override; + private slots: + + //! Changed directory to linux devices + void deviceDirectoryChanged(const QString &); + + //! Device is ready to read new input + void inputReadyRead(int); + private: /*! @@ -76,11 +87,27 @@ namespace BlackInput */ void callFunctionsBy(const BlackMisc::Hardware::CKeyboardKey &keySet, bool isPressed); + /*! + * \brief Add new raw input device + * \param filePath Path to device file + */ + void addRawInputDevice(const QString &filePath); + + /*! + * \brief Process new key event + * \param virtualKeyCode + * \param isPressed + */ + void keyEvent(int virtualKeyCode, bool isPressed); + QHash> m_registeredFunctions; //!< Registered hotkey functions BlackMisc::Hardware::CKeyboardKey m_pressedKey; //!< Set of virtual keys pressed in the last cycle bool m_ignoreNextKey; //!< Is true if the next key needs to be ignored Mode m_mode; //!< Operation mode + + QFileSystemWatcher *m_devInputWatcher; //!< Watches the device file system for input devices + QHash m_hashInputDevices; //!< Hash map containing all known input devices }; }