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