diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 108 | ||||
-rw-r--r-- | inputdev.c | 673 | ||||
-rw-r--r-- | inputdev.h | 71 | ||||
-rw-r--r-- | plugin.c | 157 |
5 files changed, 1013 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5c0f08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.so +*.so.1.* +/.dependencies diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d7c1c2 --- /dev/null +++ b/Makefile @@ -0,0 +1,108 @@ +PLUGIN = inputdev +VERSION = 0.0.1 + +### The C++ compiler and options: + +PKG_CONFIG = pkg-config +SYSTEMD_CFLAGS = $(shell ${PKG_CONFIG} --cflags libsystemd-daemon || echo "libsystemd_missing") +SYSTEMD_LIBS = $(shell ${PKG_CONFIG} --libs libsystemd-daemon || echo "libsystemd_missing") + +SOCKET_PATH = /var/run/vdr/inputdev + +AM_CXXFLAGS = -DPACKAGE_VERSION=\"${VERSION}\" -DSOCKET_PATH=\"${SOCKET_PATH}\" ${SYSTEMD_CFLAGS} +AM_CXXFLAGS += -D_FORTIFY_SOURCE=2 -Wall -Werror + +LIBS = ${SYSTEMD_LIBS} + +CXX ?= g++ +CXXFLAGS ?= -g -O3 -Wall -Werror=overloaded-virtual -Wno-parentheses + +### The directory environment: + +VDRDIR ?= ../../.. +LIBDIR ?= ../../lib +TMPDIR ?= /tmp + +### Allow user defined options to overwrite defaults: + +include $(VDRDIR)/Make.global +-include $(VDRDIR)/Make.config + +### The version number of VDR's plugin API (taken from VDR's "config.h"): + +APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +### The object files (add further files here): + +OBJS = plugin.o inputdev.o + +### The main target: + +all: libvdr-$(PLUGIN).so i18n + +### Implicit rules: + +%.o: %.c + $(CXX) $(AM_CXXFLAGS) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +### Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +LOCALEDIR = $(VDRDIR)/locale +I18Npo = $(wildcard $(PODIR)/*.po) +I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(wildcard *.c) + xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ $^ + +%.po: $(I18Npot) + msgmerge -U --no-wrap --no-location --backup=none -q $@ $< + @touch $@ + +$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo + @mkdir -p $(dir $@) + cp $< $@ + +.PHONY: i18n +i18n: $(I18Nmsgs) $(I18Npot) + +### Targets: + +libvdr-$(PLUGIN).so: $(OBJS) + $(CXX) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@ + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) + +dist: $(I18Npo) clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot diff --git a/inputdev.c b/inputdev.c new file mode 100644 index 0000000..ee1e947 --- /dev/null +++ b/inputdev.c @@ -0,0 +1,673 @@ +/* --*- c++ -*-- + * Copyright (C) 2012 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 and/or 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "inputdev.h" + +#include <algorithm> +#define __STDC_FORMAT_MACROS // Required for format specifiers +#include <inttypes.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/epoll.h> +#include <linux/input.h> + +#include <vdr/plugin.h> + + +#define ARRAY_SIZE(_a) (sizeof(_a) / sizeof(_a)[0]) +inline static bool test_bit(unsigned int bit, unsigned long const mask[]) +{ + unsigned long m = mask[bit / (sizeof mask[0] * 8)]; + unsigned int i = bit % (sizeof mask[0] * 8); + + return (m & (1u << i)) != 0u; +} + +class cInputDevice : public cListObject { +private: + cInputDeviceController &controller_; + cString dev_path_; + cString description_; + int fd_; + dev_t dev_t_; + +public: + cInputDevice(cInputDeviceController &controller, + cString const &dev_path); + ~cInputDevice(); + + virtual int Compare(cInputDevice const &b) const { + isyslog("Compare(%s, %s) -> %04lx - %04lx\n", + get_dev_path(), b.get_dev_path(), + dev_t_, b.dev_t_); + return this->dev_t_ - b.dev_t_; + } + + void handle(void); + bool open(void); + bool start(int efd); + void stop(int efd); + int get_fd(void) const { return fd_; } + char const *get_description(void) const { return description_; } + char const *get_dev_path(void) const { return dev_path_; } + + static uint64_t generate_code(uint16_t type, uint16_t code, + uint32_t value); + static void install_keymap(char const *remote); + +}; + +cInputDevice::cInputDevice(cInputDeviceController &controller, + cString const &dev_path) : + controller_(controller), dev_path_(dev_path), fd_(-1) +{ +} + +cInputDevice::~cInputDevice() +{ + controller_.close(fd_); +} + +uint64_t cInputDevice::generate_code(uint16_t type, uint16_t code, + uint32_t value) +{ + uint64_t res = type; + + res <<= 16; + res |= code; + res <<= 32; + res |= value; + + return res; +} + +void cInputDevice::install_keymap(char const *remote) +{ + static struct { + enum eKeys vdr_key; + unsigned int code; + } const MAPPING[] = { + { kUp, KEY_UP }, + { kDown, KEY_DOWN }, + { kMenu, KEY_MENU }, + { kOk, KEY_OK }, + { kBack, KEY_BACKSPACE }, // \todo + { kLeft, KEY_LEFT }, + { kRight, KEY_RIGHT }, + { kRed, KEY_RED }, + { kGreen, KEY_GREEN }, + { kYellow, KEY_YELLOW }, + { kBlue, KEY_BLUE }, + { k0, KEY_KP0 }, + { k1, KEY_KP1 }, + { k2, KEY_KP2 }, + { k3, KEY_KP3 }, + { k4, KEY_KP4 }, + { k5, KEY_KP5 }, + { k6, KEY_KP6 }, + { k7, KEY_KP7 }, + { k8, KEY_KP8 }, + { k9, KEY_KP9 }, + { kInfo, KEY_INFO }, + { kPlayPause, KEY_PLAYPAUSE }, + { kPlay, KEY_PLAY }, + { kPause, KEY_PAUSE }, + { kStop, KEY_STOP }, + { kRecord, KEY_RECORD }, + { kFastFwd, KEY_FORWARD }, // \todo + { kFastRew, KEY_BACK }, // \todo + { kNext, KEY_NEXTSONG }, + { kPrev, KEY_PREVIOUSSONG }, + { kPower, KEY_POWER }, + { kChanUp, KEY_CHANNELUP }, + { kChanDn, KEY_CHANNELDOWN }, + { kChanPrev, KEY_PREVIOUS }, // \todo + { kVolUp, KEY_VOLUMEUP }, + { kVolDn, KEY_VOLUMEDOWN }, + { kMute, KEY_MUTE }, + { kAudio, KEY_AUDIO }, + { kSubtitles, KEY_SUBTITLE }, + { kSchedule, KEY_EPG }, // \todo + { kChannels, KEY_CHANNEL }, + { kTimers, KEY_PROGRAM }, // \todo + { kRecordings, KEY_ARCHIVE }, // \todo + { kSetup, KEY_SETUP }, + { kCommands, KEY_VENDOR }, // \todo + { kUser0, KEY_FN_F10 }, + { kUser1, KEY_FN_F1 }, + { kUser2, KEY_FN_F2 }, + { kUser3, KEY_FN_F3 }, + { kUser4, KEY_FN_F4 }, + { kUser5, KEY_FN_F5 }, + { kUser6, KEY_FN_F6 }, + { kUser7, KEY_FN_F7 }, + { kUser8, KEY_FN_F8 }, + { kUser9, KEY_FN_F9 }, + }; + + size_t i; + + for (i = 0; i < ARRAY_SIZE(MAPPING); ++i) { + uint64_t code = generate_code(0, EV_KEY, MAPPING[i].code); + char buf[17]; + + snprintf(buf, sizeof buf, "%016"PRIX64, code); + Keys.Add(new cKey(remote, buf, MAPPING[i].vdr_key)); + } +} + +bool cInputDevice::open(void) +{ + char const *path = dev_path_; + char description[256]; + int fd; + int rc; + struct stat st; + unsigned long events_mask[(std::max(EV_CNT,KEY_MAX) + + sizeof(unsigned long) * 8 - 1)/ + (sizeof(unsigned long) * 8)]; + + fd = ::open(path, O_RDWR); + if (fd < 0) { + esyslog("%s: open(%s) failed: %s\n", controller_.plugin_name(), + path, strerror(errno)); + goto err; + } + + rc = fstat(fd, &st); + if (rc < 0) { + esyslog("%s: fstat(%s) failed: %s\n", controller_.plugin_name(), + path, strerror(errno)); + goto err; + } + + rc = ioctl(fd, EVIOCGNAME(sizeof description - 1), description); + if (rc < 0) { + esyslog("%s: ioctl(%s, EVIOCGNAME) failed: %s\n", + controller_.plugin_name(), path, strerror(errno)); + goto err; + } + + rc = ioctl(fd, EVIOCGBIT(0, sizeof events_mask), events_mask); + if (rc < 0) { + esyslog("%s: ioctl(%s, EVIOCGBIT) failed: %s\n", + controller_.plugin_name(), path, strerror(errno)); + goto err; + } + + if (!test_bit(EV_KEY, events_mask)) { + isyslog("%s: skipping %s; no key events\n", + controller_.plugin_name(), path); + goto err; + } + + + description[sizeof description - 1] = '\0'; + + this->dev_t_ = st.st_rdev; + this->fd_ = fd; + this->description_ = description; + + return true; + +err: + controller_.close(fd); + return false; +} + +bool cInputDevice::start(int efd) +{ + int rc; + struct epoll_event ev = { }; + char const *dev_path = dev_path_; + + ev.events = EPOLLIN; + ev.data.ptr = this; + + rc = ioctl(fd_, EVIOCGRAB, 1); + if (rc < 0) { + esyslog("%s: ioctl(GRAB, <%s>) failed: %s\n", + controller_.plugin_name(), dev_path, strerror(errno)); + goto err; + } + + rc = epoll_ctl(efd, EPOLL_CTL_ADD, fd_, &ev); + if (rc < 0) { + esyslog("%s: epoll_ctl(ADD, <%s>) failed: %s\n", + controller_.plugin_name(), dev_path, strerror(errno)); + goto err; + } + + return true; + +err: + return false; +} + +void cInputDevice::stop(int efd) +{ + ioctl(fd_, EVIOCGRAB, 0); + epoll_ctl(efd, EPOLL_CTL_DEL, fd_, NULL); +} + +void cInputDevice::handle(void) +{ + struct input_event ev; + ssize_t rc; + + uint64_t code; + bool is_released = false; + bool is_repeated = false; + bool is_valid; + + rc = read(fd_, &ev, sizeof ev); + if (rc < 0 && errno == EINTR) + return; + + if (rc < 0) { + esyslog("%s: failed to read from %s: %s\n", + controller_.plugin_name(), get_dev_path(), + strerror(errno)); + return; + } + + if ((size_t)rc != sizeof ev) { + esyslog("%s: read unexpected amount %zd of data\n", + controller_.plugin_name(), rc); + return; + } + + // \todo: do something useful with EV_SYN... + if (ev.type == EV_SYN || ev.type >= EV_MAX) + // ignore events which are no valid key events + return; + + switch (ev.type) { + case EV_KEY: + is_valid = true; + + switch (ev.value) { + case 0: + is_released = true; + break; + case 1: + break; + case 2: + is_repeated = true; + break; + default: + is_valid = false; + break; + } + + code = generate_code(0, ev.type, ev.code); + break; + + default: + is_valid = false; + break; + } + + if (!is_valid) { + esyslog("%s: unexpected key events [%02x,%04x,%u]\n", + controller_.plugin_name(), ev.type, ev.code, ev.value); + return; + } + + if (!controller_.Put(code, is_repeated, is_released)) { + esyslog("%s: failed to put [%02x,%04x,%u] event\n", + controller_.plugin_name(), ev.type, ev.code, ev.value); + return; + } +} + +// =========================== + +cInputDeviceController::cInputDeviceController(cPlugin &p) + : cRemote("inputdev"), plugin_(p), fd_udev_(-1), fd_epoll_(-1) +{ + SetDescription("inpudev handler"); +} + +cInputDeviceController::~cInputDeviceController(void) +{ + this->close(fd_udev_); + this->close(fd_epoll_); +} + + +char const *cInputDeviceController::plugin_name(void) const +{ + return plugin_.Name(); +} + +void cInputDeviceController::close(int fd) +{ + if (fd != -1) + ::close(fd); +} + +bool cInputDeviceController::open_generic(int fd_udev) +{ + struct epoll_event ev = { }; + int rc; + int fd_epoll = -1; + + if (this->fd_epoll_ != -1) { + esyslog("%s: internal error; epoll fd already open\n", + plugin_.Name()); + goto err; + } + + if (this->fd_udev_ != -1) { + esyslog("%s: internal error; udev fd already open\n", + plugin_.Name()); + goto err; + } + + // requires linux >= 2.6.27 + fd_epoll = epoll_create1(EPOLL_CLOEXEC); + if (fd_epoll < 0) { + esyslog("%s: epoll_create1() failed: %s\n", plugin_.Name(), + strerror(errno)); + goto err; + } + + ev.events = EPOLLIN; + ev.data.ptr = NULL; + + rc = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_udev, &ev); + if (rc < 0) { + esyslog("%s: epoll_ctl(ADD, <udev>) failed: %s\n", + plugin_.Name(), strerror(errno)); + goto err; + } + + this->fd_udev_ = fd_udev; + this->fd_epoll_ = fd_epoll; + + return true; + +err: + this->close(fd_epoll); + return false; +} + +bool cInputDeviceController::open_udev_socket(char const *sock_path) +{ + struct sockaddr_un addr = { AF_UNIX }; + int fd = -1; + int rc; + mode_t old_umask; + + strncpy(addr.sun_path, sock_path, sizeof addr.sun_path); + + /* requires linux >= 2.6.27 */ + rc = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (rc < 0) { + esyslog("%s: socket() failed: %s\n", + plugin_.Name(), strerror(errno)); + goto err; + } + + fd = rc; + + unlink(sock_path); // ignore errors + old_umask = umask(0077); + rc = bind(fd, reinterpret_cast<sockaddr const *>(&addr), sizeof addr); + umask(old_umask); + if (rc < 0) { + esyslog("%s: bind() failed: %s\n", + plugin_.Name(), strerror(errno)); + goto err; + } + + if (!open_generic(fd)) + goto err; + + return true; + +err: + this->close(fd); + return false; +} + +#include <systemd/sd-daemon.h> +bool cInputDeviceController::open_udev_socket(unsigned int systemd_idx) +{ + int rc; + int fd = SD_LISTEN_FDS_START + systemd_idx; + bool is_valid = false; + + rc = sd_is_socket_unix(fd, SOCK_DGRAM, 1, NULL, 0); + + if (rc < 0) + esyslog("%s: failed to check systemd socket: %s\n", + plugin_.Name(), strerror(rc)); + else if (rc == 0) + esyslog("%s: invalid systemd socket\n", plugin_.Name()); + else + is_valid = open_generic(fd); + + return is_valid; +} + +void cInputDeviceController::cleanup_devices(void) +{ + dev_mutex_.Lock(); + while (gc_devices_.Count() > 0) { + class cInputDevice *dev = gc_devices_.First(); + gc_devices_.Del(dev, false); + + dev_mutex_.Unlock(); + delete dev; + dev_mutex_.Lock(); + } + dev_mutex_.Unlock(); +} + +void cInputDeviceController::Action(void) +{ + while (Running()) { + struct epoll_event events[10]; + int rc; + size_t i; + + rc = epoll_wait(fd_epoll_, events, + sizeof events/sizeof events[0], -1); + + if (!Running()) + break; + + if (rc < 0 && errno == EINTR) + continue; + else if (rc < 0) { + esyslog("%s: epoll_wait() failed: %s\n", plugin_.Name(), + strerror(errno)); + break; + } + + for (i = 0; i < (size_t)(rc); ++i) { + unsigned int ev = events[i].events; + class cInputDevice *dev = + static_cast<class cInputDevice *>(events[i].data.ptr); + + if ((ev & (EPOLLHUP|EPOLLIN)) == EPOLLHUP) { + if (dev == NULL) { + esyslog("%s: uevent socket hung up; stopping plugin\n", + plugin_name()); + this->Cancel(-1); + } else { + isyslog("%s: device '%s' (%s) hung up\n", + plugin_name(), + dev->get_dev_path(), + dev->get_description()); + remove_device(dev); + } + } else if (dev == NULL) { + handle_uevent(); + } else { + dev->handle(); + } + } + + cleanup_devices(); + } +} + +void cInputDeviceController::remove_device(char const *dev_path) +{ + class cInputDevice *dev = NULL; + + { + cMutexLock lock(&dev_mutex_); + cInputDevice *i; + + for (i = devices_.First(); i; + i = static_cast<class cInputDevice *>(i->Next())) { + if (i->get_dev_path() == dev_path) { + dev = i; + break; + } + } + + if (dev != NULL) { + dev->stop(fd_epoll_); + + dev->Unlink(); + gc_devices_.Add(dev); + } + } + + if (dev == NULL) { + esyslog("%s: device '%s' not found\n", + plugin_name(), dev_path); + return; + } +} + +void cInputDeviceController::remove_device(class cInputDevice *dev) +{ + dev->stop(fd_epoll_); + + dev_mutex_.Lock(); + dev->Unlink(); + gc_devices_.Add(dev); + dev_mutex_.Unlock(); +} + +void cInputDeviceController::add_device(char const *dev_name) +{ + class cInputDevice *dev = + new cInputDevice(*this, + cString::sprintf("/dev/input/%s", dev_name)); + char const *desc; + + if (!dev->open()) { + delete dev; + return; + } + + desc = dev->get_description(); + + { + cMutexLock lock(&dev_mutex_); + cInputDevice *i; + + for (i = devices_.First(); i; + i = static_cast<class cInputDevice *>(i->Next())) { + if (dev->Compare(*i) == 0) { + dsyslog("%s: device '%s' (%s) already registered\n", + plugin_name(), dev_name, desc); + delete dev; + dev = NULL; + break; + } + } + + if (dev != NULL) { + isyslog("%s: added input device '%s' (%s)\n", + plugin_name(), dev_name, desc); + devices_.Add(dev); + } + } + + if (dev != NULL && !dev->start(fd_epoll_)) + remove_device(dev); +} + +void cInputDeviceController::handle_uevent(void) +{ + char buf[128]; + char cmd[sizeof buf]; + char dev[sizeof buf]; + + ssize_t rc; + + rc = read(fd_udev_, buf, sizeof buf - 1u); + if (rc < 0 && errno == EINTR) + return; + + if (rc < 0) { + esyslog("%s: read(<udev>) failed: %s\n", plugin_.Name(), + strerror(errno)); + return; + } + + if (rc == sizeof buf - 1u) { + esyslog("%s: read(<udev>) received too much data\n", + plugin_.Name()); + return; + } + + buf[rc] = '\0'; + rc = sscanf(buf, "%s %s", cmd, dev); + if (rc != 2) { + esyslog("%s: invalid uevent '%s'\n", plugin_.Name(), buf); + return; + } + + if (strcasecmp(cmd, "add") == 0 || + strcasecmp(cmd, "change") == 0) { + add_device(dev); + } else if (strcasecmp(cmd, "remove") == 0) { + remove_device(dev); + } else { + esyslog("%s: invalid command '%s' for '%s'\n", plugin_name(), + cmd, dev); + return; + } +} + +bool cInputDeviceController::start(void) +{ + cThread::Start(); + return true; +} + +bool cInputDeviceController::initialize(void) +{ + cInputDevice::install_keymap(Name()); + return true; +} + +void cInputDeviceController::stop(void) +{ + Cancel(-1); + this->close(fd_epoll_); + fd_epoll_ = -1; +} diff --git a/inputdev.h b/inputdev.h new file mode 100644 index 0000000..a483601 --- /dev/null +++ b/inputdev.h @@ -0,0 +1,71 @@ +/* --*- c++ -*-- + * Copyright (C) 2012 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 and/or 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef H_ENSC_VDR_INPUTDEV_INPUTDEV_H +#define H_ENSC_VDR_INPUTDEV_INPUTDEV_H + +#include <vdr/remote.h> +#include <vdr/thread.h> + +class cPlugin; + +class cInputDevice; +class cInputDeviceController : protected cRemote, protected cThread +{ +private: + cPlugin &plugin_; + int fd_udev_; + int fd_epoll_; + cList<cInputDevice> devices_; + cList<cInputDevice> gc_devices_; + + cMutex dev_mutex_; + + cInputDeviceController(cInputDeviceController const &); + + bool open_generic(int fd_udev); + void cleanup_devices(void); + + void handle_uevent(void); + +protected: + virtual void Action(void); + +public: + explicit cInputDeviceController(cPlugin &p); + virtual ~cInputDeviceController(); + + bool initialize(void); + bool start(void); + void stop(void); + + char const *plugin_name(void) const; + + bool open_udev_socket(char const *sock_path); + bool open_udev_socket(unsigned int systemd_idx); + + void add_device(char const *dev); + void remove_device(char const *dev); + void remove_device(class cInputDevice *dev); + + static void close(int fd); + + bool Put(uint64_t Code, bool Repeat, bool Release) { + return cRemote::Put(Code, Repeat, Release); + } +}; + +#endif /* H_ENSC_VDR_INPUTDEV_INPUTDEV_H */ diff --git a/plugin.c b/plugin.c new file mode 100644 index 0000000..346d8a9 --- /dev/null +++ b/plugin.c @@ -0,0 +1,157 @@ +/* --*- c++ -*-- + * Copyright (C) 2012 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 and/or 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <getopt.h> +#include <unistd.h> + +#include <vdr/plugin.h> + +#include "inputdev.h" + +static char const *DEFAULT_SOCKET_PATH = SOCKET_PATH; +static const char *VERSION = PACKAGE_VERSION; +static const char *DESCRIPTION = "Linux input device plugin"; + +class cInputDevicePlugin : public cPlugin { +private: + class cInputDeviceController *controller_; + + enum { + enSOCKET, + enSYSTEMD + } socket_type_; + + union { + char const *path; + int idx; + } socket_; + +public: + cInputDevicePlugin(void); + virtual ~cInputDevicePlugin(); + + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void) { return DESCRIPTION; } + + virtual bool ProcessArgs(int argc, char *argv[]); + virtual bool Initialize(void); + virtual bool Start(void); + virtual void Stop(void); +}; + +cInputDevicePlugin::cInputDevicePlugin() : + controller_(NULL) +{ +} + +cInputDevicePlugin::~cInputDevicePlugin(void) +{ + delete controller_; +} + +bool cInputDevicePlugin::ProcessArgs(int argc, char *argv[]) +{ + static struct option const CMDLINE_OPTIONS[] = { + { "systemd", required_argument, NULL, 'S' }, + { "socket", required_argument, NULL, 's' }, + { } + }; + + char const *systemd_idx = NULL; + char const *socket_path = NULL; + bool is_ok = true; + + for (;;) { + int c; + + c = getopt_long(argc, argv, "S:s:", CMDLINE_OPTIONS, NULL); + if (c == -1) + break; + + switch (c) { + case 'S': systemd_idx = optarg; break; + case 's': socket_path = optarg; break; + default: + esyslog("%s: invalid option\n", Name()); + return false; + } + } + + if (systemd_idx != NULL && socket_path != NULL) { + esyslog("%s: both systemd idx and socket path given\n", + Name()); + return false; + } + + if (systemd_idx == NULL && socket_path == NULL) + socket_path = DEFAULT_SOCKET_PATH; + + if (systemd_idx != NULL) { + socket_type_ = enSYSTEMD; + socket_.idx = atoi(systemd_idx); + } else if (socket_path != NULL) { + socket_type_ = enSOCKET; + socket_.path = socket_path; + } else + is_ok = false; + + return is_ok; +} + +bool cInputDevicePlugin::Initialize(void) +{ + bool is_ok; + + controller_ = new cInputDeviceController(*this); + + switch (socket_type_) { + case enSYSTEMD: + is_ok = controller_->open_udev_socket(socket_.idx); + break; + + case enSOCKET: + is_ok = controller_->open_udev_socket(socket_.path); + break; + + default: + esyslog("%s: bad internal socket type %d\n", Name(), + socket_type_); + is_ok = false; + break; + } + + if (is_ok) + is_ok = controller_->initialize(); + + if (!is_ok) { + delete controller_; + controller_ = NULL; + } + + return is_ok; +} + +bool cInputDevicePlugin::Start(void) +{ + return controller_->start(); +} + +void cInputDevicePlugin::Stop(void) +{ + controller_->stop(); +} + +VDRPLUGINCREATOR(cInputDevicePlugin); // Don't touch this! |