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 
15 #include <vector>
16 using namespace std;
17 
18 // Qt headers
19 #include <QCoreApplication>
20 #include <QEvent>
21 #include <QKeySequence>
22 #include <QStringList>
23 
24 // MythTV headers
25 #include "mythdb.h"
26 #include "mythsystemlegacy.h"
27 #include "lircevent.h"
28 #include "lirc_client.h"
29 
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <sys/select.h>
33 #include <sys/un.h>
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <netdb.h>
37 #include <unistd.h>
38 #include <fcntl.h>
39 #include <sys/wait.h>
40 #include "mythlogging.h"
41 
42 #define LOC QString("LIRC: ")
43 
44 class LIRCPriv
45 {
46  public:
47  LIRCPriv() = default;
49  {
50  if (m_lircState)
51  {
52  lirc_deinit(m_lircState);
53  m_lircState = nullptr;
54  }
55  if (m_lircConfig)
56  {
57  lirc_freeconfig(m_lircConfig);
58  m_lircConfig = nullptr;
59  }
60  }
61 
62  struct lirc_state *m_lircState {nullptr};
63  struct lirc_config *m_lircConfig {nullptr};
64 };
65 
66 QMutex LIRC::lirclib_lock;
67 
75 LIRC::LIRC(QObject *main_window,
76  QString lircd_device,
77  QString our_program,
78  QString config_file)
79  : MThread("LIRC"),
80  lock(QMutex::Recursive),
81  m_mainWindow(main_window),
82  lircdDevice(std::move(lircd_device)),
83  program(std::move(our_program)),
84  configFile(std::move(config_file)),
85  doRun(false),
86  buf_offset(0),
87  eofCount(0),
88  retryCount(0),
89  d(new LIRCPriv())
90 {
91  buf.resize(0);
92 }
93 
95 {
96  TeardownAll();
97 }
98 
100 {
101  TeardownAll();
102  QObject::deleteLater();
103 }
104 
106 {
107  QMutexLocker locker(&lock);
108  if (doRun)
109  {
110  doRun = false;
111  lock.unlock();
112  wait();
113  lock.lock();
114  }
115 
116  if (d)
117  {
118  delete d;
119  d = nullptr;
120  }
121 }
122 
123 static QByteArray get_ip(const QString &h)
124 {
125  QByteArray hba = h.toLatin1();
126  struct in_addr sin_addr {};
127  if (inet_aton(hba.constData(), &sin_addr))
128  return hba;
129 
130  struct addrinfo hints {};
131  hints.ai_family = AF_INET;
132  hints.ai_socktype = SOCK_STREAM;
133  hints.ai_protocol = IPPROTO_TCP;
134 
135  struct addrinfo *result = nullptr;
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  addr.sun_family = AF_UNIX;
197  strncpy(addr.sun_path, dev.constData(),107);
198 
199  int ret = ::connect(lircd_socket, (struct sockaddr*) &addr,
200  sizeof(addr));
201 
202  if (ret < 0)
203  {
204  LOG(vtype, LOG_ERR, LOC +
205  QString("Failed to connect to Unix socket '%1'")
206  .arg(lircdDevice) + ENO);
207 
208  close(lircd_socket);
209  return false;
210  }
211  }
212  else
213  {
214  lircd_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
215  if (lircd_socket < 0)
216  {
217  LOG(vtype, LOG_ERR, LOC + QString("Failed to open TCP socket '%1'")
218  .arg(lircdDevice) + ENO);
219 
220  return false;
221  }
222 
223  QString dev = lircdDevice;
224  uint port = 8765;
225  QStringList tmp = lircdDevice.split(':');
226  if (2 == tmp.size())
227  {
228  dev = tmp[0];
229  port = (tmp[1].toUInt()) ? tmp[1].toUInt() : port;
230  }
231  QByteArray device = get_ip(dev);
232  struct sockaddr_in addr {};
233  addr.sin_family = AF_INET;
234  addr.sin_port = htons(port);
235 
236  if (!inet_aton(device.constData(), &addr.sin_addr))
237  {
238  LOG(vtype, LOG_ERR, LOC + QString("Failed to parse IP address '%1'")
239  .arg(dev));
240 
241  close(lircd_socket);
242  return false;
243  }
244 
245  int ret = ::connect(lircd_socket, (struct sockaddr*) &addr,
246  sizeof(addr));
247  if (ret < 0)
248  {
249  LOG(vtype, LOG_ERR, LOC +
250  QString("Failed to connect TCP socket '%1'")
251  .arg(lircdDevice) + ENO);
252 
253  close(lircd_socket);
254  return false;
255  }
256 
257  // On Linux, select() can indicate data when there isn't
258  // any due to TCP checksum in-particular; to avoid getting
259  // stuck on a read() call add the O_NONBLOCK flag.
260  int flags = fcntl(lircd_socket, F_GETFD);
261  if (flags >= 0)
262  {
263  ret = fcntl(lircd_socket, F_SETFD, flags | O_NONBLOCK);
264  if (ret < 0)
265  {
266  LOG(VB_GENERAL, LOG_WARNING, LOC +
267  QString("Failed set flags for socket '%1'")
268  .arg(lircdDevice) + ENO);
269  }
270  }
271 
272  // Attempt to inline out-of-band messages and keep the connection open..
273  int i = 1;
274  ret = setsockopt(lircd_socket, SOL_SOCKET, SO_OOBINLINE, &i, sizeof(i));
275  if (ret < 0)
276  {
277  LOG(VB_GENERAL, LOG_WARNING, LOC +
278  QString("Failed setting OOBINLINE option for socket '%1'")
279  .arg(lircdDevice) + ENO);
280  }
281  i = 1;
282  ret = setsockopt(lircd_socket, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
283  if (ret < 0)
284  {
285  LOG(VB_GENERAL, LOG_WARNING, LOC +
286  QString("Failed setting KEEPALIVE option for socket '%1'")
287  .arg(lircdDevice) + ENO);
288  }
289  }
290 
291  d->m_lircState = lirc_init("/etc/lircrc", ".lircrc", "mythtv", nullptr, 0);
292  if (!d->m_lircState)
293  {
294  close(lircd_socket);
295  return false;
296  }
297  d->m_lircState->lirc_lircd = lircd_socket;
298 
299  // parse the config file
300  if (!d->m_lircConfig)
301  {
302  QMutexLocker static_lock(&lirclib_lock);
303  QByteArray cfg = configFile.toLocal8Bit();
304  if (lirc_readconfig(d->m_lircState, cfg.constData(), &d->m_lircConfig, nullptr))
305  {
306  LOG(vtype, LOG_ERR, LOC +
307  QString("Failed to read config file '%1'").arg(configFile));
308 
310  d->m_lircState = nullptr;
311  return false;
312  }
313  }
314 
315  LOG(VB_GENERAL, LOG_INFO, LOC +
316  QString("Successfully initialized '%1' using '%2' config")
317  .arg(lircdDevice).arg(configFile));
318 
319  return true;
320 }
321 
322 void LIRC::start(void)
323 {
324  QMutexLocker locker(&lock);
325 
326  if (!d->m_lircState)
327  {
328  LOG(VB_GENERAL, LOG_ERR, "start() called without lircd socket");
329  return;
330  }
331 
332  doRun = true;
333  MThread::start();
334 }
335 
336 bool LIRC::IsDoRunSet(void) const
337 {
338  QMutexLocker locker(&lock);
339  return doRun;
340 }
341 
342 void LIRC::Process(QByteArray &data)
343 {
344  QMutexLocker static_lock(&lirclib_lock);
345 
346  // lirc_code2char will make code point to a static datafer..
347  char *code = nullptr;
348  int ret = lirc_code2char(
349  d->m_lircState, d->m_lircConfig, data.data(), &code);
350 
351  while ((0 == ret) && code)
352  {
353  QString lirctext(code);
354  QString qtcode = code;
355  qtcode.replace("ctrl-", "ctrl+", Qt::CaseInsensitive);
356  qtcode.replace("alt-", "alt+", Qt::CaseInsensitive);
357  qtcode.replace("shift-", "shift+", Qt::CaseInsensitive);
358  qtcode.replace("meta-", "meta+", Qt::CaseInsensitive);
359  QKeySequence a(qtcode);
360 
361  // Send a dummy keycode if we couldn't convert the key sequence.
362  // This is done so the main code can output a warning for bad
363  // mappings.
364  if (a.isEmpty())
365  {
366  QCoreApplication::postEvent(
368  QEvent::KeyPress, 0,
369  (Qt::KeyboardModifiers)
371  QString(), lirctext));
372  }
373 
374  vector<LircKeycodeEvent*> keyReleases;
375 
376  for (int i = 0; i < a.count(); i++)
377  {
378  int keycode = a[i];
379  Qt::KeyboardModifiers mod = Qt::NoModifier;
380  mod |= (Qt::SHIFT & keycode) ? Qt::ShiftModifier : Qt::NoModifier;
381  mod |= (Qt::META & keycode) ? Qt::MetaModifier : Qt::NoModifier;
382  mod |= (Qt::CTRL & keycode) ? Qt::ControlModifier: Qt::NoModifier;
383  mod |= (Qt::ALT & keycode) ? Qt::AltModifier : Qt::NoModifier;
384 
385  keycode &= ~Qt::MODIFIER_MASK;
386 
387  QString text = "";
388  if (!mod)
389  text = QString(QChar(keycode));
390 
391  QCoreApplication::postEvent(
393  QEvent::KeyPress, keycode, mod, text, lirctext));
394 
395  keyReleases.push_back(
396  new LircKeycodeEvent(
397  QEvent::KeyRelease, keycode, mod, text, lirctext));
398  }
399 
400  for (int i = (int)keyReleases.size() - 1; i>=0; i--)
401  QCoreApplication::postEvent(m_mainWindow, keyReleases[i]);
402 
403  ret = lirc_code2char(
404  d->m_lircState, d->m_lircConfig, data.data(), &code);
405  }
406 }
407 
408 void LIRC::run(void)
409 {
410  RunProlog();
411 #if 0
412  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- start");
413 #endif
414  /* Process all events read */
415  while (IsDoRunSet())
416  {
417  if (eofCount && retryCount)
418  std::this_thread::sleep_for(std::chrono::milliseconds(100));
419 
420  if ((eofCount >= 10) || (!d->m_lircState))
421  {
422  QMutexLocker locker(&lock);
423  eofCount = 0;
424  if (++retryCount > 1000)
425  {
426  LOG(VB_GENERAL, LOG_ERR, LOC +
427  "Failed to reconnect, exiting LIRC thread.");
428  doRun = false;
429  continue;
430  }
431  LOG(VB_FILE, LOG_WARNING, LOC + "EOF -- reconnecting");
432 
434  d->m_lircState = nullptr;
435 
436  if (Init())
437  retryCount = 0;
438  else
439  // wait a while before we retry..
440  std::this_thread::sleep_for(std::chrono::seconds(2));
441 
442  continue;
443  }
444 
445  fd_set readfds;
446  FD_ZERO(&readfds);
447  FD_SET(d->m_lircState->lirc_lircd, &readfds);
448 
449  // the maximum time select() should wait
450  struct timeval timeout {1, 100 * 1000}; // 1 second, 100 ms
451 
452  int ret = select(d->m_lircState->lirc_lircd + 1, &readfds, nullptr, nullptr,
453  &timeout);
454 
455  if (ret < 0 && errno != EINTR)
456  {
457  LOG(VB_GENERAL, LOG_ERR, LOC + "select() failed" + ENO);
458  continue;
459  }
460 
461  // 0: Timer expired with no data, repeat select
462  // -1: Iinterrupted while waiting, repeat select
463  if (ret <= 0)
464  continue;
465 
466  QList<QByteArray> codes = GetCodes();
467  for (uint i = 0; i < (uint) codes.size(); i++)
468  Process(codes[i]);
469  }
470 #if 0
471  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- end");
472 #endif
473  RunEpilog();
474 }
475 
476 QList<QByteArray> LIRC::GetCodes(void)
477 {
478  QList<QByteArray> ret;
479  ssize_t len = -1;
480 
481  uint buf_size = buf.size() + 128;
482  buf.resize(buf_size);
483 
484  while (true)
485  {
486  len = read(d->m_lircState->lirc_lircd, buf.data() + buf_offset, 128);
487  if (len >= 0)
488  break;
489 
490  switch (errno)
491  {
492  case EINTR:
493  continue;
494 
495  case EAGAIN:
496  return ret;
497 
498  case ENOTCONN: // 107 (according to asm-generic/errno.h)
499  if (!eofCount)
500  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- EOF?");
501  eofCount++;
502  return ret;
503 
504  default:
505  LOG(VB_GENERAL, LOG_ERR, LOC + "Could not read socket" + ENO);
506  return ret;
507  }
508  }
509 
510  if (!len)
511  {
512  if (!eofCount)
513  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- eof?");
514  eofCount++;
515  return ret;
516  }
517 
518  eofCount = 0;
519  retryCount = 0;
520 
521  buf_offset += len;
522  buf.truncate(buf_offset);
523  ret = buf.split('\n');
524  if (buf.endsWith('\n'))
525  {
526  buf_offset = 0;
527  return ret;
528  }
529 
530  buf = ret.takeLast();
531  buf_offset = buf.size();
532  return ret;
533 }
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:408
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:336
static QByteArray get_ip(const QString &h)
Definition: lirc.cpp:123
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:1628
QString lircdDevice
device on which to receive lircd data
Definition: lirc.h:49
virtual void start(void)
Definition: lirc.cpp:322
uint retryCount
Definition: lirc.h:56
static guint32 * tmp
Definition: goom_core.c:35
#define LOC
Definition: lirc.cpp:42
def read(device=None, features=[])
Definition: disc.py:35
LIRCPriv * d
Definition: lirc.h:57
struct lirc_config * m_lircConfig
Definition: lirc.cpp:63
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:836
unsigned int uint
Definition: compat.h:140
#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:62
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void TeardownAll()
Definition: lirc.cpp:105
void lirc_freeconfig(struct lirc_config *config)
Definition: lirc_client.c:1398
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:48
QList< QByteArray > GetCodes(void)
Definition: lirc.cpp:476
QByteArray buf
Definition: lirc.h:54
void Process(QByteArray &data)
Definition: lirc.cpp:342
virtual ~LIRC()
Definition: lirc.cpp:94
LIRC(QObject *main_window, QString lircd_device, QString our_program, QString config_file)
Definition: lirc.cpp:75
virtual void deleteLater(void)
Definition: lirc.cpp:99