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