From 164386ca42aea42d4643a0209c1a14799bfcfb6c Mon Sep 17 00:00:00 2001 From: Enrico Scholz Date: Sat, 29 Dec 2012 20:28:54 +0100 Subject: refactored build system and renamed c++ sources to .cc --- .gitignore | 3 + Makefile | 128 +++++--- inputdev.c | 974 ------------------------------------------------------------ inputdev.cc | 974 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ plugin.c | 159 ---------- plugin.cc | 159 ++++++++++ 6 files changed, 1218 insertions(+), 1179 deletions(-) delete mode 100644 inputdev.c create mode 100644 inputdev.cc delete mode 100644 plugin.c create mode 100644 plugin.cc diff --git a/.gitignore b/.gitignore index f5c0f08..d644a99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +*.d *.o *.so *.so.1.* /.dependencies +/vdr-inputdev +/vdr-inputdev-*.tar* diff --git a/Makefile b/Makefile index 7088cfc..008833d 100644 --- a/Makefile +++ b/Makefile @@ -3,20 +3,44 @@ 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_CFLAGS = -DSOCKET_PATH=\"${SOCKET_PATH}\" -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 +CXX ?= $(CC) +TAR ?= tar +XZ ?= xz +GZIP ?= gzip +MSGFMT ?= msgfmt +MSGMERGE ?= msgmerge +XGETTEXT ?= xgettext +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") + +TAR_FLAGS = --owner root --group root --mode a+rX,go-w + +AM_CPPFLAGS = -DPACKAGE_VERSION=\"${VERSION}\" -DSOCKET_PATH=\"${SOCKET_PATH}\" \ + -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +ifneq ($(USE_SYSTEMD),) +AM_CPPFLAGS += -DVDR_USE_SYSTEMD +AM_CXXFLAGS += ${SYSTEMD_CFLAGS} +LIBS += ${SYSTEMD_LIBS} +endif + +plugin_SOURCES = \ + inputdev.cc \ + inputdev.h \ + plugin.cc \ + +helper_SOURCES = \ + udevhelper.c + +extra_SOURCES = \ + Makefile \ + README.txt \ + contrib/96-vdrkeymap.rules \ + contrib/hama-mce \ + contrib/tt6400-ir \ + contrib/x10-wti ### The directory environment: @@ -24,6 +48,8 @@ VDRDIR ?= ../../.. LIBDIR ?= ../../lib TMPDIR ?= /tmp +SOCKET_PATH = /var/run/vdr/inputdev + ### Allow user defined options to overwrite defaults: include $(VDRDIR)/Make.global @@ -36,35 +62,49 @@ APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDI ### The name of the distribution archive: ARCHIVE = $(PLUGIN)-$(VERSION) -PACKAGE = vdr-$(ARCHIVE) +PACKAGE = vdr-$(ARCHIVE).tar -### Includes and Defines (add further entries here): +### The object files (add further files here): -INCLUDES += -I$(VDRDIR)/include +_all_sources = $(plugin_SOURCES) $(helper_SOURCES) $(extra_SOURCES) -DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' +_objects = \ + $(patsubst %.c,%.o,$(filter %.c,$(plugin_SOURCES))) \ + $(patsubst %.cc,%.o,$(filter %.cc,$(plugin_SOURCES))) -### The object files (add further files here): +plugin_OBJS = $(call _objects,$(plugin_SOURCES)) +helper_OBJS = $(call _objects,$(helper_SOURCES)) -OBJS = plugin.o inputdev.o +OBJS = $(plugin_OBJS) $(helper_OBJS) ### The main target: all: libvdr-$(PLUGIN).so vdr-inputdev i18n ### Implicit rules: +_buildflags = $(foreach k,CPP $1 LD, $(AM_$kFLAGS) $($kFLAGS) $($kFLAGS_$@)) %.o: %.c - $(CXX) $(AM_CXXFLAGS) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + $(CC) $(call _buildflags,C) -MMD -MP -c $< -o $@ -### Dependencies: +%.o: %.cc + $(CC) $(call _buildflags,CXX) -MMD -MP -c $< -o $@ -MAKEDEP = $(CXX) -MM -MG -DEPFILE = .dependencies -$(DEPFILE): Makefile - @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ +%.xz: % + @rm -f $@.tmp $@ + $(XZ) -c < $< >$@.tmp + @mv $@.tmp $@ --include $(DEPFILE) +%.gz: % + @rm -f $@.tmp $@ + $(GZIP) -c < $< >$@.tmp + @mv $@.tmp $@ + +%.mo: %.po + $(MSGFMT) -c -o $@ $< + + +-include $(OBJS:%.o=%.d) ### Internationalization (I18N): @@ -74,14 +114,11 @@ 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='' -o $@ $^ +$(I18Npot): $(wildcard *.c *.cc) + $(XGETTEXT) -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='' -o $@ $^ %.po: $(I18Npot) - msgmerge -U --no-wrap --no-location --backup=none -q $@ $< + $(MSGMERGE) -U --no-wrap --no-location --backup=none -q $@ $< @touch $@ $(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo @@ -92,21 +129,20 @@ $(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo i18n: $(I18Nmsgs) $(I18Npot) ### Targets: +vdr-inputdev: $(helper_SOURCES) + $(CC) $(call _buildflags,C) $^ -o $@ -vdr-inputdev: udevhelper.c - $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) $^ -o $@ - -libvdr-$(PLUGIN).so: $(OBJS) - $(CXX) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@ +libvdr-$(PLUGIN).so: $(plugin_OBJS) + $(CXX) $(AM_LDFLAGS) $(LDFLAGS) $(LDFLAGS_$@) -shared -o $@ $^ $(LIBS) @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 +dist: $(PACKAGE).xz $(PACKAGE).gz + +_tar_transform = --transform='s!^!$(ARCHIVE)/!' + +$(PACKAGE): $(I18Npo) $(_all_sources) + $(TAR) cf $@ $(TAR_FLAGS) $(_tar_transform) $(sort $^) clean: - @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot + @rm -f $(OBJS) libvdr*.so libvdr*.so.* *.d *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot + @rm -f vdr-inputdev diff --git a/inputdev.c b/inputdev.c deleted file mode 100644 index e31fb8a..0000000 --- a/inputdev.c +++ /dev/null @@ -1,974 +0,0 @@ -/* --*- c++ -*-- - * Copyright (C) 2012 Enrico Scholz - * - * 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 . - */ - -#include "inputdev.h" - -#include -#include -#define __STDC_FORMAT_MACROS // Required for format specifiers -#include -#include -#include -#include -#include -#include - -#include - - -#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; -} - -inline static void set_bit(unsigned int bit, unsigned long mask[]) -{ - unsigned int i = bit % (sizeof mask[0] * 8); - - mask[bit / (sizeof mask[0] * 8)] |= (1u << i); -} - -class MagicState { -private: - static bool const IS_SUPPORTED_; - - unsigned int state_; - struct timespec next_; - - static int compare(struct timespec const &a, - struct timespec const &b); - - static void add(struct timespec &res, - struct timespec const &a, - struct timespec const &b); - -public: - static struct timespec const TIMEOUT; - - MagicState() : state_(0) {} - bool process(struct input_event const &ev); - -}; - -static bool check_clock_gettime(void) -{ - struct timespec tmp; - - if (clock_gettime(CLOCK_MONOTONIC, &tmp) < 0) { - fprintf(stderr, - "clock_gettime() not available; magic keysequences will not be available: %s\n", - strerror(errno)); - return false; - } - - return true; -} - -bool const MagicState::IS_SUPPORTED_ = check_clock_gettime(); -struct timespec const MagicState::TIMEOUT = { 1, 500000000 }; - -int MagicState::compare(struct timespec const &a, struct timespec const &b) -{ - if (a.tv_sec < b.tv_sec) - return -1; - else if (a.tv_sec > b.tv_sec) - return + 1; - else if (a.tv_nsec < b.tv_nsec) - return -1; - else if (a.tv_nsec > b.tv_nsec) - return +1; - else - return 0; -} - -void MagicState::add(struct timespec &res, - struct timespec const &a, struct timespec const &b) -{ - assert(a.tv_nsec < 1000000000); - assert(b.tv_nsec < 1000000000); - - res.tv_sec = a.tv_sec + b.tv_sec; - res.tv_nsec = a.tv_nsec + b.tv_nsec; - - if (res.tv_nsec >= 1000000000) { - res.tv_nsec -= 1000000000; - res.tv_sec += 1; - } -} - -bool MagicState::process(struct input_event const &ev) -{ - static unsigned int const SEQUENCE[] = { - KEY_LEFTSHIFT, - KEY_LEFTSHIFT, - KEY_ESC, - KEY_LEFTSHIFT, - }; - - unsigned int code; - - assert(state_ < ARRAY_SIZE(SEQUENCE)); - - if (!IS_SUPPORTED_) - return false; - - if (ev.type != EV_KEY || ev.value != 1) - // ignore non-key events and skip release- and repeat events - return false; - - // translate some keys - switch (ev.code) { - case KEY_RIGHTSHIFT: code = KEY_LEFTSHIFT; break; - default: code = ev.code; - } - - if (state_ > 0) { - struct timespec now; - - clock_gettime(CLOCK_MONOTONIC, &now); - if (compare(next_, now) < 0) - // reset state due to timeout - state_ = 0; - - add(next_, now, TIMEOUT); - } - - if (SEQUENCE[state_] == code) - ++state_; - else - state_ = 0; - - if (state_ == ARRAY_SIZE(SEQUENCE)) { - state_ = 0; - return true; - } - - return false; -} - - -class cInputDevice : public cListObject { -public: - enum modifier { - modSHIFT, - modCONTROL, - modALT, - modMETA, - }; - -private: - cInputDeviceController &controller_; - cString dev_path_; - cString description_; - int fd_; - dev_t dev_t_; - class MagicState magic_state_; - - unsigned long modifiers_; - -public: - // the vdr list implementation requires knowledge about the containing - // list when unlinking a object :( - enum { - lstNONE, - lstDEVICES, - lstGC - } container; - - cInputDevice(cInputDeviceController &controller, - cString const &dev_path); - ~cInputDevice(); - - virtual int Compare(cInputDevice const &b) const { - return this->dev_t_ - b.dev_t_; - } - - virtual int Compare(dev_t b) const { - return this->dev_t_ - b; - } - - 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_; } - - void dump(void) const; - - 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_); -} - -void cInputDevice::dump(void) const -{ - dsyslog("%s: %lx %s (%s), fd=%d\n", controller_.plugin_name(), - static_cast(dev_t_), - get_dev_path(), get_description(), get_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_EXIT }, // \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 }, - { kFastRew, KEY_REWIND }, - { 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_OPTION }, // \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; - bool is_internal = false; - - rc = read(fd_, &ev, sizeof ev); - if (rc < 0 && errno == EINTR) - return; - - if (rc < 0 && errno == ENODEV) { - isyslog("%s: device '%s' removed\n", - controller_.plugin_name(), get_dev_path()); - controller_.remove_device(this); - 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 the other events... - if (ev.type != EV_KEY) - // ignore events which are no valid key events - return; - - if (0) - dsyslog("%s: event{%s}=[%lu.%06u, %02x, %04x, %d]\n", - controller_.plugin_name(), get_dev_path(), - (unsigned long)(ev.time.tv_sec), - (unsigned int)(ev.time.tv_usec), - ev.type, ev.code, ev.value); - - if (magic_state_.process(ev)) { - isyslog("%s: magic keysequence from %s; detaching device\n", - controller_.plugin_name(), get_dev_path()); - controller_.remove_device(this); - return; - } - - switch (ev.type) { - case EV_KEY: { - unsigned long mask = 0; - - 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; - } - - switch (ev.code) { - case KEY_LEFTSHIFT: - case KEY_RIGHTSHIFT: - set_bit(modSHIFT, &mask); - break; - - case KEY_LEFTCTRL: - case KEY_RIGHTCTRL: - set_bit(modCONTROL, &mask); - break; - - case KEY_LEFTALT: - case KEY_RIGHTALT: - set_bit(modALT, &mask); - break; - - case KEY_LEFTMETA: - case KEY_RIGHTMETA: - set_bit(modMETA, &mask); - break; - default: - break; - } - - if (mask == 0) { - code = generate_code(0, ev.type, ev.code); - } else if (is_released) { - this->modifiers_ &= ~mask; - is_internal = true; - } else if (is_valid) { - this->modifiers_ |= mask; - is_internal = true; - } else { - // repeated events - is_internal = true; - } - - break; - } - - default: - is_valid = false; - break; - } - - if (is_internal) - return; - - 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, ) 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(&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 -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(); - - assert(dev->container == cInputDevice::lstGC); - 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(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; - struct stat st; - - if (stat(dev_path, &st) < 0) { - esyslog("%s: fstat(%s) failed: %s\n", plugin_name(), - dev_path, strerror(errno)); - } else { - cMutexLock lock(&dev_mutex_); - - for (cInputDevice *i = devices_.First(); - i != NULL && dev == NULL; - i = devices_.Next(i)) { - if (i->Compare(st.st_rdev) == 0) - dev = i; - } - - if (dev != NULL) { - dev->stop(fd_epoll_); - - assert(dev->container == cInputDevice::lstDEVICES); - devices_.Del(dev, false); - - gc_devices_.Add(dev); - dev->container = cInputDevice::lstGC; - } - } - - 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(); - switch (dev->container) { - case cInputDevice::lstDEVICES: - devices_.Del(dev, false); - break; - case cInputDevice::lstGC: - gc_devices_.Del(dev, false); - break; - case cInputDevice::lstNONE: - break; - } - - gc_devices_.Add(dev); - dev->container = cInputDevice::lstGC; - - dev_mutex_.Unlock(); -} - -bool cInputDeviceController::add_device(char const *dev_name) -{ - class cInputDevice *dev = - new cInputDevice(*this, - cString::sprintf("/dev/input/%s", dev_name)); - char const *desc; - bool res; - - if (!dev->open()) { - delete dev; - return false; - } - - desc = dev->get_description(); - - { - cMutexLock lock(&dev_mutex_); - - for (cInputDevice *i = devices_.First(); i; - i = devices_.Next(i)) { - 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); - dev->container = cInputDevice::lstDEVICES; - } - } - - if (dev != NULL && !dev->start(fd_epoll_)) { - res = false; - remove_device(dev); - } else { - res = dev != NULL; - } - - return res; -} - -void cInputDeviceController::dump_active_devices(void) -{ - cMutexLock lock(&dev_mutex_); - - dsyslog("%s: active devices:\n", plugin_name()); - for (cInputDevice *i = devices_.First(); i; i = devices_.Next(i)) - i->dump(); -} - -void cInputDeviceController::dump_gc_devices(void) -{ - cMutexLock lock(&dev_mutex_); - - dsyslog("%s: gc devices:\n", plugin_name()); - for (cInputDevice *i = gc_devices_.First(); i; i = gc_devices_.Next(i)) - i->dump(); -} - -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() failed: %s\n", plugin_.Name(), - strerror(errno)); - return; - } - - if (rc == sizeof buf - 1u) { - esyslog("%s: read() 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 if (strcasecmp(cmd, "dump") == 0) { - bool is_all = strcasecmp(dev, "all") == 0; - if (is_all || strcasecmp(dev, "active") == 0) - dump_active_devices(); - - if (is_all || strcasecmp(dev, "gc") == 0) - dump_gc_devices(); - } else { - esyslog("%s: invalid command '%s' for '%s'\n", plugin_name(), - cmd, dev); - return; - } -} - -bool cInputDeviceController::coldplug_devices(char const *path) -{ - cReadDir cdir(path); - bool res = true; - - for (;;) { - struct dirent const *ent = cdir.Next(); - - if (!ent) - break; - - if (strcmp(ent->d_name, ".") == 0 || - strcmp(ent->d_name, "..") == 0) - continue; - - if (!add_device(ent->d_name)) { - esyslog("%s: failed to coldplug '%s'\n", - plugin_name(), ent->d_name); - res = false; - } else { - isyslog("%s: coldplugged '%s'\n", - plugin_name(), ent->d_name); - } - } - - return res; -} - -bool cInputDeviceController::initialize(char const *coldplug_dir) -{ - cInputDevice::install_keymap(Name()); - - coldplug_devices(coldplug_dir); - - return true; -} - -bool cInputDeviceController::start(void) -{ - cThread::Start(); - return true; -} - -void cInputDeviceController::stop(void) -{ - Cancel(-1); - this->close(fd_epoll_); - fd_epoll_ = -1; -} diff --git a/inputdev.cc b/inputdev.cc new file mode 100644 index 0000000..e31fb8a --- /dev/null +++ b/inputdev.cc @@ -0,0 +1,974 @@ +/* --*- c++ -*-- + * Copyright (C) 2012 Enrico Scholz + * + * 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 . + */ + +#include "inputdev.h" + +#include +#include +#define __STDC_FORMAT_MACROS // Required for format specifiers +#include +#include +#include +#include +#include +#include + +#include + + +#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; +} + +inline static void set_bit(unsigned int bit, unsigned long mask[]) +{ + unsigned int i = bit % (sizeof mask[0] * 8); + + mask[bit / (sizeof mask[0] * 8)] |= (1u << i); +} + +class MagicState { +private: + static bool const IS_SUPPORTED_; + + unsigned int state_; + struct timespec next_; + + static int compare(struct timespec const &a, + struct timespec const &b); + + static void add(struct timespec &res, + struct timespec const &a, + struct timespec const &b); + +public: + static struct timespec const TIMEOUT; + + MagicState() : state_(0) {} + bool process(struct input_event const &ev); + +}; + +static bool check_clock_gettime(void) +{ + struct timespec tmp; + + if (clock_gettime(CLOCK_MONOTONIC, &tmp) < 0) { + fprintf(stderr, + "clock_gettime() not available; magic keysequences will not be available: %s\n", + strerror(errno)); + return false; + } + + return true; +} + +bool const MagicState::IS_SUPPORTED_ = check_clock_gettime(); +struct timespec const MagicState::TIMEOUT = { 1, 500000000 }; + +int MagicState::compare(struct timespec const &a, struct timespec const &b) +{ + if (a.tv_sec < b.tv_sec) + return -1; + else if (a.tv_sec > b.tv_sec) + return + 1; + else if (a.tv_nsec < b.tv_nsec) + return -1; + else if (a.tv_nsec > b.tv_nsec) + return +1; + else + return 0; +} + +void MagicState::add(struct timespec &res, + struct timespec const &a, struct timespec const &b) +{ + assert(a.tv_nsec < 1000000000); + assert(b.tv_nsec < 1000000000); + + res.tv_sec = a.tv_sec + b.tv_sec; + res.tv_nsec = a.tv_nsec + b.tv_nsec; + + if (res.tv_nsec >= 1000000000) { + res.tv_nsec -= 1000000000; + res.tv_sec += 1; + } +} + +bool MagicState::process(struct input_event const &ev) +{ + static unsigned int const SEQUENCE[] = { + KEY_LEFTSHIFT, + KEY_LEFTSHIFT, + KEY_ESC, + KEY_LEFTSHIFT, + }; + + unsigned int code; + + assert(state_ < ARRAY_SIZE(SEQUENCE)); + + if (!IS_SUPPORTED_) + return false; + + if (ev.type != EV_KEY || ev.value != 1) + // ignore non-key events and skip release- and repeat events + return false; + + // translate some keys + switch (ev.code) { + case KEY_RIGHTSHIFT: code = KEY_LEFTSHIFT; break; + default: code = ev.code; + } + + if (state_ > 0) { + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + if (compare(next_, now) < 0) + // reset state due to timeout + state_ = 0; + + add(next_, now, TIMEOUT); + } + + if (SEQUENCE[state_] == code) + ++state_; + else + state_ = 0; + + if (state_ == ARRAY_SIZE(SEQUENCE)) { + state_ = 0; + return true; + } + + return false; +} + + +class cInputDevice : public cListObject { +public: + enum modifier { + modSHIFT, + modCONTROL, + modALT, + modMETA, + }; + +private: + cInputDeviceController &controller_; + cString dev_path_; + cString description_; + int fd_; + dev_t dev_t_; + class MagicState magic_state_; + + unsigned long modifiers_; + +public: + // the vdr list implementation requires knowledge about the containing + // list when unlinking a object :( + enum { + lstNONE, + lstDEVICES, + lstGC + } container; + + cInputDevice(cInputDeviceController &controller, + cString const &dev_path); + ~cInputDevice(); + + virtual int Compare(cInputDevice const &b) const { + return this->dev_t_ - b.dev_t_; + } + + virtual int Compare(dev_t b) const { + return this->dev_t_ - b; + } + + 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_; } + + void dump(void) const; + + 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_); +} + +void cInputDevice::dump(void) const +{ + dsyslog("%s: %lx %s (%s), fd=%d\n", controller_.plugin_name(), + static_cast(dev_t_), + get_dev_path(), get_description(), get_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_EXIT }, // \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 }, + { kFastRew, KEY_REWIND }, + { 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_OPTION }, // \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; + bool is_internal = false; + + rc = read(fd_, &ev, sizeof ev); + if (rc < 0 && errno == EINTR) + return; + + if (rc < 0 && errno == ENODEV) { + isyslog("%s: device '%s' removed\n", + controller_.plugin_name(), get_dev_path()); + controller_.remove_device(this); + 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 the other events... + if (ev.type != EV_KEY) + // ignore events which are no valid key events + return; + + if (0) + dsyslog("%s: event{%s}=[%lu.%06u, %02x, %04x, %d]\n", + controller_.plugin_name(), get_dev_path(), + (unsigned long)(ev.time.tv_sec), + (unsigned int)(ev.time.tv_usec), + ev.type, ev.code, ev.value); + + if (magic_state_.process(ev)) { + isyslog("%s: magic keysequence from %s; detaching device\n", + controller_.plugin_name(), get_dev_path()); + controller_.remove_device(this); + return; + } + + switch (ev.type) { + case EV_KEY: { + unsigned long mask = 0; + + 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; + } + + switch (ev.code) { + case KEY_LEFTSHIFT: + case KEY_RIGHTSHIFT: + set_bit(modSHIFT, &mask); + break; + + case KEY_LEFTCTRL: + case KEY_RIGHTCTRL: + set_bit(modCONTROL, &mask); + break; + + case KEY_LEFTALT: + case KEY_RIGHTALT: + set_bit(modALT, &mask); + break; + + case KEY_LEFTMETA: + case KEY_RIGHTMETA: + set_bit(modMETA, &mask); + break; + default: + break; + } + + if (mask == 0) { + code = generate_code(0, ev.type, ev.code); + } else if (is_released) { + this->modifiers_ &= ~mask; + is_internal = true; + } else if (is_valid) { + this->modifiers_ |= mask; + is_internal = true; + } else { + // repeated events + is_internal = true; + } + + break; + } + + default: + is_valid = false; + break; + } + + if (is_internal) + return; + + 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, ) 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(&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 +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(); + + assert(dev->container == cInputDevice::lstGC); + 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(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; + struct stat st; + + if (stat(dev_path, &st) < 0) { + esyslog("%s: fstat(%s) failed: %s\n", plugin_name(), + dev_path, strerror(errno)); + } else { + cMutexLock lock(&dev_mutex_); + + for (cInputDevice *i = devices_.First(); + i != NULL && dev == NULL; + i = devices_.Next(i)) { + if (i->Compare(st.st_rdev) == 0) + dev = i; + } + + if (dev != NULL) { + dev->stop(fd_epoll_); + + assert(dev->container == cInputDevice::lstDEVICES); + devices_.Del(dev, false); + + gc_devices_.Add(dev); + dev->container = cInputDevice::lstGC; + } + } + + 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(); + switch (dev->container) { + case cInputDevice::lstDEVICES: + devices_.Del(dev, false); + break; + case cInputDevice::lstGC: + gc_devices_.Del(dev, false); + break; + case cInputDevice::lstNONE: + break; + } + + gc_devices_.Add(dev); + dev->container = cInputDevice::lstGC; + + dev_mutex_.Unlock(); +} + +bool cInputDeviceController::add_device(char const *dev_name) +{ + class cInputDevice *dev = + new cInputDevice(*this, + cString::sprintf("/dev/input/%s", dev_name)); + char const *desc; + bool res; + + if (!dev->open()) { + delete dev; + return false; + } + + desc = dev->get_description(); + + { + cMutexLock lock(&dev_mutex_); + + for (cInputDevice *i = devices_.First(); i; + i = devices_.Next(i)) { + 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); + dev->container = cInputDevice::lstDEVICES; + } + } + + if (dev != NULL && !dev->start(fd_epoll_)) { + res = false; + remove_device(dev); + } else { + res = dev != NULL; + } + + return res; +} + +void cInputDeviceController::dump_active_devices(void) +{ + cMutexLock lock(&dev_mutex_); + + dsyslog("%s: active devices:\n", plugin_name()); + for (cInputDevice *i = devices_.First(); i; i = devices_.Next(i)) + i->dump(); +} + +void cInputDeviceController::dump_gc_devices(void) +{ + cMutexLock lock(&dev_mutex_); + + dsyslog("%s: gc devices:\n", plugin_name()); + for (cInputDevice *i = gc_devices_.First(); i; i = gc_devices_.Next(i)) + i->dump(); +} + +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() failed: %s\n", plugin_.Name(), + strerror(errno)); + return; + } + + if (rc == sizeof buf - 1u) { + esyslog("%s: read() 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 if (strcasecmp(cmd, "dump") == 0) { + bool is_all = strcasecmp(dev, "all") == 0; + if (is_all || strcasecmp(dev, "active") == 0) + dump_active_devices(); + + if (is_all || strcasecmp(dev, "gc") == 0) + dump_gc_devices(); + } else { + esyslog("%s: invalid command '%s' for '%s'\n", plugin_name(), + cmd, dev); + return; + } +} + +bool cInputDeviceController::coldplug_devices(char const *path) +{ + cReadDir cdir(path); + bool res = true; + + for (;;) { + struct dirent const *ent = cdir.Next(); + + if (!ent) + break; + + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) + continue; + + if (!add_device(ent->d_name)) { + esyslog("%s: failed to coldplug '%s'\n", + plugin_name(), ent->d_name); + res = false; + } else { + isyslog("%s: coldplugged '%s'\n", + plugin_name(), ent->d_name); + } + } + + return res; +} + +bool cInputDeviceController::initialize(char const *coldplug_dir) +{ + cInputDevice::install_keymap(Name()); + + coldplug_devices(coldplug_dir); + + return true; +} + +bool cInputDeviceController::start(void) +{ + cThread::Start(); + return true; +} + +void cInputDeviceController::stop(void) +{ + Cancel(-1); + this->close(fd_epoll_); + fd_epoll_ = -1; +} diff --git a/plugin.c b/plugin.c deleted file mode 100644 index 8e769b0..0000000 --- a/plugin.c +++ /dev/null @@ -1,159 +0,0 @@ -/* --*- c++ -*-- - * Copyright (C) 2012 Enrico Scholz - * - * 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 . - */ - -#include -#include - -#include - -#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_; - - cString coldplug_dir; - -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), coldplug_dir("/dev/vdr/input") -{ -} - -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(coldplug_dir); - - 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! diff --git a/plugin.cc b/plugin.cc new file mode 100644 index 0000000..8e769b0 --- /dev/null +++ b/plugin.cc @@ -0,0 +1,159 @@ +/* --*- c++ -*-- + * Copyright (C) 2012 Enrico Scholz + * + * 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 . + */ + +#include +#include + +#include + +#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_; + + cString coldplug_dir; + +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), coldplug_dir("/dev/vdr/input") +{ +} + +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(coldplug_dir); + + 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! -- cgit v1.2.3