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