swift
joysticklinux.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "joysticklinux.h"
5 
6 #include <fcntl.h>
7 #include <linux/joystick.h>
8 #include <unistd.h>
9 
10 #include <QDir>
11 #include <QFile>
12 #include <QFileSystemWatcher>
13 #include <QSignalMapper>
14 #include <QSocketNotifier>
15 
16 #include "misc/logmessage.h"
17 
18 using namespace swift::misc;
19 using namespace swift::misc::input;
20 
21 namespace
22 {
23  inline QString inputDevicesDir() { return QStringLiteral("/dev/input/"); }
24 } // namespace
25 
26 namespace swift::input
27 {
28  CJoystickDevice::CJoystickDevice(const QString &path, QFile *fd, QObject *parent)
29  : QObject(parent), m_path(path), m_fd(fd)
30  {
31  m_fd->setParent(this);
32  char deviceName[256];
33  if (ioctl(m_fd->handle(), JSIOCGNAME(sizeof(deviceName)), deviceName) < 0)
34  {
35  strncpy(deviceName, "Unknown", sizeof(deviceName));
36  }
37 
38  CLogMessage(this).info(u"Found joystick: %1") << deviceName;
39 
40  fcntl(m_fd->handle(), F_SETFL, O_NONBLOCK);
41 
42  /* Forward */
43  struct js_event event;
44  while (m_fd->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event)) {}
45  QSocketNotifier *notifier = new QSocketNotifier(m_fd->handle(), QSocketNotifier::Read, m_fd);
46  connect(notifier, &QSocketNotifier::activated, this, &CJoystickDevice::processInput);
47  m_name = QString(deviceName);
48  }
49 
50  CJoystickDevice::~CJoystickDevice()
51  {
52  if (m_fd)
53  {
54  m_fd->close();
55  m_fd->deleteLater();
56  }
57  }
58 
59  void CJoystickDevice::processInput()
60  {
61  struct js_event event;
62  while (m_fd->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event))
63  {
64  switch (event.type & ~JS_EVENT_INIT)
65  {
66  case JS_EVENT_BUTTON:
67  if (event.value) { emit buttonChanged(m_name, event.number, true); }
68  else { emit buttonChanged(m_name, event.number, false); }
69  break;
70  }
71  }
72  }
73 
74  CJoystickLinux::CJoystickLinux(QObject *parent) : IJoystick(parent), m_inputWatcher(new QFileSystemWatcher(this))
75  {
76  m_inputWatcher->addPath(inputDevicesDir());
77  connect(m_inputWatcher, &QFileSystemWatcher::directoryChanged, this, &CJoystickLinux::reloadDevices);
78  reloadDevices(inputDevicesDir());
79  }
80 
82  {
83  // We don't know which buttons are available yet.
84  return {};
85  }
86 
87  void CJoystickLinux::cleanupJoysticks()
88  {
89  for (auto it = m_joystickDevices.begin(); it != m_joystickDevices.end();)
90  {
91  // Remove all joysticks that do not exist anymore (/dev/input/js* removed).
92  if (!(*it)->isAttached())
93  {
94  CJoystickDevice *joystickDevice = *it;
95  it = m_joystickDevices.erase(it);
96  joystickDevice->deleteLater();
97  }
98  else { ++it; }
99  }
100  }
101 
102  void CJoystickLinux::addJoystickDevice(const QString &path)
103  {
104  QFile *fd = new QFile(path);
105  if (fd->open(QIODevice::ReadOnly))
106  {
107  CJoystickDevice *joystickDevice = new CJoystickDevice(path, fd, this);
108  connect(joystickDevice, &CJoystickDevice::buttonChanged, this, &CJoystickLinux::joystickButtonChanged);
109  m_joystickDevices.push_back(joystickDevice);
110  }
111  else
112  {
113  swift::misc::CLogMessage(this).error(u"Failed to open joystick device %1: %2")
114  << fd->fileName() << fd->errorString();
115  fd->close();
116  fd->deleteLater();
117  }
118  }
119 
120  void CJoystickLinux::joystickButtonChanged(const QString &name, int index, bool isPressed)
121  {
122  swift::misc::input::CHotkeyCombination oldCombination(m_buttonCombination);
123  if (isPressed) { m_buttonCombination.addJoystickButton({ name, index }); }
124  else { m_buttonCombination.removeJoystickButton({ name, index }); }
125 
126  if (oldCombination != m_buttonCombination) { emit buttonCombinationChanged(m_buttonCombination); }
127  }
128 
129  void CJoystickLinux::reloadDevices(QString path)
130  {
131  cleanupJoysticks();
132 
133  QDir dir(path, QLatin1String("js*"), QDir::Name, QDir::System);
134  for (const auto &entry : dir.entryInfoList())
135  {
136  QString f = entry.absoluteFilePath();
137  auto it = std::find_if(m_joystickDevices.begin(), m_joystickDevices.end(),
138  [path](const CJoystickDevice *device) { return device->getPath() == path; });
139  if (it == m_joystickDevices.end()) { addJoystickDevice(f); }
140  }
141  }
142 
143 } // namespace swift::input
Linux Joystick device.
Definition: joysticklinux.h:24
void buttonChanged(const QString &name, int index, bool isPressed)
Joystick button changed.
CJoystickLinux(CJoystickLinux const &)=delete
Copy Constructor.
virtual swift::misc::input::CJoystickButtonList getAllAvailableJoystickButtons() const
Get all available joystick buttons.
void buttonCombinationChanged(const swift::misc::input::CHotkeyCombination &)
Joystick button combination has changed.
Class for emitting a log message.
Definition: logmessage.h:27
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Value object representing hotkey sequence.
void removeJoystickButton(CJoystickButton button)
Remove joystick button.
void addJoystickButton(const CJoystickButton &button)
Add joystick button.
Value object encapsulating a list of joystick buttons.
Free functions in swift::misc.