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