summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--Makefile32
-rw-r--r--inputdev.cc69
-rw-r--r--inputdev.h15
-rw-r--r--modmap.cc234
-rw-r--r--modmap.h63
-rw-r--r--plugin.cc13
7 files changed, 403 insertions, 29 deletions
diff --git a/.gitignore b/.gitignore
index 19ae020..990e74a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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*
diff --git a/Makefile b/Makefile
index bf23ae6..9272057 100644
--- a/Makefile
+++ b/Makefile
@@ -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;
}
diff --git a/inputdev.h b/inputdev.h
index 7fbfff0..05c2992 100644
--- a/inputdev.h
+++ b/inputdev.h
@@ -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 */
diff --git a/plugin.cc b/plugin.cc
index 313947e..dbea398 100644
--- a/plugin.cc
+++ b/plugin.cc
@@ -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