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  m_devicename = tokens[1];
168  else if (firstTok.startsWith("button") && tokens.count() == 3)
169  m_map.AddButton(tokens[1].toInt(), tokens[2]);
170  else if (firstTok.startsWith("axis") && tokens.count() == 5)
171  m_map.AddAxis(tokens[1].toInt(), tokens[2].toInt(),
172  tokens[3].toInt(), tokens[4]);
173  else if (firstTok.startsWith("chord") && tokens.count() == 4)
174  m_map.AddButton(tokens[2].toInt(), tokens[3], tokens[1].toInt());
175  else
176  LOG(VB_GENERAL, LOG_WARNING, LOC +
177  QString("ReadConfig(%1) unrecognized or malformed line \"%2\" ")
178  .arg(line) .arg(rawline));
179  }
180 
181  fclose(fp);
182  return true;
183 }
184 
185 
191 {
192  RunProlog();
193 
194  fd_set readfds;
195  struct js_event js {};
196  struct timeval timeout {};
197 
198  while (!m_bStop)
199  {
200 
201  /*--------------------------------------------------------------------
202  ** Wait for activity from the joy stick (we wait a configurable
203  ** poll time)
204  **------------------------------------------------------------------*/
205  FD_ZERO(&readfds);
206  FD_SET(m_fd, &readfds);
207 
208  // the maximum time select() should wait
209  timeout.tv_sec = 0;
210  timeout.tv_usec = 100000;
211 
212  int rc = select(m_fd + 1, &readfds, nullptr, nullptr, &timeout);
213  if (rc == -1)
214  {
215  /*----------------------------------------------------------------
216  ** TODO: In theory, we could recover from file errors
217  ** (what happens when we unplug a joystick?)
218  **--------------------------------------------------------------*/
219  LOG(VB_GENERAL, LOG_ERR, "select: " + ENO);
220  return;
221  }
222 
223  if (rc == 1)
224  {
225  /*----------------------------------------------------------------
226  ** Read a joystick event
227  **--------------------------------------------------------------*/
228  rc = read(m_fd, &js, sizeof(js));
229  if (rc != sizeof(js))
230  {
231  LOG(VB_GENERAL, LOG_ERR, "error reading js:" + ENO);
232  return;
233  }
234 
235  /*----------------------------------------------------------------
236  ** Events sent with the JS_EVENT_INIT flag are always sent
237  ** right after you open the joy stick; they are useful
238  ** for learning the initial state of buttons and axes
239  **--------------------------------------------------------------*/
240  if (js.type & JS_EVENT_INIT)
241  {
242  if (js.type & JS_EVENT_BUTTON && js.number < m_buttonCount)
243  m_buttons[js.number] = js.value;
244 
245  if (js.type & JS_EVENT_AXIS && js.number < m_axesCount)
246  m_axes[js.number] = js.value;
247  }
248  else
249  {
250  /*------------------------------------------------------------
251  ** Record new button states and look for triggers
252  ** that would make us send a key.
253  ** Things are a little tricky here; for buttons, we
254  ** only act on button up events, not button down
255  ** (this lets us implement the chord function).
256  ** For axes, we only register a change if the
257  ** Joystick moves into the specified range
258  ** (that way, we only get one event per joystick
259  ** motion).
260  **----------------------------------------------------------*/
261  if (js.type & JS_EVENT_BUTTON && js.number < m_buttonCount)
262  {
263  if (js.value == 0 && m_buttons[js.number] == 1)
264  ButtonUp(js.number);
265 
266  m_buttons[js.number] = js.value;
267  }
268 
269  if (js.type & JS_EVENT_AXIS && js.number < m_axesCount)
270  {
271  AxisChange(js.number, js.value);
272  m_axes[js.number] = js.value;
273  }
274 
275  }
276 
277  }
278 
279  }
280 
281  RunEpilog();
282 }
283 
287 void JoystickMenuThread::EmitKey(const QString& code)
288 {
289  QKeySequence a(code);
290 
291  int keycode = 0;
292 
293  // Send a dummy keycode if we couldn't convert the key sequence.
294  // This is done so the main code can output a warning for bad
295  // mappings.
296  if (a.isEmpty())
297  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
298  keycode, true));
299 
300  for (int i = 0; i < a.count(); i++)
301  {
302  keycode = a[i];
303 
304  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
305  keycode, true));
306  QCoreApplication::postEvent(m_mainWindow, new JoystickKeycodeEvent(code,
307  keycode, false));
308  }
309 }
310 
311 
319 {
320  /*------------------------------------------------------------------------
321  ** Process chords first
322  **----------------------------------------------------------------------*/
323  JoystickMap::button_map_t::const_iterator bmap;
324  for (bmap = m_map.buttonMap().begin(); bmap != m_map.buttonMap().end();
325  ++bmap)
326  {
327  if (button == bmap->button && bmap->chord != -1
328  && m_buttons[bmap->chord] == 1)
329  {
330  EmitKey(bmap->keystring);
331  m_buttons[bmap->chord] = 0;
332  return;
333  }
334  }
335 
336  /*------------------------------------------------------------------------
337  ** Process everything else
338  **----------------------------------------------------------------------*/
339  for (bmap = m_map.buttonMap().begin(); bmap != m_map.buttonMap().end();
340  ++bmap)
341  {
342  if (button == bmap->button && bmap->chord == -1)
343  EmitKey(bmap->keystring);
344  }
345 }
346 
350 void JoystickMenuThread::AxisChange(int axis, int value)
351 {
352  JoystickMap::axis_map_t::const_iterator amap;
353  for (amap = m_map.axisMap().begin(); amap < m_map.axisMap().end(); ++amap)
354  {
355  if (axis == amap->axis)
356  {
357  /* If we're currently outside the range, and the move is
358  ** into the range, then we trigger */
359  if (m_axes[axis] < amap->from || m_axes[axis] > amap->to)
360  if (value >= amap->from && value <= amap->to)
361  EmitKey(amap->keystring);
362  }
363  }
364 }
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:287
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:190
#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:318
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:136
void AxisChange(int axis, int value)
Handle a registered change in a joystick axis.
Definition: jsmenu.cpp:350
const button_map_t & buttonMap() const
Definition: jsmenu.h:67