Skip to content

Commit

Permalink
Merge PR #748: New plugin: Add linux input device support
Browse files Browse the repository at this point in the history
  • Loading branch information
jenkins committed Nov 19, 2024
2 parents e868b6a + dc780ac commit 35af6ee
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 0 deletions.
11 changes: 11 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,17 @@ Conflicts: nymea-plugins-translations (<< 1.0.1)
Description: nymea integration plugin for lifx
This package contains the nymea integration plugin for lifx devices


Package: nymea-plugin-linuxinput
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
Conflicts: nymea-plugins-translations (<< 1.0.1)
Description: nymea integration plugin for linux input devices
This package contains the nymea integration plugin integrating generic linux
input devices like keyboards, mouse and joystick devices.


Package: nymea-plugin-logilink
Architecture: any
Section: libs
Expand Down
155 changes: 155 additions & 0 deletions linuxinput/inputdevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include "inputdevice.h"

#include <QDir>
#include <QDebug>
#include <QFile>

#include "extern-plugininfo.h"
#include "inputdeviceeventmonitor.h"

#include <libevdev/libevdev.h>

#include <unistd.h>
#include <fcntl.h>

InputDevice::InputDevice(QObject *parent)
: QObject{parent}
{

}

InputDevice::InputDevice(Info inputDeviceInfo, QObject *parent)
: QObject{parent},
m_inputDeviceInfo{inputDeviceInfo},
m_pollThread{new QThread(nullptr)}
{
qCDebug(dcLinuxInput()) << "Constructing" << inputDeviceInfo;
InputDeviceEventMonitor *monitor = new InputDeviceEventMonitor(inputDeviceInfo.eventFilePath, nullptr);
monitor->moveToThread(m_pollThread);

connect(m_pollThread, &QThread::started, monitor, &InputDeviceEventMonitor::process);
connect(monitor, &InputDeviceEventMonitor::finished, m_pollThread, &QThread::quit);
connect(monitor, &InputDeviceEventMonitor::finished, monitor, &InputDeviceEventMonitor::deleteLater);
connect(m_pollThread, &QThread::finished, monitor, &InputDeviceEventMonitor::deleteLater);
connect(m_pollThread, &QThread::finished, m_pollThread, [this](){
qCDebug(dcLinuxInput()) << "Monitoring thread finished" << m_inputDeviceInfo;
m_pollThread->deleteLater();
m_pollThread = nullptr;
});

connect(monitor, &InputDeviceEventMonitor::eventOccurred, this, &InputDevice::onEventOccurred);

qCDebug(dcLinuxInput()) << "Start monitoring" << inputDeviceInfo;
m_pollThread->start();
}

InputDevice::~InputDevice()
{
if (m_pollThread) {
m_pollThread->terminate();
}
}

InputDevice::Info InputDevice::inputDeviceInfo() const
{
return m_inputDeviceInfo;
}

QList<InputDevice::Info> InputDevice::availableInputDevices()
{
QList<Info> inputeDevices;
QStringList addedInputIds;
QDir inputByIdDir("/dev/input/by-path/");

// Get the keyboards
qCDebug(dcLinuxInput()) << "---------- Keyboards:";
foreach (const QString &inputIdName, inputByIdDir.entryList({"*event-kbd"}, QDir::Dirs | QDir::NoDotAndDotDot)) {
Info info = parseDeviceInfo(Keyboard, QFileInfo(inputByIdDir.absolutePath() + QDir::separator() + inputIdName));
qCDebug(dcLinuxInput()) << " --> " << info;
inputeDevices.append(info);
addedInputIds.append(inputIdName);
}

qCDebug(dcLinuxInput()) << "---------- Mouse:";
foreach (const QString &inputIdName, inputByIdDir.entryList({"*event-mouse"}, QDir::Dirs | QDir::NoDotAndDotDot)) {
Info info = parseDeviceInfo(Mouse, QFileInfo(inputByIdDir.absolutePath() + QDir::separator() + inputIdName));
qCDebug(dcLinuxInput()) << " --> " << info;
inputeDevices.append(info);
addedInputIds.append(inputIdName);
}

qCDebug(dcLinuxInput()) << "---------- Other:";
foreach (const QString &inputIdName, inputByIdDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
if (addedInputIds.contains(inputIdName))
continue;

Info info = parseDeviceInfo(Other, QFileInfo(inputByIdDir.absolutePath() + QDir::separator() + inputIdName));
qCDebug(dcLinuxInput()) << " --> " << info;
inputeDevices.append(info);
addedInputIds.append(inputIdName);
}

return inputeDevices;
}

void InputDevice::onEventOccurred(quint16 type, quint16 code, qint32 value)
{
if (type != EV_KEY)
return;

qCDebug(dcLinuxInput()) << "Event occurred" << type << code << value;
emit keyPressed(code, value);
}

QString InputDevice::readFileContent(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCWarning(dcLinuxInput()) << "Could not open file" << fileName << ":" << file.errorString();
return QString();
}

QString content;
QTextStream in(&file);
content = in.readAll();
file.close();

return content.trimmed();
}

InputDevice::Info InputDevice::parseDeviceInfo(Type type, const QFileInfo &inputLinkFileInfo)
{
QFileInfo inputFileInfo(inputLinkFileInfo.symLinkTarget());

QDir inputsDir("/sys/class/input/");
QDir inputDir(inputsDir.absolutePath() + QDir::separator() + inputFileInfo.baseName() + QDir::separator() + "device");

Info info;
info.type = type;
info.id = QString(inputFileInfo.baseName()).remove("event").toInt();
info.name = readFileContent(inputDir.absolutePath() + QDir::separator() + "name");
info.phys = readFileContent(inputDir.absolutePath() + QDir::separator() + "phys");
info.eventFilePath = inputLinkFileInfo.symLinkTarget();

return info;
}

QDebug operator<<(QDebug debug, InputDevice::Info inputDeviceInfo)
{
QDebugStateSaver saver(debug);
debug.nospace() << "Info(" << inputDeviceInfo.type
<< ", name: " << inputDeviceInfo.name
<< ", Phys: " << inputDeviceInfo.phys
<< ", " << inputDeviceInfo.eventFilePath
<< ')';
return debug;
}

QDebug operator<<(QDebug debug, InputDevice *inputDevice)
{
QDebugStateSaver saver(debug);
debug.nospace() << "InputDevice(" << inputDevice->inputDeviceInfo() << ')';
return debug;
}


60 changes: 60 additions & 0 deletions linuxinput/inputdevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef INPUTDEVICE_H
#define INPUTDEVICE_H

#include <QDir>
#include <QDebug>
#include <QObject>
#include <QThread>

#include <linux/input-event-codes.h>
#include <linux/input.h>

class InputDevice : public QObject
{
Q_OBJECT
public:
enum Type {
Unknown,
Keyboard,
Mouse,
Other
};
Q_ENUM(Type)

typedef struct Info {
int id = -1;
Type type = Unknown;
QString name;
QString description;
QString phys;
QString eventFilePath;
} InputDeviceInfo;

explicit InputDevice(QObject *parent = nullptr);
explicit InputDevice(Info inputDeviceInfo, QObject *parent = nullptr);
~InputDevice();

InputDevice::Info inputDeviceInfo() const;

static QList<InputDevice::Info> availableInputDevices();

signals:
void keyPressed(quint16 code, qint32 value);

private slots:
void onEventOccurred(quint16 type, quint16 code, qint32 value);

private:
QString m_eventFileName;
static QString readFileContent(const QString &fileName);
Info m_inputDeviceInfo;
QThread *m_pollThread = nullptr;

static InputDevice::Info parseDeviceInfo(Type type, const QFileInfo &inputLinkFileInfo);
};

QDebug operator<<(QDebug debug, InputDevice::Info inputDeviceInfo);
QDebug operator<<(QDebug debug, InputDevice *inputDevice);


#endif // INPUTDEVICE_H
62 changes: 62 additions & 0 deletions linuxinput/inputdeviceeventmonitor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "inputdeviceeventmonitor.h"
#include "extern-plugininfo.h"

#include <QFile>
#include <QDebug>

#include <fcntl.h>
#include <unistd.h>
#include <libevdev/libevdev.h>

InputDeviceEventMonitor::InputDeviceEventMonitor(const QString &fileName, QObject *parent)
: QObject{parent},
m_fileName{fileName}
{

}

void InputDeviceEventMonitor::process()
{
struct libevdev *device = nullptr;

int fd = open(m_fileName.toStdString().c_str(), O_RDWR|O_CLOEXEC);
if (fd < 0) {
qCWarning(dcLinuxInput()) << "Could not open file" << m_fileName;
emit finished();
return;
}

int status = libevdev_new_from_fd(fd, &device);
if (status != 0) {
qCWarning(dcLinuxInput()) << "Failed to create evdev from file descriptor";
close(fd);
emit finished();
return;
}

qCDebug(dcLinuxInput()) << "Start monitoring events on" << m_fileName;
qCDebug(dcLinuxInput()) << "- name: " << libevdev_get_name(device);
qCDebug(dcLinuxInput()) << "- phys: " << libevdev_get_phys(device);

struct input_event event = {};
auto hasError = [](int v) {
return v < 0 && v != -EAGAIN;
};
auto hasNextEvent = [](int v) {
return v >= 0;
};

while (status = libevdev_next_event(device, LIBEVDEV_READ_FLAG_NORMAL | LIBEVDEV_READ_FLAG_BLOCKING, &event), !hasError(status)) {
if (!hasNextEvent(status))
continue;

emit eventOccurred(event.type, event.code, event.value);
}

libevdev_free(device);
device = nullptr;

close(fd);

emit finished();
}
24 changes: 24 additions & 0 deletions linuxinput/inputdeviceeventmonitor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef INPUTDEVICEEVENTMONITOR_H
#define INPUTDEVICEEVENTMONITOR_H

#include <QObject>

class InputDeviceEventMonitor : public QObject
{
Q_OBJECT
public:
explicit InputDeviceEventMonitor(const QString &fileName, QObject *parent = nullptr);

public slots:
void process();

signals:
void eventOccurred(quint16 type, quint16 code, qint32 value);
void finished();

private:
QString m_fileName;

};

#endif // INPUTDEVICEEVENTMONITOR_H
1 change: 1 addition & 0 deletions nymea-plugins.pro
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ PLUGIN_DIRS = \
kodi \
lgsmarttv \
lifx \
linuxinput \
logilink \
mecelectronics \
meross \
Expand Down

0 comments on commit 35af6ee

Please sign in to comment.