MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
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 <vector>
13 using namespace std;
14 
15 // Qt headers
16 #include <QCoreApplication>
17 #include <QEvent>
18 #include <QKeySequence>
19 #include <QStringList>
20 
21 // MythTV headers
22 #include "mythdb.h"
23 #include "mythsystem.h"
24 #include "lircevent.h"
25 #include "lirc_client.h"
26 
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <sys/select.h>
30 #include <sys/un.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <netdb.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <sys/wait.h>
37 #include "mythlogging.h"
38 
39 #define LOC QString("LIRC: ")
40 
41 class LIRCPriv
42 {
43  public:
44  LIRCPriv() : lircState(NULL), lircConfig(NULL) {}
46  {
47  if (lircState)
48  {
49  lirc_deinit(lircState);
50  lircState = NULL;
51  }
52  if (lircConfig)
53  {
54  lirc_freeconfig(lircConfig);
55  lircConfig = NULL;
56  }
57  }
58 
61 };
62 
63 QMutex LIRC::lirclib_lock;
64 
72 LIRC::LIRC(QObject *main_window,
73  const QString &lircd_device,
74  const QString &our_program,
75  const QString &config_file)
76  : MThread("LIRC"),
77  lock(QMutex::Recursive),
78  m_mainWindow(main_window),
79  lircdDevice(lircd_device),
80  program(our_program),
81  configFile(config_file),
82  doRun(false),
83  buf_offset(0),
84  eofCount(0),
85  retryCount(0),
86  d(new LIRCPriv())
87 {
88  lircdDevice.detach();
89  program.detach();
90  configFile.detach();
91  buf.resize(0);
92 }
93 
95 {
96  TeardownAll();
97 }
98 
100 {
101  TeardownAll();
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 = NULL;
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  memset(&hints, 0, sizeof(hints));
132  hints.ai_family = AF_INET;
133  hints.ai_socktype = SOCK_STREAM;
134  hints.ai_protocol = IPPROTO_TCP;
135 
136  struct addrinfo *result;
137  int err = getaddrinfo(hba.constData(), NULL, &hints, &result);
138  if (err)
139  {
140  LOG(VB_GENERAL, LOG_DEBUG,
141  QString("get_ip: %1").arg(gai_strerror(err)));
142  return QString("").toLatin1();
143  }
144 
145  int addrlen = result->ai_addrlen;
146  if (!addrlen)
147  {
148  freeaddrinfo(result);
149  return QString("").toLatin1();
150  }
151 
152  if (result->ai_addr->sa_family != AF_INET)
153  {
154  freeaddrinfo(result);
155  return QString("").toLatin1();
156  }
157 
158  sin_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr;
159  hba = QByteArray(inet_ntoa(sin_addr));
160  freeaddrinfo(result);
161 
162  return hba;
163 }
164 
165 bool LIRC::Init(void)
166 {
167  QMutexLocker locker(&lock);
168  if (d->lircState)
169  return true;
170 
171  uint64_t vtype = (0 == retryCount) ? VB_GENERAL : VB_FILE;
172 
173  int lircd_socket = -1;
174  if (lircdDevice.startsWith('/'))
175  {
176  // Connect the unix socket
177  QByteArray dev = lircdDevice.toLocal8Bit();
178  if (dev.size() > 107)
179  {
180  LOG(vtype, LOG_ERR, LOC +
181  QString("lircdDevice '%1'").arg(lircdDevice) +
182  " is too long for the 'unix' socket API");
183 
184  return false;
185  }
186 
187  lircd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
188  if (lircd_socket < 0)
189  {
190  LOG(vtype, LOG_ERR, LOC + QString("Failed to open Unix socket '%1'")
191  .arg(lircdDevice) + ENO);
192 
193  return false;
194  }
195 
196  struct sockaddr_un addr;
197  memset(&addr, 0, sizeof(sockaddr_un));
198  addr.sun_family = AF_UNIX;
199  strncpy(addr.sun_path, dev.constData(),107);
200 
201  int ret = ::connect(lircd_socket, (struct sockaddr*) &addr,
202  sizeof(addr));
203 
204  if (ret < 0)
205  {
206  LOG(vtype, LOG_ERR, LOC +
207  QString("Failed to connect to Unix socket '%1'")
208  .arg(lircdDevice) + ENO);
209 
210  close(lircd_socket);
211  return false;
212  }
213  }
214  else
215  {
216  lircd_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
217  if (lircd_socket < 0)
218  {
219  LOG(vtype, LOG_ERR, LOC + QString("Failed to open TCP socket '%1'")
220  .arg(lircdDevice) + ENO);
221 
222  return false;
223  }
224 
225  QString dev = lircdDevice;
226  uint port = 8765;
227  QStringList tmp = lircdDevice.split(':');
228  if (2 == tmp.size())
229  {
230  dev = tmp[0];
231  port = (tmp[1].toUInt()) ? tmp[1].toUInt() : port;
232  }
233  QByteArray device = get_ip(dev);
234  struct sockaddr_in addr;
235  memset(&addr, 0, sizeof(sockaddr_in));
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(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(lircdDevice) + ENO);
272  }
273  }
274 
275  // Attempt to inline out-of-band messages and keep the connection open..
276  int i = 1;
277  setsockopt(lircd_socket, SOL_SOCKET, SO_OOBINLINE, &i, sizeof(i));
278  i = 1;
279  setsockopt(lircd_socket, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
280  }
281 
282  d->lircState = lirc_init("/etc/lircrc", ".lircrc", "mythtv", NULL, 0);
283  if (!d->lircState)
284  {
285  close(lircd_socket);
286  return false;
287  }
288  d->lircState->lirc_lircd = lircd_socket;
289 
290  // parse the config file
291  if (!d->lircConfig)
292  {
293  QMutexLocker static_lock(&lirclib_lock);
294  QByteArray cfg = configFile.toLocal8Bit();
295  if (lirc_readconfig(d->lircState, cfg.constData(), &d->lircConfig, NULL))
296  {
297  LOG(vtype, LOG_ERR, LOC +
298  QString("Failed to read config file '%1'").arg(configFile));
299 
301  d->lircState = NULL;
302  return false;
303  }
304  }
305 
306  LOG(VB_GENERAL, LOG_INFO, LOC +
307  QString("Successfully initialized '%1' using '%2' config")
308  .arg(lircdDevice).arg(configFile));
309 
310  return true;
311 }
312 
313 void LIRC::start(void)
314 {
315  QMutexLocker locker(&lock);
316 
317  if (!d->lircState)
318  {
319  LOG(VB_GENERAL, LOG_ERR, "start() called without lircd socket");
320  return;
321  }
322 
323  doRun = true;
324  MThread::start();
325 }
326 
327 bool LIRC::IsDoRunSet(void) const
328 {
329  QMutexLocker locker(&lock);
330  return doRun;
331 }
332 
333 void LIRC::Process(const QByteArray &data)
334 {
335  QMutexLocker static_lock(&lirclib_lock);
336 
337  // lirc_code2char will make code point to a static datafer..
338  char *code = NULL;
339  int ret = lirc_code2char(
340  d->lircState, d->lircConfig, const_cast<char*>(data.constData()), &code);
341 
342  while ((0 == ret) && code)
343  {
344  QString lirctext(code);
345  QString qtcode = code;
346  qtcode.replace("ctrl-", "ctrl+", Qt::CaseInsensitive);
347  qtcode.replace("alt-", "alt+", Qt::CaseInsensitive);
348  qtcode.replace("shift-", "shift+", Qt::CaseInsensitive);
349  qtcode.replace("meta-", "meta+", Qt::CaseInsensitive);
350  QKeySequence a(qtcode);
351 
352  // Send a dummy keycode if we couldn't convert the key sequence.
353  // This is done so the main code can output a warning for bad
354  // mappings.
355  if (a.isEmpty())
356  {
357  QCoreApplication::postEvent(
359  QEvent::KeyPress, 0,
360  (Qt::KeyboardModifiers)
362  QString(), lirctext));
363  }
364 
365  vector<LircKeycodeEvent*> keyReleases;
366  for (unsigned int i = 0; i < a.count(); i++)
367  {
368  int keycode = a[i];
369  Qt::KeyboardModifiers mod = Qt::NoModifier;
370  mod |= (Qt::SHIFT & keycode) ? Qt::ShiftModifier : Qt::NoModifier;
371  mod |= (Qt::META & keycode) ? Qt::MetaModifier : Qt::NoModifier;
372  mod |= (Qt::CTRL & keycode) ? Qt::ControlModifier: Qt::NoModifier;
373  mod |= (Qt::ALT & keycode) ? Qt::AltModifier : Qt::NoModifier;
374 
375  keycode &= ~Qt::MODIFIER_MASK;
376 
377  QString text = "";
378  if (!mod)
379  text = QString(QChar(keycode));
380 
381  QCoreApplication::postEvent(
383  QEvent::KeyPress, keycode, mod, text, lirctext));
384 
385  keyReleases.push_back(
386  new LircKeycodeEvent(
387  QEvent::KeyRelease, keycode, mod, text, lirctext));
388  }
389 
390  for (int i = (int)keyReleases.size() - 1; i>=0; i--)
391  QCoreApplication::postEvent(m_mainWindow, keyReleases[i]);
392 
393  ret = lirc_code2char(
394  d->lircState, d->lircConfig, const_cast<char*>(data.constData()), &code);
395  }
396 }
397 
398 void LIRC::run(void)
399 {
400  RunProlog();
401 #if 0
402  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- start");
403 #endif
404  /* Process all events read */
405  while (IsDoRunSet())
406  {
407  if (eofCount && retryCount)
408  usleep(100 * 1000);
409 
410  if ((eofCount >= 10) || (!d->lircState))
411  {
412  QMutexLocker locker(&lock);
413  eofCount = 0;
414  if (++retryCount > 1000)
415  {
416  LOG(VB_GENERAL, LOG_ERR, LOC +
417  "Failed to reconnect, exiting LIRC thread.");
418  doRun = false;
419  continue;
420  }
421  LOG(VB_FILE, LOG_WARNING, LOC + "EOF -- reconnecting");
422 
424  d->lircState = NULL;
425 
426  if (Init())
427  retryCount = 0;
428  else
429  sleep(2); // wait a while before we retry..
430 
431  continue;
432  }
433 
434  fd_set readfds;
435  FD_ZERO(&readfds);
436  FD_SET(d->lircState->lirc_lircd, &readfds);
437 
438  // the maximum time select() should wait
439  struct timeval timeout;
440  timeout.tv_sec = 1; // 1 second
441  timeout.tv_usec = 100 * 1000; // 100 ms
442 
443  int ret = select(d->lircState->lirc_lircd + 1, &readfds, NULL, NULL,
444  &timeout);
445 
446  if (ret < 0 && errno != EINTR)
447  {
448  LOG(VB_GENERAL, LOG_ERR, LOC + "select() failed" + ENO);
449  continue;
450  }
451 
452  // 0: Timer expired with no data, repeat select
453  // -1: Iinterrupted while waiting, repeat select
454  if (ret <= 0)
455  continue;
456 
457  QList<QByteArray> codes = GetCodes();
458  for (uint i = 0; i < (uint) codes.size(); i++)
459  Process(codes[i]);
460  }
461 #if 0
462  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- end");
463 #endif
464  RunEpilog();
465 }
466 
467 QList<QByteArray> LIRC::GetCodes(void)
468 {
469  QList<QByteArray> ret;
470  ssize_t len = -1;
471 
472  uint buf_size = buf.size() + 128;
473  buf.resize(buf_size);
474 
475  while (true)
476  {
477  len = read(d->lircState->lirc_lircd, buf.data() + buf_offset, 128);
478  if (len >= 0)
479  break;
480 
481  switch (errno)
482  {
483  case EINTR:
484  continue;
485 
486  case EAGAIN:
487  return ret;
488 
489  case ENOTCONN: // 107 (according to asm-generic/errno.h)
490  if (!eofCount)
491  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- EOF?");
492  eofCount++;
493  return ret;
494 
495  default:
496  LOG(VB_GENERAL, LOG_ERR, LOC + "Could not read socket" + ENO);
497  return ret;
498  }
499  }
500 
501  if (!len)
502  {
503  if (!eofCount)
504  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- eof?");
505  eofCount++;
506  return ret;
507  }
508 
509  eofCount = 0;
510  retryCount = 0;
511 
512  buf_offset += len;
513  buf.truncate(buf_offset);
514  ret = buf.split('\n');
515  if (buf.endsWith('\n'))
516  {
517  buf_offset = 0;
518  return ret;
519  }
520 
521  buf = ret.takeLast();
522  buf_offset = buf.size();
523  return ret;
524 }
525