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