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