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