From 1aeb51dea6f432cb4dd9769d9cd9e94ba30c00ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= <marko.makela@iki.fi>
Date: Sun, 6 Nov 2022 15:44:31 +0200
Subject: [PATCH] Support kernel-based LIRC

The Linux Infrared Remote Control (LIRC) started as a user-space driver
that offered a Unix Domain Socket based interface to other processes.

Nowadays, there is a LIRC driver in the Linux kernel, which supports
LIRC_MODE_SCANCODE for reporting key codes in addition to raw scan codes.
The key codes and the low-level interface can be configured with
ir-keytable.

We introduce the option -k or --klirc (default: /dev/lirc0) for
interfacing to the kernel-based driver.
---
 Make.config.template |   1 +
 Makefile             |   3 ++
 klirc.c              | 105 +++++++++++++++++++++++++++++++++++++++++++
 klirc.h              |  27 +++++++++++
 vdr.c                |  11 +++++
 5 files changed, 147 insertions(+)
 create mode 100644 klirc.c
 create mode 100644 klirc.h

diff --git a/Make.config.template b/Make.config.template
index 82d55617..957f398e 100644
--- a/Make.config.template
+++ b/Make.config.template
@@ -73,6 +73,7 @@ endif
 #PLGCFG = $(CONFDIR)/plugins.mk
 
 ### The remote control:
+KLIRC_DEVICE = /dev/lirc0
 LIRC_DEVICE = /var/run/lirc/lircd
 
 ### Define if you always want to use LIRC, independent of the --lirc option:
diff --git a/Makefile b/Makefile
index a251f903..f4df3ad2 100644
--- a/Makefile
+++ b/Makefile
@@ -89,6 +89,7 @@ SILIB    = $(LSIDIR)/libsi.a
 
 OBJS = args.o audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o\
        dvbplayer.o dvbspu.o dvbsubtitle.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\
+       klirc.o \
        lirc.o menu.o menuitems.o mtd.o nit.o osdbase.o osd.o pat.o player.o plugin.o positioner.o\
        receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o shutdown.o\
        skinclassic.o skinlcars.o skins.o skinsttng.o sourceparams.o sources.o spu.o status.o svdrp.o themes.o thread.o\
@@ -120,8 +121,10 @@ DEFINES += -DSDNOTIFY
 LIBS += $(shell $(PKG_CONFIG) --silence-errors --libs libsystemd-daemon || $(PKG_CONFIG) --libs libsystemd)
 endif
 
+KLIRC_DEVICE ?= /dev/lirc0
 LIRC_DEVICE ?= /var/run/lirc/lircd
 
+DEFINES += -DKLIRC_DEVICE=\"$(KLIRC_DEVICE)\"
 DEFINES += -DLIRC_DEVICE=\"$(LIRC_DEVICE)\"
 DEFINES += -DVIDEODIR=\"$(VIDEODIR)\"
 DEFINES += -DCONFDIR=\"$(CONFDIR)\"
diff --git a/klirc.c b/klirc.c
new file mode 100644
index 00000000..5ccbd047
--- /dev/null
+++ b/klirc.c
@@ -0,0 +1,105 @@
+/*
+ * klirc.c: LIRC remote control
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ */
+
+#include "klirc.h"
+#include <sys/ioctl.h>
+
+cKLircRemote::cKLircRemote(const char *DeviceName)
+:cRemote("KLIRC")
+,cThread("KLIRC remote control")
+{
+  Connect(DeviceName);
+  Start();
+}
+
+cKLircRemote::~cKLircRemote()
+{
+  int fh = f;
+  f = -1;
+  Cancel();
+  if (fh >= 0)
+     close(fh);
+}
+
+inline void cKLircRemote::Connect(const char *DeviceName)
+{
+  unsigned mode = LIRC_MODE_SCANCODE;
+  f = open(DeviceName, O_RDONLY, 0);
+  if (f < 0)
+     LOG_ERROR_STR(DeviceName);
+  else if (ioctl(f, LIRC_SET_REC_MODE, &mode)) {
+     LOG_ERROR_STR(DeviceName);
+     close(f);
+     f = -1;
+     }
+}
+
+bool cKLircRemote::Ready(void)
+{
+  return f >= 0;
+}
+
+void cKLircRemote::Action(void)
+{
+  if (f < 0)
+     return;
+  cTimeMs FirstTime;
+  cTimeMs LastTime;
+  cTimeMs ThisTime;
+  uint32_t LastKeyCode = 0;
+  uint16_t LastFlags = false;
+  bool pressed = false;
+  bool repeat = false;
+  int timeout = -1;
+
+  while (Running()) {
+        lirc_scancode sc;
+        bool ready = cFile::FileReady(f, timeout);
+        int ret = ready ? safe_read(f, &sc, sizeof sc) : -1;
+
+        if (ready && ret > 0) {
+           int Delta = ThisTime.Elapsed(); // the time between two subsequent LIRC events
+           ThisTime.Set();
+           if (!(sc.flags & LIRC_SCANCODE_FLAG_REPEAT)) { // new key pressed
+              if (sc.keycode == LastKeyCode &&
+                  (sc.flags ^ LastFlags) & LIRC_SCANCODE_FLAG_TOGGLE &&
+                  FirstTime.Elapsed() < (uint)Setup.RcRepeatDelay)
+                 continue; // skip keys coming in too fast
+              if (repeat)
+                 Put(LastKeyCode, false, true); // generated release for previous repeated key
+              LastKeyCode = sc.keycode;
+              LastFlags = sc.flags;
+              pressed = true;
+              repeat = false;
+              FirstTime.Set();
+              timeout = -1;
+              }
+           else if (FirstTime.Elapsed() < (uint)Setup.RcRepeatDelay)
+              continue; // repeat function kicks in after a short delay
+           else if (LastTime.Elapsed() < (uint)Setup.RcRepeatDelta)
+              continue; // skip same keys coming in too fast
+           else {
+              pressed = true;
+              repeat = true;
+              timeout = Delta * 3 / 2;
+              }
+           if (pressed) {
+              LastTime.Set();
+              Put(sc.keycode, repeat);
+              }
+           }
+        else {
+           if (pressed && repeat) // the last one was a repeat, so let's generate a release
+              Put(LastKeyCode, false, true);
+           pressed = false;
+           repeat = false;
+           LastKeyCode = 0;
+           timeout = -1;
+           }
+        }
+}
diff --git a/klirc.h b/klirc.h
new file mode 100644
index 00000000..c3dca52e
--- /dev/null
+++ b/klirc.h
@@ -0,0 +1,27 @@
+/*
+ * klirc.h: Kernel Linux Infrared Remote Control
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ */
+
+#ifndef __KLIRC_H
+#define __KLIRC_H
+
+#include <linux/lirc.h>
+#include "remote.h"
+#include "thread.h"
+
+class cKLircRemote : public cRemote, private cThread {
+private:
+  int f;
+  virtual void Action(void);
+  inline void Connect(const char *DeviceName);
+public:
+  cKLircRemote(const char *DeviceName);
+  virtual ~cKLircRemote();
+  virtual bool Ready(void);
+  };
+
+#endif //__KLIRC_H
diff --git a/vdr.c b/vdr.c
index 06c0c9a9..8136c98a 100644
--- a/vdr.c
+++ b/vdr.c
@@ -53,6 +53,7 @@
 #include "interface.h"
 #include "keys.h"
 #include "libsi/si.h"
+#include "klirc.h"
 #include "lirc.h"
 #include "menu.h"
 #include "osdbase.h"
@@ -240,6 +241,7 @@ int main(int argc, char *argv[])
 
   bool UseKbd = true;
   const char *LircDevice = NULL;
+  const char *KLircDevice = NULL;
 #if !defined(REMOTE_KBD)
   UseKbd = false;
 #endif
@@ -281,6 +283,7 @@ int main(int argc, char *argv[])
       { "grab",     required_argument, NULL, 'g' },
       { "help",     no_argument,       NULL, 'h' },
       { "instance", required_argument, NULL, 'i' },
+      { "klirc",    optional_argument, NULL, 'k' },
       { "lib",      required_argument, NULL, 'L' },
       { "lirc",     optional_argument, NULL, 'l' | 0x100 },
       { "localedir",required_argument, NULL, 'l' | 0x200 },
@@ -405,6 +408,9 @@ int main(int argc, char *argv[])
                        }
                     fprintf(stderr, "vdr: invalid instance id: %s\n", optarg);
                     return 2;
+          case 'k':
+                    KLircDevice = optarg ? optarg : KLIRC_DEVICE;
+                    break;
           case 'l': {
                     char *p = strchr(optarg, '.');
                     if (p)
@@ -593,6 +599,8 @@ int main(int argc, char *argv[])
                "                           if logging should be done to LOG_LOCALn instead of\n"
                "                           LOG_USER, add '.n' to LEVEL, as in 3.7 (n=0..7)\n"
                "  -L DIR,   --lib=DIR      search for plugins in DIR (default is %s)\n"
+               "            --klirc[=PATH] use a Linux kernel LIRC remote control device, attached to PATH\n"
+               "                           (default: %s)\n"
                "            --lirc[=PATH]  use a LIRC remote control device, attached to PATH\n"
                "                           (default: %s)\n"
                "            --localedir=DIR search for locale files in DIR (default is\n"
@@ -629,6 +637,7 @@ int main(int argc, char *argv[])
                DEFAULTEPGDATAFILENAME,
                MAXVIDEOFILESIZEDEFAULT,
                DEFAULTPLUGINDIR,
+               KLIRC_DEVICE,
                LIRC_DEVICE,
                DEFAULTLOCDIR,
                DEFAULTSVDRPPORT,
@@ -874,6 +883,8 @@ int main(int argc, char *argv[])
      }
 
   // Remote Controls:
+  if (KLircDevice)
+     new cKLircRemote(KLircDevice);
   if (LircDevice)
      new cLircRemote(LircDevice);
   if (!DaemonMode && HasStdin && UseKbd)
-- 
2.38.1

