MythTV  master
lirc.cpp
Go to the documentation of this file.
1 
2 // Own header
3 #include "lirc.h"
4 
5 // C headers
6 #include <cstdio>
7 #include <cerrno>
8 #include <cstdlib>
9 
10 // C++ headers
11 #include <algorithm>
12 #include <chrono> // for milliseconds
13 #include <thread> // for sleep_for
14 #include <vector>
15 using namespace std;
16 
17 // Qt headers
18 #include <QCoreApplication>
19 #include <QEvent>
20 #include <QKeySequence>
21 #include <QStringList>
22 
23 // MythTV headers
24 #include "mythdb.h"
25 #include "mythsystemlegacy.h"
26 #include "lircevent.h"
27 #include "lirc_client.h"
28 
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/select.h>
32 #include <sys/un.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
35 #include <netdb.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <sys/wait.h>
39 #include "mythlogging.h"
40 
41 #define LOC QString("LIRC: ")
42 
43 class LIRCPriv
44 {
45  public:
46  LIRCPriv() : lircState(NULL), lircConfig(NULL) {}
48  {
49  if (lircState)
50  {
51  lirc_deinit(lircState);
52  lircState = NULL;
53  }
54  if (lircConfig)
55  {
56  lirc_freeconfig(lircConfig);
57  lircConfig = NULL;
58  }
59  }
60 
63 };
64 
65 QMutex LIRC::lirclib_lock;
66 
74 LIRC::LIRC(QObject *main_window,
75  const QString &lircd_device,
76  const QString &our_program,
77  const QString &config_file)
78  : MThread("LIRC"),
79  lock(QMutex::Recursive),
80  m_mainWindow(main_window),
81  lircdDevice(lircd_device),
82  program(our_program),
83  configFile(config_file),
84  doRun(false),
85  buf_offset(0),
86  eofCount(0),
87  retryCount(0),
88  d(new LIRCPriv())
89 {
90  lircdDevice.detach();
91  program.detach();
92  configFile.detach();
93  buf.resize(0);
94 }
95 
97 {
98  TeardownAll();
99 }
100 
102 {
103  TeardownAll();
104  QObject::deleteLater();
105 }
106 
108 {
109  QMutexLocker locker(&lock);
110  if (doRun)
111  {
112  doRun = false;
113  lock.unlock();
114  wait();
115  lock.lock();
116  }
117 
118  if (d)
119  {
120  delete d;
121  d = NULL;
122  }
123 }
124 
125 static QByteArray get_ip(const QString &h)
126 {
127  QByteArray hba = h.toLatin1();
128  struct in_addr sin_addr;
129  if (inet_aton(hba.constData(), &sin_addr))
130  return hba;
131 
132  struct addrinfo hints;
133  memset(&hints, 0, sizeof(hints));
134  hints.ai_family = AF_INET;
135  hints.ai_socktype = SOCK_STREAM;
136  hints.ai_protocol = IPPROTO_TCP;
137 
138  struct addrinfo *result;
139  int err = getaddrinfo(hba.constData(), NULL, &hints, &result);
140  if (err)
141  {
142  LOG(VB_GENERAL, LOG_DEBUG,
143  QString("get_ip: %1").arg(gai_strerror(err)));
144  return QString("").toLatin1();
145  }
146 
147  int addrlen = result->ai_addrlen;
148  if (!addrlen)
149  {
150  freeaddrinfo(result);
151  return QString("").toLatin1();
152  }
153 
154  if (result->ai_addr->sa_family != AF_INET)
155  {
156  freeaddrinfo(result);
157  return QString("").toLatin1();
158  }
159 
160  sin_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr;
161  hba = QByteArray(inet_ntoa(sin_addr));
162  freeaddrinfo(result);
163 
164  return hba;
165 }
166 
167 bool LIRC::Init(void)
168 {
169  QMutexLocker locker(&lock);
170  if (d->lircState)
171  return true;
172 
173  uint64_t vtype = (0 == retryCount) ? VB_GENERAL : VB_FILE;
174 
175  int lircd_socket = -1;
176  if (lircdDevice.startsWith('/'))
177  {
178  // Connect the unix socket
179  QByteArray dev = lircdDevice.toLocal8Bit();
180  if (dev.size() > 107)
181  {
182  LOG(vtype, LOG_ERR, LOC +
183  QString("lircdDevice '%1'").arg(lircdDevice) +
184  " is too long for the 'unix' socket API");
185 
186  return false;
187  }
188 
189  lircd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
190  if (lircd_socket < 0)
191  {
192  LOG(vtype, LOG_ERR, LOC + QString("Failed to open Unix socket '%1'")
193  .arg(lircdDevice) + ENO);
194 
195  return false;
196  }
197 
198  struct sockaddr_un addr;
199  memset(&addr, 0, sizeof(sockaddr_un));
200  addr.sun_family = AF_UNIX;
201  strncpy(addr.sun_path, dev.constData(),107);
202 
203  int ret = ::connect(lircd_socket, (struct sockaddr*) &addr,
204  sizeof(addr));
205 
206  if (ret < 0)
207  {
208  LOG(vtype, LOG_ERR, LOC +
209  QString("Failed to connect to Unix socket '%1'")
210  .arg(lircdDevice) + ENO);
211 
212  close(lircd_socket);
213  return false;
214  }
215  }
216  else
217  {
218  lircd_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
219  if (lircd_socket < 0)
220  {
221  LOG(vtype, LOG_ERR, LOC + QString("Failed to open TCP socket '%1'")
222  .arg(lircdDevice) + ENO);
223 
224  return false;
225  }
226 
227  QString dev = lircdDevice;
228  uint port = 8765;
229  QStringList tmp = lircdDevice.split(':');
230  if (2 == tmp.size())
231  {
232  dev = tmp[0];
233  port = (tmp[1].toUInt()) ? tmp[1].toUInt() : port;
234  }
235  QByteArray device = get_ip(dev);
236  struct sockaddr_in addr;
237  memset(&addr, 0, sizeof(sockaddr_in));
238  addr.sin_family = AF_INET;
239  addr.sin_port = htons(port);
240 
241  if (!inet_aton(device.constData(), &addr.sin_addr))
242  {
243  LOG(vtype, LOG_ERR, LOC + QString("Failed to parse IP address '%1'")
244  .arg(dev));
245 
246  close(lircd_socket);
247  return false;
248  }
249 
250  int ret = ::connect(lircd_socket, (struct sockaddr*) &addr,
251  sizeof(addr));
252  if (ret < 0)
253  {
254  LOG(vtype, LOG_ERR, LOC +
255  QString("Failed to connect TCP socket '%1'")
256  .arg(lircdDevice) + ENO);
257 
258  close(lircd_socket);
259  return false;
260  }
261 
262  // On Linux, select() can indicate data when there isn't
263  // any due to TCP checksum in-particular; to avoid getting
264  // stuck on a read() call add the O_NONBLOCK flag.
265  int flags = fcntl(lircd_socket, F_GETFD);
266  if (flags >= 0)
267  {
268  ret = fcntl(lircd_socket, F_SETFD, flags | O_NONBLOCK);
269  if (ret < 0)
270  {
271  LOG(VB_GENERAL, LOG_WARNING, LOC +
272  QString("Failed set flags for socket '%1'")
273  .arg(lircdDevice) + ENO);
274  }
275  }
276 
277  // Attempt to inline out-of-band messages and keep the connection open..
278  int i = 1;
279  ret = setsockopt(lircd_socket, SOL_SOCKET, SO_OOBINLINE, &i, sizeof(i));
280  if (ret < 0)
281  {
282  LOG(VB_GENERAL, LOG_WARNING, LOC +
283  QString("Failed setting OOBINLINE option for socket '%1'")
284  .arg(lircdDevice) + ENO);
285  }
286  i = 1;
287  ret = setsockopt(lircd_socket, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
288  if (ret < 0)
289  {
290  LOG(VB_GENERAL, LOG_WARNING, LOC +
291  QString("Failed setting KEEPALIVE option for socket '%1'")
292  .arg(lircdDevice) + ENO);
293  }
294  }
295 
296  d->lircState = lirc_init("/etc/lircrc", ".lircrc", "mythtv", NULL, 0);
297  if (!d->lircState)
298  {
299  close(lircd_socket);
300  return false;
301  }
302  d->lircState->lirc_lircd = lircd_socket;
303 
304  // parse the config file
305  if (!d->lircConfig)
306  {
307  QMutexLocker static_lock(&lirclib_lock);
308  QByteArray cfg = configFile.toLocal8Bit();
309  if (lirc_readconfig(d->lircState, cfg.constData(), &d->lircConfig, NULL))
310  {
311  LOG(vtype, LOG_ERR, LOC +
312  QString("Failed to read config file '%1'").arg(configFile));
313 
315  d->lircState = NULL;
316  return false;
317  }
318  }
319 
320  LOG(VB_GENERAL, LOG_INFO, LOC +
321  QString("Successfully initialized '%1' using '%2' config")
322  .arg(lircdDevice).arg(configFile));
323 
324  return true;
325 }
326 
327 void LIRC::start(void)
328 {
329  QMutexLocker locker(&lock);
330 
331  if (!d->lircState)
332  {
333  LOG(VB_GENERAL, LOG_ERR, "start() called without lircd socket");
334  return;
335  }
336 
337  doRun = true;
338  MThread::start();
339 }
340 
341 bool LIRC::IsDoRunSet(void) const
342 {
343  QMutexLocker locker(&lock);
344  return doRun;
345 }
346 
347 void LIRC::Process(const QByteArray &data)
348 {
349  QMutexLocker static_lock(&lirclib_lock);
350 
351  // lirc_code2char will make code point to a static datafer..
352  char *code = NULL;
353  int ret = lirc_code2char(
354  d->lircState, d->lircConfig, const_cast<char*>(data.constData()), &code);
355 
356  while ((0 == ret) && code)
357  {
358  QString lirctext(code);
359  QString qtcode = code;
360  qtcode.replace("ctrl-", "ctrl+", Qt::CaseInsensitive);
361  qtcode.replace("alt-", "alt+", Qt::CaseInsensitive);
362  qtcode.replace("shift-", "shift+", Qt::CaseInsensitive);
363  qtcode.replace("meta-", "meta+", Qt::CaseInsensitive);
364  QKeySequence a(qtcode);
365 
366  // Send a dummy keycode if we couldn't convert the key sequence.
367  // This is done so the main code can output a warning for bad
368  // mappings.
369  if (a.isEmpty())
370  {
371  QCoreApplication::postEvent(
373  QEvent::KeyPress, 0,
374  (Qt::KeyboardModifiers)
376  QString(), lirctext));
377  }
378 
379  vector<LircKeycodeEvent*> keyReleases;
380 
381 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
382  unsigned int i;
383 #else
384  int i;
385 #endif
386  for (i = 0; i < a.count(); i++)
387  {
388  int keycode = a[i];
389  Qt::KeyboardModifiers mod = Qt::NoModifier;
390  mod |= (Qt::SHIFT & keycode) ? Qt::ShiftModifier : Qt::NoModifier;
391  mod |= (Qt::META & keycode) ? Qt::MetaModifier : Qt::NoModifier;
392  mod |= (Qt::CTRL & keycode) ? Qt::ControlModifier: Qt::NoModifier;
393  mod |= (Qt::ALT & keycode) ? Qt::AltModifier : Qt::NoModifier;
394 
395  keycode &= ~Qt::MODIFIER_MASK;
396 
397  QString text = "";
398  if (!mod)
399  text = QString(QChar(keycode));
400 
401  QCoreApplication::postEvent(
403  QEvent::KeyPress, keycode, mod, text, lirctext));
404 
405  keyReleases.push_back(
406  new LircKeycodeEvent(
407  QEvent::KeyRelease, keycode, mod, text, lirctext));
408  }
409 
410  for (int i = (int)keyReleases.size() - 1; i>=0; i--)
411  QCoreApplication::postEvent(m_mainWindow, keyReleases[i]);
412 
413  ret = lirc_code2char(
414  d->lircState, d->lircConfig, const_cast<char*>(data.constData()), &code);
415  }
416 }
417 
418 void LIRC::run(void)
419 {
420  RunProlog();
421 #if 0
422  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- start");
423 #endif
424  /* Process all events read */
425  while (IsDoRunSet())
426  {
427  if (eofCount && retryCount)
428  std::this_thread::sleep_for(std::chrono::milliseconds(100));
429 
430  if ((eofCount >= 10) || (!d->lircState))
431  {
432  QMutexLocker locker(&lock);
433  eofCount = 0;
434  if (++retryCount > 1000)
435  {
436  LOG(VB_GENERAL, LOG_ERR, LOC +
437  "Failed to reconnect, exiting LIRC thread.");
438  doRun = false;
439  continue;
440  }
441  LOG(VB_FILE, LOG_WARNING, LOC + "EOF -- reconnecting");
442 
444  d->lircState = NULL;
445 
446  if (Init())
447  retryCount = 0;
448  else
449  // wait a while before we retry..
450  std::this_thread::sleep_for(std::chrono::seconds(2));
451 
452  continue;
453  }
454 
455  fd_set readfds;
456  FD_ZERO(&readfds);
457  FD_SET(d->lircState->lirc_lircd, &readfds);
458 
459  // the maximum time select() should wait
460  struct timeval timeout;
461  timeout.tv_sec = 1; // 1 second
462  timeout.tv_usec = 100 * 1000; // 100 ms
463 
464  int ret = select(d->lircState->lirc_lircd + 1, &readfds, NULL, NULL,
465  &timeout);
466 
467  if (ret < 0 && errno != EINTR)
468  {
469  LOG(VB_GENERAL, LOG_ERR, LOC + "select() failed" + ENO);
470  continue;
471  }
472 
473  // 0: Timer expired with no data, repeat select
474  // -1: Iinterrupted while waiting, repeat select
475  if (ret <= 0)
476  continue;
477 
478  QList<QByteArray> codes = GetCodes();
479  for (uint i = 0; i < (uint) codes.size(); i++)
480  Process(codes[i]);
481  }
482 #if 0
483  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- end");
484 #endif
485  RunEpilog();
486 }
487 
488 QList<QByteArray> LIRC::GetCodes(void)
489 {
490  QList<QByteArray> ret;
491  ssize_t len = -1;
492 
493  uint buf_size = buf.size() + 128;
494  buf.resize(buf_size);
495 
496  while (true)
497  {
498  len = read(d->lircState->lirc_lircd, buf.data() + buf_offset, 128);
499  if (len >= 0)
500  break;
501 
502  switch (errno)
503  {
504  case EINTR:
505  continue;
506 
507  case EAGAIN:
508  return ret;
509 
510  case ENOTCONN: // 107 (according to asm-generic/errno.h)
511  if (!eofCount)
512  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- EOF?");
513  eofCount++;
514  return ret;
515 
516  default:
517  LOG(VB_GENERAL, LOG_ERR, LOC + "Could not read socket" + ENO);
518  return ret;
519  }
520  }
521 
522  if (!len)
523  {
524  if (!eofCount)
525  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- eof?");
526  eofCount++;
527  return ret;
528  }
529 
530  eofCount = 0;
531  retryCount = 0;
532 
533  buf_offset += len;
534  buf.truncate(buf_offset);
535  ret = buf.split('\n');
536  if (buf.endsWith('\n'))
537  {
538  buf_offset = 0;
539  return ret;
540  }
541 
542  buf = ret.takeLast();
543  buf_offset = buf.size();
544  return ret;
545 }
void RunEpilog(void)
Cleans up a thread&#39;s resources, call this if you reimplement run().
Definition: mthread.cpp:216
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:291
int lirc_deinit(struct lirc_state *state)
Definition: lirc_client.c:208
static const int kLIRCInvalidKeyCombo
Definition: lircevent.h:31
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
struct lirc_state * lirc_init(const char *lircrc_root_file, const char *lircrc_user_file, const char *prog, const char *lircd, int verbose)
Definition: lirc_client.c:134
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:83
__int64 ssize_t
Definition: compat.h:72
struct lirc_state * lircState
Definition: lirc.cpp:61
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:308
bool Init(void)
Definition: lirc.cpp:167
bool IsDoRunSet(void) const
Definition: lirc.cpp:341
static QByteArray get_ip(const QString &h)
Definition: lirc.cpp:125
uint buf_offset
Definition: lirc.h:53
int lirc_code2char(const struct lirc_state *state, struct lirc_config *config, char *code, char **string)
Definition: lirc_client.c:1676
virtual void run(void)
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead...
Definition: lirc.cpp:418
unsigned int uint
Definition: compat.h:136
QString lircdDevice
device on which to receive lircd data
Definition: lirc.h:49
virtual void start(void)
Definition: lirc.cpp:327
uint retryCount
Definition: lirc.h:56
__u8 data[42]
Definition: videodev2.h:1039
voidpf void * buf
Definition: ioapi.h:136
def read(device=None, features=[])
Definition: disc.py:35
LIRCPriv * d
Definition: lirc.h:57
QObject * m_mainWindow
window to send key events to
Definition: lirc.h:48
int errno
static const uint16_t * d
LIRCPriv()
Definition: lirc.cpp:46
static QMutex lirclib_lock
Definition: lirc.h:47
uint eofCount
Definition: lirc.h:55
bool doRun
Definition: lirc.h:52
QMutex lock
Definition: lirc.h:46
__u32 flags
Definition: videodev2.h:1053
int lirc_readconfig(const struct lirc_state *state, const char *file, struct lirc_config **config, int(check)(char *s))
Definition: lirc_client.c:858
QString configFile
file containing LIRC->key mappings
Definition: lirc.h:51
void TeardownAll()
Definition: lirc.cpp:107
void lirc_freeconfig(struct lirc_config *config)
Definition: lirc_client.c:1435
int lirc_lircd
Definition: lirc_client.h:39
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:203
~LIRCPriv()
Definition: lirc.cpp:47
QList< QByteArray > GetCodes(void)
Definition: lirc.cpp:488
void Process(const QByteArray &data)
Definition: lirc.cpp:347
static const QString LOC
struct lirc_config * lircConfig
Definition: lirc.cpp:62
virtual ~LIRC()
Definition: lirc.cpp:96
LIRC(QObject *main_window, const QString &lircd_device, const QString &our_program, const QString &config_file)
Definition: lirc.cpp:74
QString program
program to extract from config file
Definition: lirc.h:50
virtual void deleteLater(void)
Definition: lirc.cpp:101