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#ifndef __suseconds_t
43#ifdef Q_OS_MACOS
44using __suseconds_t = __darwin_suseconds_t;
45#else
46using __suseconds_t = long int;
47#endif
48#endif
49static constexpr __suseconds_t k100Milliseconds {static_cast<__suseconds_t>(100 * 1000)};
50
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
74
82LIRC::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{
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
125static 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
166bool 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,
295 VERBOSE_LEVEL_CHECK(VB_LIRC,LOG_DEBUG));
296 if (!d->m_lircState)
297 {
298 close(lircd_socket);
299 return false;
300 }
301 d->m_lircState->lirc_lircd = lircd_socket;
302
303 // parse the config file
304 if (!d->m_lircConfig)
305 {
306 QMutexLocker static_lock(&s_lirclibLock);
307 QByteArray cfg = m_configFile.toLocal8Bit();
308 if (lirc_readconfig(d->m_lircState, cfg.constData(), &d->m_lircConfig, nullptr))
309 {
310 LOG(vtype, LOG_ERR, LOC +
311 QString("Failed to read config file '%1'").arg(m_configFile));
312
314 d->m_lircState = nullptr;
315 return false;
316 }
317 }
318
319 LOG(VB_GENERAL, LOG_INFO, LOC +
320 QString("Successfully initialized '%1' using '%2' config")
322
323 return true;
324}
325
326void LIRC::start(void)
327{
328 QMutexLocker locker(&m_lock);
329
330 if (!d->m_lircState)
331 {
332 LOG(VB_GENERAL, LOG_ERR, "start() called without lircd socket");
333 return;
334 }
335
336 m_doRun = true;
338}
339
340bool LIRC::IsDoRunSet(void) const
341{
342 QMutexLocker locker(&m_lock);
343 return m_doRun;
344}
345
346void LIRC::Process(const QByteArray &data)
347{
348 QMutexLocker static_lock(&s_lirclibLock);
349
350 // lirc_code2char will make code point to a static datafer..
351 char *code = nullptr;
352 int ret = lirc_code2char(
353 d->m_lircState, d->m_lircConfig, data.data(), &code);
354
355 while ((0 == ret) && code)
356 {
357 QString lirctext(code);
358 QString qtcode = code;
359 qtcode.replace("ctrl-", "ctrl+", Qt::CaseInsensitive);
360 qtcode.replace("alt-", "alt+", Qt::CaseInsensitive);
361 qtcode.replace("shift-", "shift+", Qt::CaseInsensitive);
362 qtcode.replace("meta-", "meta+", Qt::CaseInsensitive);
363 QKeySequence a(qtcode);
364
365 // Send a dummy keycode if we couldn't convert the key sequence.
366 // This is done so the main code can output a warning for bad
367 // mappings.
368 if (a.isEmpty())
369 {
370 QCoreApplication::postEvent(
372 QEvent::KeyPress, 0,
373 (Qt::KeyboardModifiers)
375 QString(), lirctext));
376 }
377
378 std::vector<LircKeycodeEvent*> keyReleases;
379
380 for (int i = 0; i < a.count(); i++)
381 {
382#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
383 int keycode = a[i];
384 Qt::KeyboardModifiers mod = Qt::NoModifier;
385 mod |= (Qt::SHIFT & keycode) ? Qt::ShiftModifier : Qt::NoModifier;
386 mod |= (Qt::META & keycode) ? Qt::MetaModifier : Qt::NoModifier;
387 mod |= (Qt::CTRL & keycode) ? Qt::ControlModifier: Qt::NoModifier;
388 mod |= (Qt::ALT & keycode) ? Qt::AltModifier : Qt::NoModifier;
389
390 keycode &= ~Qt::MODIFIER_MASK;
391#else
392 int keycode = a[i].key();
393 Qt::KeyboardModifiers mod = a[i].keyboardModifiers();
394#endif
395
396 QString text = "";
397 if (!mod)
398 text = QString(QChar(keycode));
399
400 QCoreApplication::postEvent(
402 QEvent::KeyPress, keycode, mod, text, lirctext));
403
404 keyReleases.push_back(
406 QEvent::KeyRelease, keycode, mod, text, lirctext));
407 }
408
409 for (int i = (int)keyReleases.size() - 1; i>=0; i--)
410 QCoreApplication::postEvent(m_mainWindow, keyReleases[i]);
411
412 ret = lirc_code2char(
413 d->m_lircState, d->m_lircConfig, data.data(), &code);
414 }
415}
416
417void LIRC::run(void)
418{
419 RunProlog();
420#if 0
421 LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- start");
422#endif
423 /* Process all events read */
424 while (IsDoRunSet())
425 {
427 std::this_thread::sleep_for(100ms);
428
429 if ((m_eofCount >= 10) || (!d->m_lircState))
430 {
431 QMutexLocker locker(&m_lock);
432 m_eofCount = 0;
433 if (++m_retryCount > 1000)
434 {
435 LOG(VB_GENERAL, LOG_ERR, LOC +
436 "Failed to reconnect, exiting LIRC thread.");
437 m_doRun = false;
438 continue;
439 }
440 LOG(VB_FILE, LOG_WARNING, LOC + "EOF -- reconnecting");
441
443 d->m_lircState = nullptr;
444
445 if (Init())
446 m_retryCount = 0;
447 else
448 // wait a while before we retry..
449 std::this_thread::sleep_for(2s);
450
451 continue;
452 }
453
454 fd_set readfds;
455 FD_ZERO(&readfds); // NOLINT(readability-isolate-declaration)
456 FD_SET(d->m_lircState->lirc_lircd, &readfds);
457
458 // the maximum time select() should wait
459 struct timeval timeout {1, k100Milliseconds}; // 1 second, 100 ms
460
461 int ret = select(d->m_lircState->lirc_lircd + 1, &readfds, nullptr, nullptr,
462 &timeout);
463
464 if (ret < 0 && errno != EINTR)
465 {
466 LOG(VB_GENERAL, LOG_ERR, LOC + "select() failed" + ENO);
467 continue;
468 }
469
470 // 0: Timer expired with no data, repeat select
471 // -1: Iinterrupted while waiting, repeat select
472 if (ret <= 0)
473 continue;
474
475 QList<QByteArray> codes = GetCodes();
476 for (const auto & code : std::as_const(codes))
477 Process(code);
478 }
479#if 0
480 LOG(VB_GENERAL, LOG_DEBUG, LOC + "run -- end");
481#endif
482 RunEpilog();
483}
484
485QList<QByteArray> LIRC::GetCodes(void)
486{
487 QList<QByteArray> ret;
488 ssize_t len = -1;
489
490 uint buf_size = m_buf.size() + 128;
491 m_buf.resize(buf_size);
492
493 while (true)
494 {
495 len = read(d->m_lircState->lirc_lircd, m_buf.data() + m_bufOffset, 128);
496 if (len >= 0)
497 break;
498
499 switch (errno)
500 {
501 case EINTR:
502 continue;
503
504 case EAGAIN:
505 return ret;
506
507 case ENOTCONN: // 107 (according to asm-generic/errno.h)
508 if (!m_eofCount)
509 LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- EOF?");
510 m_eofCount++;
511 return ret;
512
513 default:
514 LOG(VB_GENERAL, LOG_ERR, LOC + "Could not read socket" + ENO);
515 return ret;
516 }
517 }
518
519 if (!len)
520 {
521 if (!m_eofCount)
522 LOG(VB_GENERAL, LOG_NOTICE, LOC + "GetCodes -- eof?");
523 m_eofCount++;
524 return ret;
525 }
526
527 m_eofCount = 0;
528 m_retryCount = 0;
529
530 m_bufOffset += len;
531 m_buf.truncate(m_bufOffset);
532 ret = m_buf.split('\n');
533 if (m_buf.endsWith('\n'))
534 {
535 m_bufOffset = 0;
536 return ret;
537 }
538
539 m_buf = ret.takeLast();
540 m_bufOffset = m_buf.size();
541 return ret;
542}
~LIRCPriv()
Definition: lirc.cpp:55
LIRCPriv()=default
struct lirc_state * m_lircState
Definition: lirc.cpp:69
struct lirc_config * m_lircConfig
Definition: lirc.cpp:70
void Process(const QByteArray &data)
Definition: lirc.cpp:346
~LIRC() override
Definition: lirc.cpp:96
uint m_bufOffset
Definition: lirc.h:54
QString m_lircdDevice
device on which to receive lircd data
Definition: lirc.h:50
virtual void start(void)
Definition: lirc.cpp:326
bool IsDoRunSet(void) const
Definition: lirc.cpp:340
static QMutex s_lirclibLock
Definition: lirc.h:48
QString m_configFile
file containing LIRC->key mappings
Definition: lirc.h:52
QObject * m_mainWindow
window to send key events to
Definition: lirc.h:49
QRecursiveMutex m_lock
Definition: lirc.h:47
bool Init(void)
Definition: lirc.cpp:166
uint m_retryCount
Definition: lirc.h:57
bool m_doRun
Definition: lirc.h:53
LIRCPriv * d
Definition: lirc.h:58
QByteArray m_buf
Definition: lirc.h:55
uint m_eofCount
Definition: lirc.h:56
void TeardownAll()
Definition: lirc.cpp:107
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:417
QList< QByteArray > GetCodes(void)
Definition: lirc.cpp:485
LIRC(QObject *main_window, QString lircd_device, QString our_program, QString config_file)
Definition: lirc.cpp:82
virtual void deleteLater(void)
Definition: lirc.cpp:101
static const unsigned kLIRCInvalidKeyCombo
Definition: lircevent.h:29
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
#define O_NONBLOCK
Definition: compat.h:258
unsigned int uint
Definition: compat.h:68
#define close
Definition: compat.h:30
__darwin_suseconds_t __suseconds_t
static guint32 * tmp
Definition: goom_core.cpp:26
static const iso6937table * d
#define LOC
Definition: lirc.cpp:40
static QByteArray get_ip(const QString &h)
Definition: lirc.cpp:125
static constexpr __suseconds_t k100Milliseconds
Definition: lirc.cpp:49
int lirc_code2char(const struct lirc_state *state, struct lirc_config *config, const char *code, char **string)
int lirc_deinit(struct lirc_state *state)
void lirc_freeconfig(struct lirc_config *config)
int lirc_readconfig(const struct lirc_state *state, const char *file, struct lirc_config **config, int(check)(char *s))
struct lirc_state * lirc_init(const char *lircrc_root_file, const char *lircrc_user_file, const char *prog, const char *lircd, int verbose)
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
def read(device=None, features=[])
Definition: disc.py:35
STL namespace.
int lirc_lircd
Definition: lirc_client.h:39