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