actionmap: Implement key translations
authorMirakels <mirakels@openpli.org>
Fri, 18 Dec 2015 12:28:05 +0000 (13:28 +0100)
committerMirakels <mirakels@openpli.org>
Fri, 18 Dec 2015 12:33:14 +0000 (13:33 +0100)
Some remotes lack support for certian buttons or have buttons
with different key codes. This translation feature allows to
translate specific keys into a different one reducing to need
to create bloated keymaps to map several buttons to the same function.

Some remotes are very small and lack certain keys. Using a toggle
feature based on a specific button, buttons can be multi functional

The definitions are like a keymap.xml and the translations can be added
to the default keymap.xml. But to keep keymap.xml clean and to let it serve
as a standard keymap, enigma2 will look for a keytranslation.xml file.
In is just like a keymap.xml but can be used for STB specific mappings
and translations.

An example keytranslation.xml in which the number keys are mapped
to specific other keys is shown below.  The toggle="1" means that
translation will only be done after the toggle key has been pressed. The
toggle element defines the toggle button, in this case the BACKSPACE key.
key definitions without the toggle= attribute (or with a value not equal
to "1") will always be translated regardless the toggle state.

The device name is taken from the /dev/input identifications. Check your
enigma2 log to find the proper names.

<keymap>
      <translate>
              <device name="Small remote device name">
                      <toggle from="KEY_BACKSPACE"/>
                      <key from="KEY_1" to="KEY_RED" toggle="1"/>
                      <key from="KEY_2" to="KEY_GREEN" toggle="1"/>
                      <key from="KEY_3" to="KEY_YELLOW" toggle="1"/>
                      <key from="KEY_4" to="KEY_BLUE" toggle="1"/>
                      <key from="KEY_5" to="KEY_PREVIOUS" toggle="1"/>
                      <key from="KEY_6" to="KEY_NEXT" toggle="1"/>
                      <key from="KEY_7" to="KEY_REWIND" toggle="1"/>
                      <key from="KEY_8" to="KEY_STOP" toggle="1"/>
                      <key from="KEY_9" to="KEY_FASTFORWARD" toggle="1"/>
                      <key from="KEY_0" to="KEY_PLAYPAUSE" toggle="1"/>

                      <key from="KEY_F7" to="KEY_MENU"/>
                      <key from="KEY_F1" to="KEY_VIDEO"/>
                      <key from="KEY_HOME" to="KEY_INFO"/>
                      <key from="KEY_BACK" to="KEY_EXIT"/>
                      <key from="KEY_F2" to="KEY_EPG"/>
                      <key from="KEY_ENTER" to="KEY_OK"/>
              </device>
      </translate>
      <translate>
              <device name="dreambox advanced remote control (native)">
                      <key from="KEY_PLAY" to="KEY_PLAYPAUSE"/>
              </device>
      </translate>
</keymap>

keymapparser.py
lib/actions/action.cpp
lib/actions/action.h
lib/python/Components/UsageConfig.py
mytest.py

index 57044a3..3562075 100644 (file)
@@ -13,6 +13,25 @@ class KeymapError(Exception):
     def __str__(self):
         return self.msg
 
+def getKeyId(id):
+       if len(id) == 1:
+               keyid = ord(id) | 0x8000
+       elif id[0] == '\\':
+               if id[1] == 'x':
+                       keyid = int(id[2:], 0x10) | 0x8000
+               elif id[1] == 'd':
+                       keyid = int(id[2:]) | 0x8000
+               else:
+                       raise KeymapError("[keymapparser] key id '" + str(id) + "' is neither hex nor dec")
+       else:
+               try:
+                       keyid = KEYIDS[id]
+               except:
+                       raise KeymapError("[keymapparser] key id '" + str(id) + "' is illegal")
+
+       return keyid
+
+
 def parseKeys(context, filename, actionmap, device, keys):
        for x in keys.findall("key"):
                get_attr = x.attrib.get
@@ -24,50 +43,67 @@ def parseKeys(context, filename, actionmap, device, keys):
 
                flags = sum(map(flag_ascii_to_id, flags))
 
-               assert mapto, "%s: must specify mapto in context %s, id '%s'" % (filename, context, id)
-               assert id, "%s: must specify id in context %s, mapto '%s'" % (filename, context, mapto)
-               assert flags, "%s: must specify at least one flag in context %s, id '%s'" % (filename, context, id)
-
-               if len(id) == 1:
-                       keyid = ord(id) | 0x8000
-               elif id[0] == '\\':
-                       if id[1] == 'x':
-                               keyid = int(id[2:], 0x10) | 0x8000
-                       elif id[1] == 'd':
-                               keyid = int(id[2:]) | 0x8000
-                       else:
-                               raise KeymapError("key id '" + str(id) + "' is neither hex nor dec")
-               else:
-                       try:
-                               keyid = KEYIDS[id]
-                       except:
-                               raise KeymapError("key id '" + str(id) + "' is illegal")
-#                              print context + "::" + mapto + " -> " + device + "." + hex(keyid)
+               assert mapto, "[keymapparser] %s: must specify mapto in context %s, id '%s'" % (filename, context, id)
+               assert id, "[keymapparser] %s: must specify id in context %s, mapto '%s'" % (filename, context, mapto)
+               assert flags, "[keymapparser] %s: must specify at least one flag in context %s, id '%s'" % (filename, context, id)
+
+               keyid = getKeyId(id)
+#                              print "[keymapparser] " + context + "::" + mapto + " -> " + device + "." + hex(keyid)
                actionmap.bindKey(filename, device, keyid, flags, context, mapto)
                addKeyBinding(filename, keyid, context, mapto, flags)
 
+
+def parseTrans(filename, actionmap, device, keys):
+       for x in keys.findall("toggle"):
+               get_attr = x.attrib.get
+               toggle_key = get_attr("from")
+               toggle_key = getKeyId(toggle_key)
+                actionmap.bindToggle(filename, device, toggle_key)
+
+       for x in keys.findall("key"):
+               get_attr = x.attrib.get
+               keyin = get_attr("from")
+               keyout = get_attr("to")
+               toggle = get_attr("toggle") or "0"
+               assert keyin, "[keymapparser] %s: must specify key to translate from '%s'" % (filename, keyin)
+               assert keyout, "[keymapparser] %s: must specify key to translate to '%s'" % (filename, keyout)
+
+               keyin  = getKeyId(keyin)
+               keyout = getKeyId(keyout)
+               toggle = int(toggle)
+                actionmap.bindTranslation(filename, device, keyin, keyout, toggle)
+
+
 def readKeymap(filename):
        p = enigma.eActionMap.getInstance()
        assert p
 
-       source = open(filename)
+       try:
+               source = open(filename)
+       except:
+               print "[keymapparser] keymap file " + filename + " not found"
+               return
 
        try:
                dom = xml.etree.cElementTree.parse(source)
        except:
-               raise KeymapError("keymap %s not well-formed." % filename)
+               raise KeymapError("[keymapparser] keymap %s not well-formed." % filename)
 
        keymap = dom.getroot()
 
        for cmap in keymap.findall("map"):
                context = cmap.attrib.get("context")
-               assert context, "map must have context"
+               assert context, "[keymapparser] map must have context"
 
                parseKeys(context, filename, p, "generic", cmap)
 
                for device in cmap.findall("device"):
                        parseKeys(context, filename, p, device.attrib.get("name"), device)
 
+       for ctrans in keymap.findall("translate"):
+               for device in ctrans.findall("device"):
+                       parseTrans(filename, p, device.attrib.get("name"), device)
+
 def removeKeymap(filename):
        p = enigma.eActionMap.getInstance()
        p.unbindKeyDomain(filename)
index b8c08ee..d7367d1 100644 (file)
@@ -122,6 +122,50 @@ void eActionMap::bindKey(const std::string &domain, const std::string &device, i
        m_python_keys.insert(std::pair<std::string,ePythonKeyBinding>(context, bind));
 }
 
+
+void eActionMap::bindTranslation(const std::string &domain, const std::string &device, int keyin, int keyout, int toggle)
+{
+       //eDebug("[eActionMap] bind translation for %s from %d to %d toggle=%d in %s", device.c_str(), keyin, keyout, toggle, domain.c_str());
+       eTranslationBinding trans;
+
+       trans.m_keyin  = keyin;
+       trans.m_keyout = keyout;
+       trans.m_toggle = toggle;
+       trans.m_domain = domain;
+
+       std::map<std::string, eDeviceBinding>::iterator r = m_rcDevices.find(device);
+       if (r == m_rcDevices.end())
+       {
+               eDeviceBinding rc;
+               rc.m_togglekey = KEY_RESERVED;
+               rc.m_toggle = 0;;
+               rc.m_translations.push_back(trans);
+               m_rcDevices.insert(std::pair<std::string, eDeviceBinding>(device, rc));
+       }
+       else
+               r->second.m_translations.push_back(trans);
+}
+
+
+void eActionMap::bindToggle(const std::string &domain, const std::string &device, int togglekey)
+{
+       //eDebug("[eActionMap] bind togglekey for %s togglekey=%d in %s", device.c_str(), togglekey, domain.c_str());
+       std::map<std::string, eDeviceBinding>::iterator r = m_rcDevices.find(device);
+       if (r == m_rcDevices.end())
+       {
+               eDeviceBinding rc;
+               rc.m_togglekey = togglekey;
+               rc.m_toggle = 0;;
+               m_rcDevices.insert(std::pair<std::string, eDeviceBinding>(device, rc));
+       }
+       else
+       {
+               r->second.m_togglekey = togglekey;
+               r->second.m_toggle = 0;
+       }
+}
+
+
 void eActionMap::unbindKeyDomain(const std::string &domain)
 {
        //eDebug("[eActionMap] unbindDomain %s", domain.c_str());
@@ -152,8 +196,31 @@ struct call_entry
 void eActionMap::keyPressed(const std::string &device, int key, int flags)
 {
        //eDebug("[eActionMap] key from %s: %d %d", device.c_str(), key, flags);
-       std::list<call_entry> call_list;
 
+       // Check for remotes that need key translations
+       std::map<std::string, eDeviceBinding>::iterator r = m_rcDevices.find(device);
+       if (r != m_rcDevices.end())
+       {
+
+               if (key == r->second.m_togglekey && flags == eRCKey::flagMake)
+               {
+                       r->second.m_toggle ^= 1;
+                       //eDebug("[eActionMap]   toggle key %d: now %d", key, r->second.m_toggle);
+                       return;
+               }
+               std::vector<eTranslationBinding> *trans = &r->second.m_translations;
+               for (std::vector<eTranslationBinding>::iterator t(trans->begin()); t != trans->end(); ++t)
+               {
+                       if (t->m_keyin == key && (t->m_toggle == 0 || r->second.m_toggle))
+                       {
+                               //eDebug("[eActionMap]   translate from %d to %d", key, t->m_keyout);
+                               key = t->m_keyout;
+                               break;
+                       }
+               }
+       }
+
+       std::vector<call_entry> call_list;
        // iterate active contexts
        for (std::multimap<int,eActionBinding>::iterator c(m_bindings.begin()); c != m_bindings.end(); ++c)
        {
@@ -226,7 +293,7 @@ void eActionMap::keyPressed(const std::string &device, int key, int flags)
 
        int res = 0;
        // iterate over all to not loose a reference
-       for (std::list<call_entry>::iterator i(call_list.begin()); i != call_list.end(); ++i)
+       for (std::vector<call_entry>::iterator i(call_list.begin()); i != call_list.end(); ++i)
        {
                if (i->m_fnc)
                {
index cdc854f..871311b 100644 (file)
@@ -6,6 +6,7 @@
 #include <lib/python/python.h>
 #include <string>
 #include <map>
+#include <vector>
 
 class eWidget;
 
@@ -29,6 +30,8 @@ public:
        void unbindAction(const std::string &context, SWIG_PYOBJECT(ePyObject) function);
 
        void bindKey(const std::string &domain, const std::string &device, int key, int flags, const std::string &context, const std::string &action);
+       void bindTranslation(const std::string &domain, const std::string &device, int keyin, int keyout, int toggle);
+       void bindToggle(const std::string &domain, const std::string &device, int togglekey);
        void unbindKeyDomain(const std::string &domain);
 
        void keyPressed(const std::string &device, int key, int flags);
@@ -55,6 +58,21 @@ private:
 
        std::multimap<int, eActionBinding> m_bindings;
 
+       struct eTranslationBinding
+       {
+               int m_keyin;
+               int m_keyout;
+               int m_toggle;
+               std::string m_domain;
+       };
+       struct eDeviceBinding
+       {
+               int m_togglekey;
+               int m_toggle;
+               std::vector<eTranslationBinding> m_translations;
+       };
+       std::map <std::string, eDeviceBinding> m_rcDevices;
+
        friend struct compare_string_keybind_native;
        struct eNativeKeyBinding
        {
index c853457..3ddf820 100644 (file)
@@ -356,6 +356,7 @@ def InitUsageConfig():
                config.usage.output_12V.addNotifier(set12VOutput, immediate_feedback=False)
 
        config.usage.keymap = ConfigText(default = eEnv.resolve("${datadir}/enigma2/keymap.xml"))
+       config.usage.keytrans = ConfigText(default = eEnv.resolve("${datadir}/enigma2/keytranslation.xml"))
 
        config.seek = ConfigSubsection()
        config.seek.selfdefined_13 = ConfigNumber(default=15)
index f30f5c2..2c716de 100644 (file)
--- a/mytest.py
+++ b/mytest.py
@@ -552,6 +552,7 @@ Components.UsageConfig.InitUsageConfig()
 profile("keymapparser")
 import keymapparser
 keymapparser.readKeymap(config.usage.keymap.value)
+keymapparser.readKeymap(config.usage.keytrans.value)
 
 profile("Network")
 import Components.Network