MythTV  master
jsmenu.cpp
Go to the documentation of this file.
1 /*----------------------------------------------------------------------------
2 ** jsmenu.cpp
3 **
4 ** Description:
5 ** Set of functions to generate key events based on
6 ** input from a Joystick.
7 **
8 ** Original Copyright 2004 by Jeremy White <jwhite@whitesen.org>
9 **
10 ** License:
11 ** This program is free software; you can redistribute it
12 ** and/or modify it under the terms of the GNU General
13 ** Public License as published bythe Free Software Foundation;
14 ** either version 2, or (at your option)
15 ** any later version.
16 **
17 ** This program is distributed in the hope that it will be useful,
18 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ** GNU General Public License for more details.
21 **
22 **--------------------------------------------------------------------------*/
23 
24 // Own header
25 #include "devices/jsmenu.h"
26 
27 // QT headers
28 #include <QCoreApplication>
29 #include <QEvent>
30 #include <QKeySequence>
31 #include <QTextStream>
32 #include <QStringList>
33 #include <QFile>
34 
35 // C/C++ headers
36 #include <cstdio>
37 #include <cerrno>
38 #include <sys/wait.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 
43 // Kernel joystick header
44 #include <linux/joystick.h>
45 
46 // Myth headers
47 #include "libmythbase/mythconfig.h"
49 
50 // Mythui headers
51 #include "jsmenuevent.h"
52 
53 #if HAVE_LIBUDEV
54 #include <libudev.h>
55 #endif
56 
57 #define LOC QString("JoystickMenuThread: ")
58 
60 {
61  if (m_fd != -1)
62  {
63  close(m_fd);
64  m_fd = -1;
65  }
66 
67  delete [] m_axes;
68  m_axes = nullptr;
69 
70  delete [] m_buttons;
71  m_buttons = nullptr;
72 }
73 
77 bool JoystickMenuThread::Init(QString &config_file)
78 {
79  /*------------------------------------------------------------------------
80  ** Read the config file
81  **----------------------------------------------------------------------*/
82  if (!ReadConfig(config_file)){
83  m_configRead = false;
84  return false;
85  }
86  m_configFile = config_file;
87  m_configRead = true;
88 
89  /*------------------------------------------------------------------------
90  ** Open the joystick device, retrieve basic info
91  **----------------------------------------------------------------------*/
92  m_fd = open(qPrintable(m_devicename), O_RDONLY);
93  if (m_fd == -1)
94  {
95  LOG(VB_GENERAL, LOG_ERR, LOC +
96  QString("Joystick disabled - Failed to open device %1")
97  .arg(m_devicename));
98  m_readError = true;
99  // If udev is avaliable we want to return true on read error to start the required loop
100 #if HAVE_LIBUDEV
101  return true;
102 #else
103  return false;
104 #endif
105  }
106 
107  int rc = ioctl(m_fd, JSIOCGAXES, &m_axesCount);
108  if (rc == -1)
109  {
110  LOG(VB_GENERAL, LOG_ERR, LOC +
111  "Joystick disabled - ioctl JSIOCGAXES failed");
112  return false;
113  }
114 
115  rc = ioctl(m_fd, JSIOCGBUTTONS, &m_buttonCount);
116  if (rc == -1)
117  {
118  LOG(VB_GENERAL, LOG_ERR, LOC +
119  "Joystick disabled - ioctl JSIOCGBUTTONS failed");
120  return false;
121  }
122 
123  LOG(VB_GENERAL, LOG_INFO, LOC +
124  QString("Controller has %1 axes and %2 buttons")
125  .arg(m_axesCount).arg(m_buttonCount));
126 
127  /*------------------------------------------------------------------------
128  ** Allocate the arrays in which we track button and axis status
129  **----------------------------------------------------------------------*/
130  m_buttons = new int[m_buttonCount];
131  memset(m_buttons, '\0', m_buttonCount * sizeof(*m_buttons));
132 
133  m_axes = new int[m_axesCount];
134  memset(m_axes, '\0', m_axesCount * sizeof(*m_axes));
135 
136  LOG(VB_GENERAL, LOG_INFO, LOC +
137  QString("Initialization of %1 succeeded using config file %2")
138  .arg(m_devicename, config_file));
139  m_readError = false;
140  return true;
141 }
142 
156 bool JoystickMenuThread::ReadConfig(const QString& config_file)
157 {
158  if (!QFile::exists(config_file))
159  {
160  LOG(VB_GENERAL, LOG_INFO, "No joystick configuration found, not enabling joystick control");
161  return false;
162  }
163 
164  FILE *fp = fopen(qPrintable(config_file), "r");
165  if (!fp)
166  {
167  LOG(VB_GENERAL, LOG_ERR, LOC +
168  QString("Joystick disabled - Failed to open %1") .arg(config_file));
169  return false;
170  }
171 
172  m_map.Clear();
173 
174  QTextStream istream(fp);
175  for (int line = 1; ! istream.atEnd(); line++)
176  {
177  QString rawline = istream.readLine();
178  QString simple_line = rawline.simplified();
179  if (simple_line.isEmpty() || simple_line.startsWith('#'))
180  continue;
181 
182  QStringList tokens = simple_line.split(" ");
183  if (tokens.count() < 1)
184  continue;
185 
186  QString firstTok = tokens[0].toLower();
187 
188  if (firstTok.startsWith("devicename") && tokens.count() == 2)
189  {
190  m_devicename = tokens[1];
191  }
192  else if (firstTok.startsWith("button") && tokens.count() == 3)
193  {
194  m_map.AddButton(tokens[1].toInt(), tokens[2]);
195  }
196  else if (firstTok.startsWith("axis") && tokens.count() == 5)
197  {
198  m_map.AddAxis(tokens[1].toInt(), tokens[2].toInt(),
199  tokens[3].toInt(), tokens[4]);
200  }
201  else if (firstTok.startsWith("chord") && tokens.count() == 4)
202  {
203  m_map.AddButton(tokens[2].toInt(), tokens[3], tokens[1].toInt());
204  }
205  else
206  {
207  LOG(VB_GENERAL, LOG_WARNING, LOC +
208  QString("ReadConfig(%1) unrecognized or malformed line \"%2\" ")
209  .arg(line) .arg(rawline));
210  }
211  }
212 
213  fclose(fp);
214  return true;
215 }
216 
217 
223 {
224  RunProlog();
225 
226  fd_set readfds;
227  struct js_event js {};
228  struct timeval timeout {};
229 
230  while (!m_bStop && m_configRead)
231  {
232 #if HAVE_LIBUDEV
233  if (m_configRead && m_readError)
234  {
235  LOG(VB_GENERAL, LOG_INFO, LOC +
236  QString("Joystick error, Awaiting Reconnection"));
237  struct udev *udev = udev_new();
238  /* Set up a monitor to monitor input devices */
239  struct udev_monitor *mon =
240  udev_monitor_new_from_netlink(udev, "udev");
241  udev_monitor_filter_add_match_subsystem_devtype(mon, "input", nullptr);
242  udev_monitor_enable_receiving(mon);
243  /* Get the file descriptor (fd) for the monitor.
244  This fd will get passed to select() */
245  int fd = udev_monitor_get_fd(mon);
246  /* This section will run till no error, calling usleep() at
247  the end of each pass. This is to use a udev_monitor in a
248  non-blocking way. */
249  /*===========================================================
250  * instead of a loop, could QSocketNotifier be used here
251  *=========================================================*/
252  while (!m_bStop && m_configRead && m_readError)
253  {
254  /* Set up the call to select(). In this case, select() will
255  only operate on a single file descriptor, the one
256  associated with our udev_monitor. Note that the timeval
257  object is set to 0, which will cause select() to not
258  block.
259  */
260  fd_set fds;
261  struct timeval tv {};
262  FD_ZERO(&fds);
263  FD_SET(fd, &fds);
264  tv.tv_sec = 0;
265  tv.tv_usec = 0;
266  int ret = select(fd+1, &fds, nullptr, nullptr, &tv);
267  /* Check if our file descriptor has received data. */
268  if (ret > 0 && FD_ISSET(fd, &fds))
269  {
270  struct udev_device *dev = udev_monitor_receive_device(mon);
271  if (dev)
272  {
273  this->Init(m_configFile);
274  udev_device_unref(dev);
275  }
276  }
277  usleep(250ms);
278  }
279  // unref the monitor
280  udev_monitor_unref(mon); // Also closes fd.
281  udev_unref(udev);
282  }
283 #endif
284 
285  /*--------------------------------------------------------------------
286  ** Wait for activity from the joy stick (we wait a configurable
287  ** poll time)
288  **------------------------------------------------------------------*/
289  FD_ZERO(&readfds); // NOLINT(readability-isolate-declaration)
290  FD_SET(m_fd, &readfds);
291 
292  // the maximum time select() should wait
293  timeout.tv_sec = 0;
294  timeout.tv_usec = 100000;
295 
296  int rc = select(m_fd + 1, &readfds, nullptr, nullptr, &timeout);
297  if (rc == -1)
298  {
299  /*----------------------------------------------------------------
300  ** TODO: In theory, we could recover from file errors
301  ** (what happens when we unplug a joystick?)
302  **--------------------------------------------------------------*/
303  LOG(VB_GENERAL, LOG_ERR, "select: " + ENO);
304 #if HAVE_LIBUDEV
305  m_readError = true;
306  continue;
307 #else
308  return;
309 #endif
310  }
311 
312  if (rc == 1)
313  {
314  /*----------------------------------------------------------------
315  ** Read a joystick event
316  **--------------------------------------------------------------*/
317  rc = read(m_fd, &js, sizeof(js));
318  if (rc != sizeof(js))
319  {
320  LOG(VB_GENERAL, LOG_ERR, "error reading js:" + ENO);
321 #if HAVE_LIBUDEV
322  m_readError = true;
323  continue;
324 #else
325  return;
326 #endif
327  }
328 
329  /*----------------------------------------------------------------
330  ** Events sent with the JS_EVENT_INIT flag are always sent
331  ** right after you open the joy stick; they are useful
332  ** for learning the initial state of buttons and axes
333  **--------------------------------------------------------------*/
334  if (js.type & JS_EVENT_INIT)
335  {
336  if (js.type & JS_EVENT_BUTTON && js.number < m_buttonCount)
337  m_buttons[js.number] = js.value;
338 
339  if (js.type & JS_EVENT_AXIS && js.number < m_axesCount)
340  m_axes[js.number] = js.value;
341  }
342  else
343  {
344  /*------------------------------------------------------------
345  ** Record new button states and look for triggers
346  ** that would make us send a key.
347  ** Things are a little tricky here; for buttons, we
348  ** only act on button up events, not button down
349  ** (this lets us implement the chord function).
350  ** For axes, we only register a change if the
351  ** Joystick moves into the specified range
352  ** (that way, we only get one event per joystick
353  ** motion).
354  **----------------------------------------------------------*/
355  if (js.type & JS_EVENT_BUTTON && js.number < m_buttonCount)
356  {
357  if (js.value == 0 && m_buttons[js.number] == 1)
358  ButtonUp(js.number);
359 
360  m_buttons[js.number] = js.value;
361  }
362 
363  if (js.type & JS_EVENT_AXIS && js.number < m_axesCount)
364  {
365  AxisChange(js.number, js.value);
366  m_axes[js.number] = js.value;
367  }
368 
369  }
370 
371  }
372 
373  }
374 
375  RunEpilog();
376 }
377 
381 void JoystickMenuThread::EmitKey(const QString& code)
382 {
383  QKeySequence a(code);
384 
385  int key { QKeySequence::UnknownKey };
386  Qt::KeyboardModifiers modifiers { Qt::NoModifier };
387 
388  // Send a dummy keycode if we couldn't convert the key sequence.
389  // This is done so the main code can output a warning for bad
390  // mappings.
391  if (a.isEmpty())
392  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
393  key, Qt::NoModifier, QEvent::KeyPress));
394 
395  for (int i = 0; i < a.count(); i++)
396  {
397 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
398  key = a[i] & ~(Qt::MODIFIER_MASK);
399  modifiers = static_cast<Qt::KeyboardModifiers>(a[i] & Qt::MODIFIER_MASK);
400 #else
401  key = a[i].key();
402  modifiers = a[i].keyboardModifiers();
403 #endif
404 
405  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
406  key, modifiers, QEvent::KeyPress));
407  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
408  key, modifiers, QEvent::KeyRelease));
409  }
410 }
411 
412 
420 {
421  /*------------------------------------------------------------------------
422  ** Process chords first
423  **----------------------------------------------------------------------*/
424  JoystickMap::button_map_t::const_iterator bmap;
425  for (bmap = m_map.buttonMap().begin(); bmap != m_map.buttonMap().end();
426  ++bmap)
427  {
428  if (button == bmap->button && bmap->chord != -1
429  && m_buttons[bmap->chord] == 1)
430  {
431  EmitKey(bmap->keystring);
432  m_buttons[bmap->chord] = 0;
433  return;
434  }
435  }
436 
437  /*------------------------------------------------------------------------
438  ** Process everything else
439  **----------------------------------------------------------------------*/
440  for (bmap = m_map.buttonMap().begin(); bmap != m_map.buttonMap().end();
441  ++bmap)
442  {
443  if (button == bmap->button && bmap->chord == -1)
444  EmitKey(bmap->keystring);
445  }
446 }
447 
451 void JoystickMenuThread::AxisChange(int axis, int value)
452 {
453  JoystickMap::axis_map_t::const_iterator amap;
454  for (amap = m_map.axisMap().begin(); amap < m_map.axisMap().end(); ++amap)
455  {
456  if (axis == amap->axis)
457  {
458  /* If we're currently outside the range, and the move is
459  ** into the range, then we trigger */
460  if (m_axes[axis] < amap->from || m_axes[axis] > amap->to)
461  if (value >= amap->from && value <= amap->to)
462  EmitKey(amap->keystring);
463  }
464  }
465 }
JoystickMenuThread::m_map
JoystickMap m_map
Definition: jsmenu.h:112
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
JoystickMenuThread::m_readError
bool m_readError
Definition: jsmenu.h:105
jsmenu.h
JoystickMenuThread::EmitKey
void EmitKey(const QString &code)
Send a keyevent to the main UI loop with the appropriate keycode.
Definition: jsmenu.cpp:381
JoystickMenuThread::m_bStop
volatile bool m_bStop
Definition: jsmenu.h:129
discid.disc.read
def read(device=None, features=[])
Definition: disc.py:35
jsmenuevent.h
JoystickKeycodeEvent
Definition: jsmenuevent.h:16
MThread::usleep
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:335
xbmcvfs.exists
bool exists(str path)
Definition: xbmcvfs.py:51
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
JoystickMenuThread::m_buttonCount
unsigned char m_buttonCount
Track the status of the joystick buttons as we do depend slightly on state.
Definition: jsmenu.h:118
JoystickMenuThread::m_buttons
int * m_buttons
Definition: jsmenu.h:126
JoystickMenuThread::m_configFile
QString m_configFile
Definition: jsmenu.h:107
mythburn.FILE
int FILE
Definition: mythburn.py:139
close
#define close
Definition: compat.h:43
JoystickMenuThread::m_axes
int * m_axes
Definition: jsmenu.h:127
JoystickMap::Clear
void Clear()
Definition: jsmenu.h:66
JoystickMenuThread::m_fd
int m_fd
Definition: jsmenu.h:111
mythlogging.h
JoystickMenuThread::ButtonUp
void ButtonUp(int button)
Handle a button up event.
Definition: jsmenu.cpp:419
LOC
#define LOC
Definition: jsmenu.cpp:57
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
JoystickMenuThread::m_configRead
bool m_configRead
Definition: jsmenu.h:104
JoystickMap::AddAxis
void AddAxis(int in_axis, int in_from, int in_to, QString in_keystr)
Definition: jsmenu.h:60
JoystickMenuThread::m_devicename
QString m_devicename
Definition: jsmenu.h:110
JoystickMenuThread::m_mainWindow
QObject * m_mainWindow
Definition: jsmenu.h:109
JoystickMenuThread::AxisChange
void AxisChange(int axis, int value)
Handle a registered change in a joystick axis.
Definition: jsmenu.cpp:451
JoystickMap::buttonMap
const button_map_t & buttonMap() const
Definition: jsmenu.h:73
JoystickMenuThread::run
void run() override
This function is the heart of a thread which looks for Joystick input and translates it into key pres...
Definition: jsmenu.cpp:222
JoystickMenuThread::~JoystickMenuThread
~JoystickMenuThread() override
Definition: jsmenu.cpp:59
JoystickMenuThread::ReadConfig
bool ReadConfig(const QString &config_file)
Read from action to key mappings from flat file config file.
Definition: jsmenu.cpp:156
JoystickMenuThread::Init
bool Init(QString &config_file)
Initialise the class variables with values from the config file.
Definition: jsmenu.cpp:77
JoystickMap::AddButton
void AddButton(int in_button, QString in_keystr, int in_chord=-1)
Definition: jsmenu.h:54
JoystickMap::axisMap
const axis_map_t & axisMap() const
Definition: jsmenu.h:74
JoystickMenuThread::m_axesCount
unsigned char m_axesCount
Track the status of the joystick axes as we do depend slightly on state.
Definition: jsmenu.h:124