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