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 "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 "mythlogging.h"
48 
49 // Mythui headers
50 #include "jsmenuevent.h"
51 
52 #define LOC QString("JoystickMenuThread: ")
53 
55 {
56  if (m_fd != -1)
57  {
58  close(m_fd);
59  m_fd = -1;
60  }
61 
62  delete [] m_axes;
63  m_axes = nullptr;
64 
65  delete [] m_buttons;
66  m_buttons = nullptr;
67 }
68 
72 bool JoystickMenuThread::Init(QString &config_file)
73 {
74  /*------------------------------------------------------------------------
75  ** Read the config file
76  **----------------------------------------------------------------------*/
77  if (!ReadConfig(config_file))
78  return false;
79 
80  /*------------------------------------------------------------------------
81  ** Open the joystick device, retrieve basic info
82  **----------------------------------------------------------------------*/
83  m_fd = open(qPrintable(m_devicename), O_RDONLY);
84  if (m_fd == -1)
85  {
86  LOG(VB_GENERAL, LOG_ERR, LOC +
87  QString("Joystick disabled - Failed to open device %1")
88  .arg(m_devicename));
89  return false;
90  }
91 
92  int rc = ioctl(m_fd, JSIOCGAXES, &m_axesCount);
93  if (rc == -1)
94  {
95  LOG(VB_GENERAL, LOG_ERR, LOC +
96  "Joystick disabled - ioctl JSIOCGAXES failed");
97  return false;
98  }
99 
100  rc = ioctl(m_fd, JSIOCGBUTTONS, &m_buttonCount);
101  if (rc == -1)
102  {
103  LOG(VB_GENERAL, LOG_ERR, LOC +
104  "Joystick disabled - ioctl JSIOCGBUTTONS failed");
105  return false;
106  }
107 
108  /*------------------------------------------------------------------------
109  ** Allocate the arrays in which we track button and axis status
110  **----------------------------------------------------------------------*/
111  m_buttons = new int[m_buttonCount];
112  memset(m_buttons, '\0', m_buttonCount * sizeof(*m_buttons));
113 
114  m_axes = new int[m_axesCount];
115  memset(m_axes, '\0', m_axesCount * sizeof(*m_axes));
116 
117  LOG(VB_GENERAL, LOG_INFO, LOC +
118  QString("Initialization of %1 succeeded using config file %2")
119  .arg(m_devicename) .arg(config_file));
120  return true;
121 }
122 
136 bool JoystickMenuThread::ReadConfig(const QString& config_file)
137 {
138  if (!QFile::exists(config_file))
139  {
140  LOG(VB_GENERAL, LOG_INFO, "No joystick configuration found, not enabling joystick control");
141  return false;
142  }
143 
144  FILE *fp = fopen(qPrintable(config_file), "r");
145  if (!fp)
146  {
147  LOG(VB_GENERAL, LOG_ERR, LOC +
148  QString("Joystick disabled - Failed to open %1") .arg(config_file));
149  return false;
150  }
151 
152  QTextStream istream(fp);
153  for (int line = 1; ! istream.atEnd(); line++)
154  {
155  QString rawline = istream.readLine();
156  QString simple_line = rawline.simplified();
157  if (simple_line.isEmpty() || simple_line.startsWith('#'))
158  continue;
159 
160  QStringList tokens = simple_line.split(" ");
161  if (tokens.count() < 1)
162  continue;
163 
164  QString firstTok = tokens[0].toLower();
165 
166  if (firstTok.startsWith("devicename") && tokens.count() == 2)
167  {
168  m_devicename = tokens[1];
169  }
170  else if (firstTok.startsWith("button") && tokens.count() == 3)
171  {
172  m_map.AddButton(tokens[1].toInt(), tokens[2]);
173  }
174  else if (firstTok.startsWith("axis") && tokens.count() == 5)
175  {
176  m_map.AddAxis(tokens[1].toInt(), tokens[2].toInt(),
177  tokens[3].toInt(), tokens[4]);
178  }
179  else if (firstTok.startsWith("chord") && tokens.count() == 4)
180  {
181  m_map.AddButton(tokens[2].toInt(), tokens[3], tokens[1].toInt());
182  }
183  else
184  {
185  LOG(VB_GENERAL, LOG_WARNING, LOC +
186  QString("ReadConfig(%1) unrecognized or malformed line \"%2\" ")
187  .arg(line) .arg(rawline));
188  }
189  }
190 
191  fclose(fp);
192  return true;
193 }
194 
195 
201 {
202  RunProlog();
203 
204  fd_set readfds;
205  struct js_event js {};
206  struct timeval timeout {};
207 
208  while (!m_bStop)
209  {
210 
211  /*--------------------------------------------------------------------
212  ** Wait for activity from the joy stick (we wait a configurable
213  ** poll time)
214  **------------------------------------------------------------------*/
215  FD_ZERO(&readfds); // NOLINT(readability-isolate-declaration)
216  FD_SET(m_fd, &readfds);
217 
218  // the maximum time select() should wait
219  timeout.tv_sec = 0;
220  timeout.tv_usec = 100000;
221 
222  int rc = select(m_fd + 1, &readfds, nullptr, nullptr, &timeout);
223  if (rc == -1)
224  {
225  /*----------------------------------------------------------------
226  ** TODO: In theory, we could recover from file errors
227  ** (what happens when we unplug a joystick?)
228  **--------------------------------------------------------------*/
229  LOG(VB_GENERAL, LOG_ERR, "select: " + ENO);
230  return;
231  }
232 
233  if (rc == 1)
234  {
235  /*----------------------------------------------------------------
236  ** Read a joystick event
237  **--------------------------------------------------------------*/
238  rc = read(m_fd, &js, sizeof(js));
239  if (rc != sizeof(js))
240  {
241  LOG(VB_GENERAL, LOG_ERR, "error reading js:" + ENO);
242  return;
243  }
244 
245  /*----------------------------------------------------------------
246  ** Events sent with the JS_EVENT_INIT flag are always sent
247  ** right after you open the joy stick; they are useful
248  ** for learning the initial state of buttons and axes
249  **--------------------------------------------------------------*/
250  if (js.type & JS_EVENT_INIT)
251  {
252  if (js.type & JS_EVENT_BUTTON && js.number < m_buttonCount)
253  m_buttons[js.number] = js.value;
254 
255  if (js.type & JS_EVENT_AXIS && js.number < m_axesCount)
256  m_axes[js.number] = js.value;
257  }
258  else
259  {
260  /*------------------------------------------------------------
261  ** Record new button states and look for triggers
262  ** that would make us send a key.
263  ** Things are a little tricky here; for buttons, we
264  ** only act on button up events, not button down
265  ** (this lets us implement the chord function).
266  ** For axes, we only register a change if the
267  ** Joystick moves into the specified range
268  ** (that way, we only get one event per joystick
269  ** motion).
270  **----------------------------------------------------------*/
271  if (js.type & JS_EVENT_BUTTON && js.number < m_buttonCount)
272  {
273  if (js.value == 0 && m_buttons[js.number] == 1)
274  ButtonUp(js.number);
275 
276  m_buttons[js.number] = js.value;
277  }
278 
279  if (js.type & JS_EVENT_AXIS && js.number < m_axesCount)
280  {
281  AxisChange(js.number, js.value);
282  m_axes[js.number] = js.value;
283  }
284 
285  }
286 
287  }
288 
289  }
290 
291  RunEpilog();
292 }
293 
297 void JoystickMenuThread::EmitKey(const QString& code)
298 {
299  QKeySequence a(code);
300 
301  int keycode = 0;
302 
303  // Send a dummy keycode if we couldn't convert the key sequence.
304  // This is done so the main code can output a warning for bad
305  // mappings.
306  if (a.isEmpty())
307  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
308  keycode, true));
309 
310  for (int i = 0; i < a.count(); i++)
311  {
312  keycode = a[i];
313 
314  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
315  keycode, true));
316  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
317  keycode, false));
318  }
319 }
320 
321 
329 {
330  /*------------------------------------------------------------------------
331  ** Process chords first
332  **----------------------------------------------------------------------*/
333  JoystickMap::button_map_t::const_iterator bmap;
334  for (bmap = m_map.buttonMap().begin(); bmap != m_map.buttonMap().end();
335  ++bmap)
336  {
337  if (button == bmap->button && bmap->chord != -1
338  && m_buttons[bmap->chord] == 1)
339  {
340  EmitKey(bmap->keystring);
341  m_buttons[bmap->chord] = 0;
342  return;
343  }
344  }
345 
346  /*------------------------------------------------------------------------
347  ** Process everything else
348  **----------------------------------------------------------------------*/
349  for (bmap = m_map.buttonMap().begin(); bmap != m_map.buttonMap().end();
350  ++bmap)
351  {
352  if (button == bmap->button && bmap->chord == -1)
353  EmitKey(bmap->keystring);
354  }
355 }
356 
360 void JoystickMenuThread::AxisChange(int axis, int value)
361 {
362  JoystickMap::axis_map_t::const_iterator amap;
363  for (amap = m_map.axisMap().begin(); amap < m_map.axisMap().end(); ++amap)
364  {
365  if (axis == amap->axis)
366  {
367  /* If we're currently outside the range, and the move is
368  ** into the range, then we trigger */
369  if (m_axes[axis] < amap->from || m_axes[axis] > amap->to)
370  if (value >= amap->from && value <= amap->to)
371  EmitKey(amap->keystring);
372  }
373  }
374 }
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
unsigned char m_buttonCount
Track the status of the joystick buttons as we do depend slightly on state.
Definition: jsmenu.h:108
void EmitKey(const QString &code)
Send a keyevent to the main UI loop with the appropriate keycode.
Definition: jsmenu.cpp:297
unsigned char m_axesCount
Track the status of the joystick axes as we do depend slightly on state.
Definition: jsmenu.h:114
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:200
#define LOC
Definition: jsmenu.cpp:52
void AddAxis(int in_axis, int in_from, int in_to, QString in_keystr)
Definition: jsmenu.h:60
QObject * m_mainWindow
Definition: jsmenu.h:99
def read(device=None, features=[])
Definition: disc.py:35
#define close
Definition: compat.h:16
QString m_devicename
Definition: jsmenu.h:100
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
int FILE
Definition: mythburn.py:139
bool Init(QString &config_file)
Initialise the class variables with values from the config file.
Definition: jsmenu.cpp:72
const axis_map_t & axisMap() const
Definition: jsmenu.h:69
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void ButtonUp(int button)
Handle a button up event.
Definition: jsmenu.cpp:328
volatile bool m_bStop
Definition: jsmenu.h:119
void AddButton(int in_button, QString in_keystr, int in_chord=-1)
Definition: jsmenu.h:54
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
JoystickMap m_map
Definition: jsmenu.h:102
~JoystickMenuThread() override
Definition: jsmenu.cpp:54
bool ReadConfig(const QString &config_file)
Read from action to key mappings from flat file config file.
Definition: jsmenu.cpp:136
void AxisChange(int axis, int value)
Handle a registered change in a joystick axis.
Definition: jsmenu.cpp:360
const button_map_t & buttonMap() const
Definition: jsmenu.h:68