MythTV  0.28pre
 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 "mythsystemlegacy.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();
102  QObject::deleteLater();
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  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(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(lircdDevice) + ENO);
291  }
292  }
293 
294  d->lircState = lirc_init("/etc/lircrc", ".lircrc", "mythtv", NULL, 0);
295  if (!d->lircState)
296  {
297  close(lircd_socket);
298  return false;
299  }
300  d->lircState->lirc_lircd = lircd_socket;
301 
302  // parse the config file
303  if (!d->lircConfig)
304  {
305  QMutexLocker static_lock(&lirclib_lock);
306  QByteArray cfg = configFile.toLocal8Bit();
307  if (lirc_readconfig(d->lircState, cfg.constData(), &d->lircConfig, NULL))
308  {
309  LOG(vtype, LOG_ERR, LOC +
310  QString("Failed to read config file '%1'").arg(configFile));
311 
313  d->lircState = NULL;
314  return false;
315  }
316  }
317 
318  LOG(VB_GENERAL, LOG_INFO, LOC +
319  QString("Successfully initialized '%1' using '%2' config")
320  .arg(lircdDevice).arg(configFile));
321 
322  return true;
323 }
324 
325 void LIRC::start(void)
326 {
327  QMutexLocker locker(&lock);
328 
329  if (!d->lircState)
330  {
331  LOG(VB_GENERAL, LOG_ERR, "start() called without lircd socket");
332  return;
333  }
334 
335  doRun = true;
336  MThread::start();
337 }
338 
339 bool LIRC::IsDoRunSet(void) const
340 {
341  QMutexLocker locker(&lock);
342  return doRun;
343 }
344 
345 void LIRC::Process(const QByteArray &data)
346 {
347  QMutexLocker static_lock(&lirclib_lock);
348 
349  // lirc_code2char will make code point to a static datafer..
350  char *code = NULL;
351  int ret = lirc_code2char(
352  d->lircState, d->lircConfig, const_cast<char*>(data.constData()), &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  vector<LircKeycodeEvent*> keyReleases;
378  for (unsigned int i = 0; i < a.count(); i++)
379  {
380  int keycode = a[i];
381  Qt::KeyboardModifiers mod = Qt::NoModifier;
382  mod |= (Qt::SHIFT & keycode) ? Qt::ShiftModifier : Qt::NoModifier;
383  mod |= (Qt::META & keycode) ? Qt::MetaModifier : Qt::NoModifier;
384  mod |= (Qt::CTRL & keycode) ? Qt::ControlModifier: Qt::NoModifier;
385  mod |= (Qt::ALT & keycode) ? Qt::AltModifier : Qt::NoModifier;
386 
387  keycode &= ~Qt::MODIFIER_MASK;
388 
389  QString text = "";
390  if (!mod)
391  text = QString(QChar(keycode));
392 
393  QCoreApplication::postEvent(
395  QEvent::KeyPress, keycode, mod, text, lirctext));
396 
397  keyReleases.push_back(
398  new LircKeycodeEvent(
399  QEvent::KeyRelease, keycode, mod, text, lirctext));
400  }
401 
402  for (int i = (int)keyReleases.size() - 1; i>=0; i--)
403  QCoreApplication::postEvent(m_mainWindow, keyReleases[i]);
404 
405  ret = lirc_code2char(
406  d->lircState, d->lircConfig, const_cast<char*>(data.constData()), &code);
407  }
408 }
409 
410 void LIRC::run(void)
411 {
412  RunProlog();
413 #if 0
414  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- start");
415 #endif
416  /* Process all events read */
417  while (IsDoRunSet())
418  {
419  if (eofCount && retryCount)
420  usleep(100 * 1000);
421 
422  if ((eofCount >= 10) || (!d->lircState))
423  {
424  QMutexLocker locker(&lock);
425  eofCount = 0;
426  if (++retryCount > 1000)
427  {
428  LOG(VB_GENERAL, LOG_ERR, LOC +
429  "Failed to reconnect, exiting LIRC thread.");
430  doRun = false;
431  continue;
432  }
433  LOG(VB_FILE, LOG_WARNING, LOC + "EOF -- reconnecting");
434 
436  d->lircState = NULL;
437 
438  if (Init())
439  retryCount = 0;
440  else
441  sleep(2); // wait a while before we retry..
442 
443  continue;
444  }
445 
446  fd_set readfds;
447  FD_ZERO(&readfds);
448  FD_SET(d->lircState->lirc_lircd, &readfds);
449 
450  // the maximum time select() should wait
451  struct timeval timeout;
452  timeout.tv_sec = 1; // 1 second
453  timeout.tv_usec = 100 * 1000; // 100 ms
454 
455  int ret = select(d->lircState->lirc_lircd + 1, &readfds, NULL, NULL,
456  &timeout);
457 
458  if (ret < 0 && errno != EINTR)
459  {
460  LOG(VB_GENERAL, LOG_ERR, LOC + "select() failed" + ENO);
461  continue;
462  }
463 
464  // 0: Timer expired with no data, repeat select
465  // -1: Iinterrupted while waiting, repeat select
466  if (ret <= 0)
467  continue;
468 
469  QList<QByteArray> codes = GetCodes();
470  for (uint i = 0; i < (uint) codes.size(); i++)
471  Process(codes[i]);
472  }
473 #if 0
474  LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- end");
475 #endif
476  RunEpilog();
477 }
478 
479 QList<QByteArray> LIRC::GetCodes(void)
480 {
481  QList<QByteArray> ret;
482  ssize_t len = -1;
483 
484  uint buf_size = buf.size() + 128;
485  buf.resize(buf_size);
486 
487  while (true)
488  {
489  len = read(d->lircState->lirc_lircd, buf.data() + buf_offset, 128);
490  if (len >= 0)
491  break;
492 
493  switch (errno)
494  {
495  case EINTR:
496  continue;
497 
498  case EAGAIN:
499  return ret;
500 
501  case ENOTCONN: // 107 (according to asm-generic/errno.h)
502  if (!eofCount)
503  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- EOF?");
504  eofCount++;
505  return ret;
506 
507  default:
508  LOG(VB_GENERAL, LOG_ERR, LOC + "Could not read socket" + ENO);
509  return ret;
510  }
511  }
512 
513  if (!len)
514  {
515  if (!eofCount)
516  LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- eof?");
517  eofCount++;
518  return ret;
519  }
520 
521  eofCount = 0;
522  retryCount = 0;
523 
524  buf_offset += len;
525  buf.truncate(buf_offset);
526  ret = buf.split('\n');
527  if (buf.endsWith('\n'))
528  {
529  buf_offset = 0;
530  return ret;
531  }
532 
533  buf = ret.takeLast();
534  buf_offset = buf.size();
535  return ret;
536 }
537