diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | Makefile | 32 | ||||
-rw-r--r-- | inputdev.cc | 69 | ||||
-rw-r--r-- | inputdev.h | 15 | ||||
-rw-r--r-- | modmap.cc | 234 | ||||
-rw-r--r-- | modmap.h | 63 | ||||
-rw-r--r-- | plugin.cc | 13 |
7 files changed, 403 insertions, 29 deletions
@@ -3,7 +3,9 @@ *.so *.so.1.* /.dependencies -/vdr-inputdev -/vdr-inputdev-*.tar* +/gen-keymap.h +/gen-keymap.perf /po/*.mo /po/inputdev.pot +/vdr-inputdev +/vdr-inputdev-*.tar* @@ -5,6 +5,8 @@ plugin_SOURCES = \ inputdev.cc \ inputdev.h \ plugin.cc \ + modmap.cc \ + modmap.h helper_SOURCES = \ udevhelper.c @@ -33,6 +35,8 @@ MSGFMT ?= msgfmt MSGMERGE ?= msgmerge XGETTEXT ?= xgettext PKG_CONFIG ?= pkg-config +GPERF ?= gperf +SED ?= sed INSTALL ?= install INSTALL_DATA ?= $(INSTALL) -p -m 0644 @@ -41,6 +45,8 @@ INSTALL_BIN ?= $(INSTALL) -p -m 0755 MKDIR_P ?= $(INSTALL) -d -m 0755 TOUCH_C ?= touch -c +GPERF_FLAGS = -L C++ --readonly-tables --enum --ignore-case + SYSTEMD_CFLAGS = $(shell ${PKG_CONFIG} --cflags libsystemd-daemon || echo "libsystemd_missing") SYSTEMD_LIBS = $(shell ${PKG_CONFIG} --libs libsystemd-daemon || echo "libsystemd_missing") @@ -171,6 +177,8 @@ $(POTFILE): $(plugin_SOURCES) vdr-inputdev: $(helper_SOURCES) $(CC) $(call _buildflags,C) $^ -o $@ +modmap.o: gen-keymap.h + $(vdr_PLUGINS): $(plugin_OBJS) $(CXX) $(AM_LDFLAGS) $(LDFLAGS) $(LDFLAGS_$@) -shared -o $@ $^ $(LIBS) @@ -180,7 +188,7 @@ dist: $(_packages) $(addsuffix .asc,$(_packages)) _tar_transform = --transform='s!^!$(ARCHIVE)/!' -$(PACKAGE): $(I18Npo) $(_all_sources) +$(PACKAGE): $(_po_files) $(_all_sources) $(TAR) cf $@ $(TAR_FLAGS) $(_tar_transform) $(sort $^) %.asc: % @@ -200,3 +208,25 @@ install-extra: vdr-inputdev | $(DESTDIR)$(udevdir) clean: @rm -f $(OBJS) libvdr*.so libvdr*.so.* *.d *.tgz core* *~ po/*.mo po/*.pot @rm -f vdr-inputdev + +### + +KEY_BLACKLIST = RESERVED\|MAX\|CNT + +KEYMAP_SED = \ + -e '/^\#define KEY_\($(KEY_BLACKLIST)\)$$/d' \ + -e '/^\#define KEY_/{' \ + -e 's/^\#define KEY_\([A-Za-z0-9_]\+\)[[:space:]]*$$/\L\1\E,KEY_\1/p' \ + -e '};d' + +gen-keymap.h: gen-keymap.perf + $(GPERF) $(GPERF_FLAGS) -t $< --output-file=$@ + +gen-keymap.perf: Makefile + rm -f $@ $@.tmp + @echo '%readonly-tables' > $@.tmp + @echo 'struct keymap_def { char const *name; unsigned int num; };' >> $@.tmp + @echo '%%' >> $@.tmp + @$(CC) $(call _buildflags,C) -imacros 'linux/input.h' -E -dN - </dev/null | \ + $(SED) $(KEYMAP_SED) | sort -n >>$@.tmp + @mv $@.tmp $@ diff --git a/inputdev.cc b/inputdev.cc index b7c9399..70d883b 100644 --- a/inputdev.cc +++ b/inputdev.cc @@ -28,6 +28,7 @@ #include <vdr/plugin.h> +#include "modmap.h" #include "util.h" class MagicState { @@ -146,15 +147,6 @@ bool MagicState::process(struct input_event const &ev) } class cInputDevice : public cListObject, public cEpollHandler { -public: - enum modifier { - modSHIFT, - modCONTROL, - modALT, - modMETA, - modNUMLOCK, - }; - private: cInputDeviceController &controller_; cString dev_path_; @@ -182,6 +174,10 @@ public: cString const &dev_path); virtual ~cInputDevice(); + virtual int Compare(cListObject const &b) const { + return Compare(dynamic_cast<cInputDevice const &>(b)); + } + virtual int Compare(cInputDevice const &b) const { return this->dev_t_ - b.dev_t_; } @@ -435,6 +431,11 @@ void cInputDevice::handle_hup(void) controller_.remove_device(this); } +static uint64_t wchar_t_to_ekey(wchar_t c) +{ + return KBDKEY(c); +} + void cInputDevice::handle_pollin(void) { struct input_event ev; @@ -445,6 +446,7 @@ void cInputDevice::handle_pollin(void) bool is_repeated = false; bool is_valid; bool is_internal = false; + bool is_raw = false; rc = read(fd_, &ev, sizeof ev); if (rc < 0 && errno == EINTR) @@ -492,7 +494,8 @@ void cInputDevice::handle_pollin(void) switch (ev.type) { case EV_KEY: { unsigned long mask = 0; - + wchar_t c; + is_valid = true; switch (ev.value) { @@ -512,26 +515,26 @@ void cInputDevice::handle_pollin(void) switch (ev.code) { case KEY_LEFTSHIFT: case KEY_RIGHTSHIFT: - set_bit(modSHIFT, &mask); + set_bit(ModifierMap::modSHIFT, &mask); break; case KEY_LEFTCTRL: case KEY_RIGHTCTRL: - set_bit(modCONTROL, &mask); + set_bit(ModifierMap::modCONTROL, &mask); break; case KEY_LEFTALT: case KEY_RIGHTALT: - set_bit(modALT, &mask); + set_bit(ModifierMap::modALT, &mask); break; case KEY_LEFTMETA: case KEY_RIGHTMETA: - set_bit(modMETA, &mask); + set_bit(ModifierMap::modMETA, &mask); break; case KEY_NUMLOCK: - set_bit(modNUMLOCK, &mask); + set_bit(ModifierMap::modNUMLOCK, &mask); break; default: @@ -539,7 +542,7 @@ void cInputDevice::handle_pollin(void) } if (mask == 0) { - code = generate_code(0, ev.type, ev.code); + is_internal = false; } else if (is_released) { this->modifiers_ &= ~mask; is_internal = true; @@ -551,6 +554,17 @@ void cInputDevice::handle_pollin(void) is_internal = true; } + if (is_internal) { + ; // noop + } else if (controller_.get_modmap().translate( + c, ev.code, this->modifiers_)) { + code = wchar_t_to_ekey(c); + is_raw = true; + } else { + code = generate_code(0, ev.type, ev.code); + is_raw = false; + } + break; } @@ -568,9 +582,17 @@ void cInputDevice::handle_pollin(void) 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); + if (is_raw && !controller_.PutRaw(code, is_repeated, is_released)) + rc = -1; + else if (!is_raw && controller_.Put(code, is_repeated, is_released)) + rc = -1; + else + rc = 0; + + if (rc < 0) { + esyslog("%s: failed to put [%02x,%04x,%u] event [%016" PRIX64 ", %d, %d\n", + controller_.plugin_name(), ev.type, ev.code, ev.value, + code, is_repeated, is_released); return; } } @@ -600,8 +622,9 @@ bool cInputDevice::set_repeat_rate(unsigned int delay_ms, // =========================== -cInputDeviceController::cInputDeviceController(cPlugin &p) - : cRemote("inputdev"), plugin_(p), fd_udev_(-1), fd_epoll_(-1), +cInputDeviceController::cInputDeviceController(cPlugin &p, ModifierMap &mod_map) + : cRemote("inputdev"), plugin_(p), mod_map_(mod_map), + fd_udev_(-1), fd_epoll_(-1), repeat_delay_ms_(250), repeat_rate_ms_(100) { fd_alive_[0] = -1; @@ -718,8 +741,8 @@ bool cInputDeviceController::open_udev_socket(char const *sock_path) 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)); + esyslog("%s: bind(%s) failed: %s\n", + plugin_.Name(), sock_path, strerror(errno)); goto err; } @@ -28,6 +28,7 @@ public: virtual void handle_pollin() = 0; }; +class ModifierMap; class cPlugin; class cInputDevice; class cInputDeviceController : protected cRemote, protected cThread, @@ -35,6 +36,7 @@ class cInputDeviceController : protected cRemote, protected cThread, { private: cPlugin &plugin_; + ModifierMap &mod_map_; int fd_udev_; int fd_epoll_; int fd_alive_[2]; @@ -63,7 +65,7 @@ protected: virtual void handle_pollin(); public: - explicit cInputDeviceController(cPlugin &p); + explicit cInputDeviceController(cPlugin &p, ModifierMap &modmap); virtual ~cInputDeviceController(); bool initialize(char const *coldplug_dir); @@ -82,11 +84,22 @@ public: bool set_repeat_rate(unsigned int delay_ms, unsigned int rate_ms); + ModifierMap const &get_modmap() const { return mod_map_; } + static void close(int &fd); bool Put(uint64_t Code, bool Repeat, bool Release) { return cRemote::Put(Code, Repeat, Release); } + + bool PutRaw(uint64_t Code, bool Repeat, bool Release) { + if (Repeat) + Code |= k_Repeat; + if (Release) + Code |= k_Release; + + return cRemote::Put(static_cast<enum eKeys>(Code)); + } }; #endif /* H_ENSC_VDR_INPUTDEV_INPUTDEV_H */ diff --git a/modmap.cc b/modmap.cc new file mode 100644 index 0000000..62369fa --- /dev/null +++ b/modmap.cc @@ -0,0 +1,234 @@ +/* --*- c++ -*-- + * Copyright (C) 2013 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 "modmap.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <linux/input.h> +#include <vdr/tools.h> + +#include "util.h" +#include "gen-keymap.h" + +#define ARRAY_SIZE(_a) (sizeof(_a) / sizeof(_a)[0]) + +ModifierMap::ModifierMap() +{ + for (size_t i = 0; i < ARRAY_SIZE(keytables_); ++i) { + keytables_[i] = new wchar_t[KEY_MAX]; + memset(keytables_[i], 0, sizeof keytables_[i][0] * KEY_MAX); + } + + set_default_tables(); +} + +static struct { + unsigned int key; + char code[2]; +} const DEFAULT_MAP[] = { + { KEY_A, { 'a', 'A' } }, + { KEY_B, { 'b', 'B' } }, + { KEY_C, { 'c', 'C' } }, + { KEY_D, { 'd', 'D' } }, + { KEY_E, { 'e', 'E' } }, + { KEY_F, { 'f', 'F' } }, + { KEY_G, { 'g', 'G' } }, + { KEY_H, { 'h', 'H' } }, + { KEY_I, { 'i', 'I' } }, + { KEY_J, { 'j', 'J' } }, + { KEY_K, { 'k', 'K' } }, + { KEY_L, { 'l', 'L' } }, + { KEY_M, { 'm', 'M' } }, + { KEY_N, { 'n', 'N' } }, + { KEY_O, { 'o', 'O' } }, + { KEY_P, { 'p', 'P' } }, + { KEY_Q, { 'q', 'Q' } }, + { KEY_R, { 'r', 'R' } }, + { KEY_S, { 's', 'S' } }, + { KEY_T, { 't', 'T' } }, + { KEY_U, { 'u', 'U' } }, + { KEY_V, { 'v', 'V' } }, + { KEY_W, { 'w', 'W' } }, + { KEY_X, { 'x', 'X' } }, + { KEY_Y, { 'y', 'Y' } }, + { KEY_Z, { 'z', 'Z' } }, + { KEY_0, { '0', ')' } }, + { KEY_1, { '1', '!' } }, + { KEY_2, { '2', '@' } }, + { KEY_3, { '3', '#' } }, + { KEY_4, { '4', '$' } }, + { KEY_5, { '5', '%' } }, + { KEY_6, { '6', '^' } }, + { KEY_7, { '7', '&' } }, + { KEY_8, { '8', '*' } }, + { KEY_9, { '9', '(' } }, +}; + +void ModifierMap::set_default_tables() +{ +#define assign(_kt, _idx) \ + keytables_[_kt][DEFAULT_MAP[i].key] = DEFAULT_MAP[i].code[_idx] + + for (size_t i = 0; i < ARRAY_SIZE(DEFAULT_MAP); ++i) { + assign(ktNORMAL, 0); + assign(ktSHIFT, 1); + if (i < 27) + keytables_[ktCONTROL][DEFAULT_MAP[i].key] = i + 1; + } +#undef assign +} + +ModifierMap::~ModifierMap() +{ + for (size_t i = ARRAY_SIZE(keytables_); i > 0; --i) + delete [] keytables_[i-1]; +} + +static wchar_t utf8_to_wchar(char const *str) +{ + wchar_t r = 0; + size_t cnt; + unsigned char c = str[0]; + + if ((c & 0x80) == 0) { + cnt = 1; + } else if ((c & 0xe0) == 0xc0) { + cnt = 2; + c &= 0x1f; + } else if ((c & 0xf0) == 0xe0) { + cnt = 3; + c &= 0x0f; + } else if ((c & 0xf8) == 0xf0) { + cnt = 4; + c &= 0x07; + } else { + return L'\0'; + } + + if (str[cnt] != '\0') + return L'\0'; + + r = c; + + for (size_t i = 1; i < cnt; ++i) { + c = str[i]; + if ((c & 0xc0) != 0x80) + return L'\0'; + + r <<= 6; + r |= c & 0x3f; + } + + return r; +} + +bool ModifierMap::read_modmap(char const *fname) +{ + FILE *f = fopen(fname, "r"); + cReadLine r; + + if (!f) { + esyslog("failed to open keymap file '%s': %s", + fname, strerror(errno)); + return false; + } + + for (size_t line_num = 1;; ++line_num) { + static char const DELIMS[] = " \t"; + char *buf = r.Read(f); + char *buf_next; + char const *keycode; + struct keymap_def const *keydef; + + if (!buf) + break; + + keycode = strtok_r(buf, DELIMS, &buf_next); + if (!keycode || strchr(keycode, '#') != NULL) + continue; + + keydef = Perfect_Hash::in_word_set(keycode, strlen(keycode)); + if (!keydef) { + esyslog("%s:%zu invalid keycode '%s'", fname, line_num, + keycode); + continue; + } + + for (size_t v_idx = 0; v_idx < ARRAY_SIZE(keytables_); ++v_idx) { + char const *s; + + s = strtok_r(NULL, DELIMS, &buf_next); + if (!s) + break; + + if (strncmp(s, "\\x", 2) == 0) { + keytables_[v_idx][keydef->num] = strtoul(s+2, NULL, 16); + // \todo: error checks + } else if (strncmp(s, "\\", 2) == 0) { + keytables_[v_idx][keydef->num] = strtoul(s+1, NULL, 8); + // \todo: error checks + } else if (strcmp(s, "DEF") == 0) { + // noop + } else { + wchar_t v = utf8_to_wchar(s); + + if (!v) + esyslog("%s:%zu invalid utf-8 char '%s'", + fname, line_num, s); + else + keytables_[v_idx][keydef->num] = v; + } + } + + if (strtok_r(NULL, DELIMS, &buf_next)) + esyslog("%s:%zu superflous data", fname, line_num); + } + + return true; +} + +bool ModifierMap::translate(wchar_t &chr, unsigned int code, + unsigned long mask) const +{ + ssize_t idx = -1; + + if (code >= KEY_MAX) + return false; + + if (test_bit(modCAPSLOCK, &mask)) + change_bit(modSHIFT, &mask); + + clear_bit(modCAPSLOCK, &mask); + clear_bit(modNUMLOCK, &mask); // \todo: implement me! + + if (test_bit(modCONTROL, &mask)) + idx = ktCONTROL; + else if (test_bit(modSHIFT, &mask) && test_bit(modMODE, &mask)) + idx = ktMODE_SHIFT; + else if (test_bit(modSHIFT, &mask)) + idx = ktSHIFT; + else if (test_bit(modMODE, &mask)) + idx = ktMODE; + else + idx = ktNORMAL; + + if (idx != -1) + chr = keytables_[idx][code]; + + return idx != -1 && chr != L'\0'; +} diff --git a/modmap.h b/modmap.h new file mode 100644 index 0000000..75e1595 --- /dev/null +++ b/modmap.h @@ -0,0 +1,63 @@ +/* --*- c++ -*-- + * Copyright (C) 2013 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 HH_ENSC_VDR_INPUTDEV_MODMAP_HH +#define HH_ENSC_VDR_INPUTDEV_MODMAP_HH + +class ModifierMap { +public: + enum modifier { + modSHIFT, + modCONTROL, + modALT, + modMETA, + modNUMLOCK, + modCAPSLOCK, + modMODE, + + _modMAX, + }; + + ModifierMap(); + ~ModifierMap(); + + bool read_modmap(char const *fname); + bool translate(wchar_t &chr, unsigned int code, + unsigned long mask) const; + +private: + enum { + ktNORMAL, + ktSHIFT, + ktCONTROL, + ktMODE, + ktMODE_SHIFT, + ktUNUSED0, + ktUNUSED1, + ktUNUSED2, + + _ktMAX + }; + + typedef wchar_t *keytable_t; + + // same semantics like 'keycode' in xmodmap(1) + keytable_t keytables_[_ktMAX]; + + void set_default_tables(void); +}; + +#endif /* HH_ENSC_VDR_INPUTDEV_MODMAP_HH */ @@ -20,6 +20,7 @@ #include <vdr/plugin.h> #include "inputdev.h" +#include "modmap.h" static char const *DEFAULT_SOCKET_PATH = SOCKET_PATH; static const char *VERSION = PACKAGE_VERSION; @@ -28,6 +29,7 @@ static const char *DESCRIPTION = trNOOP("Linux input device plugin"); class cInputDevicePlugin : public cPlugin { private: class cInputDeviceController *controller_; + ModifierMap mod_map_; enum { enSOCKET, @@ -40,6 +42,7 @@ private: } socket_; cString coldplug_dir; + cString mod_map_fname_; private: cInputDevicePlugin(cInputDevicePlugin const &); @@ -73,6 +76,7 @@ bool cInputDevicePlugin::ProcessArgs(int argc, char *argv[]) static struct option const CMDLINE_OPTIONS[] = { { "systemd", required_argument, NULL, 'S' }, { "socket", required_argument, NULL, 's' }, + { "modmap", required_argument, NULL, 'M' }, { } }; @@ -83,7 +87,7 @@ bool cInputDevicePlugin::ProcessArgs(int argc, char *argv[]) for (;;) { int c; - c = getopt_long(argc, argv, "S:s:", CMDLINE_OPTIONS, NULL); + c = getopt_long(argc, argv, "S:s:M:", CMDLINE_OPTIONS, NULL); if (c == -1) break; @@ -98,6 +102,7 @@ bool cInputDevicePlugin::ProcessArgs(int argc, char *argv[]) return false; #endif case 's': socket_path = optarg; break; + case 'M': mod_map_fname_ = optarg; break; default: esyslog("%s: invalid option\n", Name()); return false; @@ -129,7 +134,11 @@ bool cInputDevicePlugin::Initialize(void) { bool is_ok; - controller_ = new cInputDeviceController(*this); + if (mod_map_fname_ != "") + mod_map_.read_modmap(mod_map_fname_); + // \todo: handle errors? + + controller_ = new cInputDeviceController(*this, mod_map_); switch (socket_type_) { #ifdef VDR_USE_SYSTEMD |