refs #401 Implementation of IJoystick for Linux

Basically, the implementation uses the API described in the kernel
documentation. It listens for new events that come from the device.
It also supports joystick hotplugging.

* https://www.kernel.org/doc/Documentation/input/joystick-api.txt
This commit is contained in:
Michał Garapich
2015-04-03 21:35:39 +02:00
committed by Roland Winklmeier
parent c94e5c0fbd
commit 2b07dfb789
3 changed files with 143 additions and 10 deletions

View File

@@ -7,29 +7,135 @@
* contained in the LICENSE file.
*/
#include "blackmisc/logmessage.h"
#include "joystick_linux.h"
#include <QFile>
#include <QFileSystemWatcher>
#include <QSocketNotifier>
#include <QSignalMapper>
#include <linux/joystick.h>
#include <unistd.h>
#include <fcntl.h>
using namespace BlackMisc;
using namespace BlackMisc::Hardware;
namespace
{
inline QString inputDevicesDir()
{
return QStringLiteral("/dev/input/");
}
}
namespace BlackInput
{
CJoystickLinux::CJoystickLinux(QObject *parent) :
IJoystick(parent)
IJoystick(parent),
m_mapper(new QSignalMapper(this)),
m_inputWatcher(new QFileSystemWatcher(this))
{
}
connect(m_mapper, static_cast<void (QSignalMapper::*)(QObject *)>(&QSignalMapper::mapped), this, &CJoystickLinux::ps_readInput);
CJoystickLinux::~CJoystickLinux()
{
m_inputWatcher->addPath(inputDevicesDir());
connect(m_inputWatcher, &QFileSystemWatcher::directoryChanged, this, &CJoystickLinux::ps_directoryChanged);
ps_directoryChanged(inputDevicesDir());
}
void CJoystickLinux::startCapture()
{
}
void CJoystickLinux::triggerButton(const CJoystickButton button, bool isPressed)
{
if(!isPressed) emit buttonUp(button);
if (!isPressed) emit buttonUp(button);
else emit buttonDown(button);
}
void CJoystickLinux::cleanupJoysticks()
{
for (auto it = m_joysticks.begin(); it != m_joysticks.end();)
{
if (!it.value()->exists())
{
it.value()->deleteLater();
it = m_joysticks.erase(it);
}
else
{
++it;
}
}
}
void CJoystickLinux::addJoystickDevice(const QString &path)
{
Q_ASSERT(!m_joysticks.contains(path));
QFile *joystick = new QFile(path, this);
if (joystick->open(QIODevice::ReadOnly))
{
char name[256];
if (ioctl(joystick->handle(), JSIOCGNAME(sizeof(name)), name) < 0)
{
strncpy(name, "Unknown", sizeof(name));
}
CLogMessage(this).info("Found joystick: %1") << name;
fcntl(joystick->handle(), F_SETFL, O_NONBLOCK);
/* Forward */
struct js_event event;
while (joystick->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event)) {}
QSocketNotifier *notifier = new QSocketNotifier(joystick->handle(), QSocketNotifier::Read, joystick);
m_mapper->setMapping(notifier, joystick);
connect(notifier, &QSocketNotifier::activated, m_mapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
notifier->setEnabled(true);
m_joysticks.insert(path, joystick);
}
else
{
joystick->deleteLater();
}
}
void CJoystickLinux::ps_directoryChanged(QString path)
{
cleanupJoysticks();
QDir dir(path, QLatin1String("js*"), 0, QDir::System);
for (const auto &entry : dir.entryInfoList())
{
QString f = entry.absoluteFilePath();
if (!m_joysticks.contains(f))
{
addJoystickDevice(f);
}
}
}
void CJoystickLinux::ps_readInput(QObject *object)
{
QFile *joystick = qobject_cast<QFile *>(object);
Q_ASSERT(joystick);
struct js_event event;
while (joystick->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event))
{
switch (event.type & ~JS_EVENT_INIT)
{
case JS_EVENT_BUTTON:
if (event.value)
emit buttonDown(CJoystickButton(event.number));
else
emit buttonUp(CJoystickButton(event.number));
break;
}
}
}
} // namespace BlackInput

View File

@@ -15,11 +15,17 @@
#include "blackinput/joystick.h"
#include "blackmisc/hardware/joystickbutton.h"
#include "blackmisc/collection.h"
#include <QSet>
#include <QMap>
#include <QString>
class QFile;
class QFileSystemWatcher;
class QSignalMapper;
namespace BlackInput
{
//! Linux implemenation of IJoystick with DirectInput
//! Linux implemenation of IJoystick
//! \sa https://www.kernel.org/doc/Documentation/input/joystick-api.txt
class CJoystickLinux : public IJoystick
{
Q_OBJECT
@@ -33,7 +39,7 @@ namespace BlackInput
CJoystickLinux &operator=(CJoystickLinux const &) = delete;
//! \brief Destructor
virtual ~CJoystickLinux();
virtual ~CJoystickLinux() = default;
//! \copydoc IJoystick::startCapture()
virtual void startCapture() override;
@@ -45,10 +51,31 @@ namespace BlackInput
friend class IJoystick;
//! Destructor
//! Removes all joysticks that are no longer present.
void cleanupJoysticks();
//! Adds new joystick input for reading
void addJoystickDevice(const QString &path);
//! Constructor
CJoystickLinux(QObject *parent = nullptr);
private slots:
//! Slot for handling directory changes
//! \param path Watched directory path.
void ps_directoryChanged(QString path);
//! Slot for reading the device handle
//! \param object QFile that has data to be read.
void ps_readInput(QObject *object);
private:
IJoystick::Mode m_mode = ModeNominal; //!< Current working mode
QSignalMapper *m_mapper = nullptr; //!< Maps device handles
QMap<QString, QFile *> m_joysticks; //!< All read joysticks, file path <-> file instance pairs
QFileSystemWatcher *m_inputWatcher = nullptr;
};
} // namespace BlackInput

View File

@@ -131,7 +131,7 @@ namespace BlackInput
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)) {
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) && !test_bit(EV_SYN, events) && (((version >> 16) & 0xFF) > 0)) {
name[255]=0;
qDebug() << "CKeyboardLinux: " << qPrintable(inputFile->fileName()) << " " << name;
// Is it grabbed by someone else?