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  struct sockaddr_un addr {};
170  addr.sun_family = AF_UNIX;
171  static constexpr int max_copy = sizeof(addr.sun_path) - 1;
172  QByteArray dev = m_lircdDevice.toLocal8Bit();
173  if (dev.size() > max_copy)
174  {
175  LOG(vtype, LOG_ERR, LOC +
176  QString("m_lircdDevice '%1'").arg(m_lircdDevice) +
177  " is too long for the 'unix' socket API");
178 
179  return false;
180  }
181 
182  lircd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
183  if (lircd_socket < 0)
184  {
185  LOG(vtype, LOG_ERR, LOC + QString("Failed to open Unix socket '%1'")
186  .arg(m_lircdDevice) + ENO);
187 
188  return false;
189  }
190 
191  strncpy(addr.sun_path, dev.constData(), max_copy);
192 
193  int ret = ::connect(lircd_socket, (struct sockaddr*) &addr,
194  sizeof(addr));
195 
196  if (ret < 0)
197  {
198  LOG(vtype, LOG_ERR, LOC +
199  QString("Failed to connect to Unix socket '%1'")
200  .arg(m_lircdDevice) + ENO);
201 
202  close(lircd_socket);
203  return false;
204  }
205  }
206  else
207  {
208  lircd_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
209  if (lircd_socket < 0)
210  {
211  LOG(vtype, LOG_ERR, LOC + QString("Failed to open TCP socket '%1'")
212  .arg(m_lircdDevice) + ENO);
213 
214  return false;
215  }
216 
217  QString dev = m_lircdDevice;
218  uint port = 8765;
219  QStringList tmp = m_lircdDevice.split(':');
220  if (2 == tmp.size())
221  {
222  dev = tmp[0];
223  port = (tmp[1].toUInt()) ? tmp[1].toUInt() : port;
224  }
225  QByteArray device = get_ip(dev);
226  struct sockaddr_in addr {};
227  addr.sin_family = AF_INET;
228  addr.sin_port = htons(port);
229 
230  if (!inet_aton(device.constData(), &addr.sin_addr))
231  {
232  LOG(vtype, LOG_ERR, LOC + QString("Failed to parse IP address '%1'")
233  .arg(dev));
234 
235  close(lircd_socket);
236  return false;
237  }
238 
239  int ret = ::connect(lircd_socket, (struct sockaddr*) &addr,
240  sizeof(addr));
241  if (ret < 0)
242  {
243  LOG(vtype, LOG_ERR, LOC +
244  QString("Failed to connect TCP socket '%1'")
245  .arg(m_lircdDevice) + ENO);
246 
247  close(lircd_socket);
248  return false;
249  }
250 
251  // On Linux, select() can indicate data when there isn't
252  // any due to TCP checksum in-particular; to avoid getting
253  // stuck on a read() call add the O_NONBLOCK flag.
254  int flags = fcntl(lircd_socket, F_GETFD);
255  if (flags >= 0)
256  {
257  ret = fcntl(lircd_socket, F_SETFD, flags | O_NONBLOCK);
258  if (ret < 0)
259  {
260  LOG(VB_GENERAL, LOG_WARNING, LOC +
261  QString("Failed set flags for socket '%1'")
262  .arg(m_lircdDevice) + ENO);
263  }
264  }
265 
266  // Attempt to inline out-of-band messages and keep the connection open..
267  int i = 1;
268  ret = setsockopt(lircd_socket, SOL_SOCKET, SO_OOBINLINE, &i, sizeof(i));
269  if (ret < 0)
270  {
271  LOG(VB_GENERAL, LOG_WARNING, LOC +
272  QString("Failed setting OOBINLINE option for socket '%1'")
273  .arg(m_lircdDevice) + ENO);
274  }
275  i = 1;
276  ret = setsockopt(lircd_socket, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
277  if (ret < 0)
278  {
279  LOG(VB_GENERAL, LOG_WARNING, LOC +
280  QString("Failed setting KEEPALIVE option for socket '%1'")
281  .arg(m_lircdDevice) + ENO);
282  }
283  }
284 
285  d->m_lircState = lirc_init("/etc/lircrc", ".lircrc", "mythtv", nullptr, 0);
286  if (!d->m_lircState)
287  {
288  close(lircd_socket);
289  return false;
290  }
291  d->m_lircState->lirc_lircd = lircd_socket;
292 
293  // parse the config file
294  if (!d->m_lircConfig)
295  {
296  QMutexLocker static_lock(&s_lirclibLock);
297  QByteArray cfg = m_configFile.toLocal8Bit();
298  if (lirc_readconfig(d->m_lircState, cfg.constData(), &d->m_lircConfig, nullptr))
299  {
300  LOG(vtype, LOG_ERR, LOC +
301  QString("Failed to read config file '%1'").arg(m_configFile));
302 
304  d->m_lircState = nullptr;
305  return false;
306  }
307  }
308 
309  LOG(VB_GENERAL, LOG_INFO, LOC +
310  QString("Successfully initialized '%1' using '%2' config")
311  .arg(m_lircdDevice, m_configFile));
312 
313  return true;
314 }
315 
316 void LIRC::start(void)
317 {
318  QMutexLocker locker(&m_lock);
319 
320  if (!d->m_lircState)
321  {
322  LOG(VB_GENERAL, LOG_ERR, "start() called without lircd socket");
323  return;
324  }
325 
326  m_doRun = true;
327  MThread::start();
328 }
329 
330 bool LIRC::IsDoRunSet(void) const
331 {
332  QMutexLocker locker(&m_lock);
333  return m_doRun;
334 }
335 
336 void LIRC::Process(const QByteArray &data)
337 {
338  QMutexLocker static_lock(&s_lirclibLock);
339 
340  // lirc_code2char will make code point to a static datafer..
341  char *code = nullptr;
342  int ret = lirc_code2char(
343  d->m_lircState, d->m_lircConfig, data.data(), &code);
344 
345  while ((0 == ret) && code)
346  {
347  QString lirctext(code);
348  QString qtcode = code;
349  qtcode.replace("ctrl-", "ctrl+", Qt::CaseInsensitive);
350  qtcode.replace("alt-", "alt+", Qt::CaseInsensitive);
351  qtcode.replace("shift-", "shift+", Qt::CaseInsensitive);
352  qtcode.replace("meta-", "meta+", Qt::CaseInsensitive);
353  QKeySequence a(qtcode);
354 
355  // Send a dummy keycode if we couldn't convert the key sequence.
356  // This is done so the main code can output a warning for bad
357  // mappings.
358  if (a.isEmpty())
359  {
360  QCoreApplication::postEvent(
362  QEvent::KeyPress, 0,
363  (Qt::KeyboardModifiers)
365  QString(), lirctext));
366  }
367 
368  std::vector<LircKeycodeEvent*> keyReleases;
369 
370  for (int i = 0; i < a.count(); i++)
371  {
372 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
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 #else
382  int keycode = a[i].key();
383  Qt::KeyboardModifiers mod = a[i].keyboardModifiers();
384 #endif
385 
386  QString text = "";
387  if (!mod)
388  text = QString(QChar(keycode));
389 
390  QCoreApplication::postEvent(
392  QEvent::KeyPress, keycode, mod, text, lirctext));
393 
394  keyReleases.push_back(
395  new LircKeycodeEvent(
396  QEvent::KeyRelease, keycode, mod, text, lirctext));
397  }
398 
399  for (int i = (int)keyReleases.size() - 1; i>=0; i--)
400  QCoreApplication::postEvent(m_mainWindow, keyReleases[i]);
401 
402  ret = lirc_code2char(
403  d->m_lircState, d->m_lircConfig, data.data(), &code);
404  }
405 }
406 
407 void LIRC::run(void)
408 {
409  RunProlog();
410 #if 0
411  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- start");
412 #endif
413  /* Process all events read */
414  while (IsDoRunSet())
415  {
416  if (m_eofCount && m_retryCount)
417  std::this_thread::sleep_for(100ms);
418 
419  if ((m_eofCount >= 10) || (!d->m_lircState))
420  {
421  QMutexLocker locker(&m_lock);
422  m_eofCount = 0;
423  if (++m_retryCount > 1000)
424  {
425  LOG(VB_GENERAL, LOG_ERR, LOC +
426  "Failed to reconnect, exiting LIRC thread.");
427  m_doRun = false;
428  continue;
429  }
430  LOG(VB_FILE, LOG_WARNING, LOC + "EOF -- reconnecting");
431 
433  d->m_lircState = nullptr;
434 
435  if (Init())
436  m_retryCount = 0;
437  else
438  // wait a while before we retry..
439  std::this_thread::sleep_for(2s);
440 
441  continue;
442  }
443 
444  fd_set readfds;
445  FD_ZERO(&readfds); // NOLINT(readability-isolate-declaration)
446  FD_SET(d->m_lircState->lirc_lircd, &readfds);
447 
448  // the maximum time select() should wait
449  struct timeval timeout {1, 100 * 1000}; // 1 second, 100 ms
450 
451  int ret = select(d->m_lircState->lirc_lircd + 1, &readfds, nullptr, nullptr,
452  &timeout);
453 
454  if (ret < 0 && errno != EINTR)
455  {
456  LOG(VB_GENERAL, LOG_ERR, LOC + "select() failed" + ENO);
457  continue;
458  }
459 
460  // 0: Timer expired with no data, repeat select
461  // -1: Iinterrupted while waiting, repeat select
462  if (ret <= 0)
463  continue;
464 
465  QList<QByteArray> codes = GetCodes();
466  for (const auto & code : qAsConst(codes))
467  Process(code);
468  }
469 #if 0
470  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- end");
471 #endif
472  RunEpilog();
473 }
474 
475 QList<QByteArray> LIRC::GetCodes(void)
476 {
477  QList<QByteArray> ret;
478  ssize_t len = -1;
479 
480  uint buf_size = m_buf.size() + 128;
481  m_buf.resize(buf_size);
482 
483  while (true)
484  {
485  len = read(d->m_lircState->lirc_lircd, m_buf.data() + m_bufOffset, 128);
486  if (len >= 0)
487  break;
488 
489  switch (errno)
490  {
491  case EINTR:
492  continue;
493 
494  case EAGAIN:
495  return ret;
496 
497  case ENOTCONN: // 107 (according to asm-generic/errno.h)
498  if (!m_eofCount)
499  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- EOF?");
500  m_eofCount++;
501  return ret;
502 
503  default:
504  LOG(VB_GENERAL, LOG_ERR, LOC + "Could not read socket" + ENO);
505  return ret;
506  }
507  }
508 
509  if (!len)
510  {
511  if (!m_eofCount)
512  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- eof?");
513  m_eofCount++;
514  return ret;
515  }
516 
517  m_eofCount = 0;
518  m_retryCount = 0;
519 
520  m_bufOffset += len;
521  m_buf.truncate(m_bufOffset);
522  ret = m_buf.split('\n');
523  if (m_buf.endsWith('\n'))
524  {
525  m_bufOffset = 0;
526  return ret;
527  }
528 
529  m_buf = ret.takeLast();
530  m_bufOffset = m_buf.size();
531  return ret;
532 }
O_NONBLOCK
#define O_NONBLOCK
Definition: compat.h:357
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:286
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_mainWindow
QObject * m_mainWindow
window to send key events to
Definition: lirc.h:55
mythdb.h
lirc_deinit
int lirc_deinit(struct lirc_state *state)
Definition: lirc_client.cpp:210
LIRC::m_lock
QRecursiveMutex m_lock
Definition: lirc.h:52
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:56
LIRC::m_configFile
QString m_configFile
file containing LIRC->key mappings
Definition: lirc.h:58
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:303
discid.disc.read
def read(device=None, features=[])
Definition: disc.py:35
LIRC::m_retryCount
uint m_retryCount
Definition: lirc.h:63
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:196
LIRC::Process
void Process(const QByteArray &data)
Definition: lirc.cpp:336
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:475
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:62
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:54
LIRC::deleteLater
virtual void deleteLater(void)
Definition: lirc.cpp:92
LIRC::m_buf
QByteArray m_buf
Definition: lirc.h:61
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: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:407
LircKeycodeEvent::kLIRCInvalidKeyCombo
static const unsigned kLIRCInvalidKeyCombo
Definition: lircevent.h:29
uint
unsigned int uint
Definition: compat.h:144
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
std
Definition: mythchrono.h:23
LIRC::IsDoRunSet
bool IsDoRunSet(void) const
Definition: lirc.cpp:330
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:60
LIRC::Init
bool Init(void)
Definition: lirc.cpp:157
LIRC::m_doRun
bool m_doRun
Definition: lirc.h:59
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
LIRCPriv::m_lircConfig
struct lirc_config * m_lircConfig
Definition: lirc.cpp:61
LIRC::d
LIRCPriv * d
Definition: lirc.h:64
LIRCPriv::LIRCPriv
LIRCPriv()=default
lircevent.h
LIRC::start
virtual void start(void)
Definition: lirc.cpp:316
LIRCPriv::~LIRCPriv
~LIRCPriv()
Definition: lirc.cpp:46