summaryrefslogtreecommitdiffstats
path: root/xbmc/input/ButtonTranslator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/input/ButtonTranslator.cpp')
-rw-r--r--xbmc/input/ButtonTranslator.cpp436
1 files changed, 436 insertions, 0 deletions
diff --git a/xbmc/input/ButtonTranslator.cpp b/xbmc/input/ButtonTranslator.cpp
new file mode 100644
index 0000000..193fc44
--- /dev/null
+++ b/xbmc/input/ButtonTranslator.cpp
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ButtonTranslator.h"
+
+#include "AppTranslator.h"
+#include "CustomControllerTranslator.h"
+#include "FileItem.h"
+#include "GamepadTranslator.h"
+#include "IButtonMapper.h"
+#include "IRTranslator.h"
+#include "Key.h"
+#include "KeyboardTranslator.h"
+#include "WindowTranslator.h"
+#include "filesystem/Directory.h"
+#include "guilib/WindowIDs.h"
+#include "input/actions/ActionIDs.h"
+#include "input/actions/ActionTranslator.h"
+#include "input/mouse/MouseTranslator.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <utility>
+
+using namespace KODI;
+
+// Add the supplied device name to the list of connected devices
+bool CButtonTranslator::AddDevice(const std::string& strDevice)
+{
+ // Only add the device if it isn't already in the list
+ if (m_deviceList.find(strDevice) != m_deviceList.end())
+ return false;
+
+ // Add the device
+ m_deviceList.insert(strDevice);
+
+ // New device added so reload the key mappings
+ Load();
+
+ return true;
+}
+
+bool CButtonTranslator::RemoveDevice(const std::string& strDevice)
+{
+ // Find the device
+ auto it = m_deviceList.find(strDevice);
+ if (it == m_deviceList.end())
+ return false;
+
+ // Remove the device
+ m_deviceList.erase(it);
+
+ // Device removed so reload the key mappings
+ Load();
+
+ return true;
+}
+
+bool CButtonTranslator::Load()
+{
+ Clear();
+
+ // Directories to search for keymaps. They're applied in this order,
+ // so keymaps in profile/keymaps/ override e.g. system/keymaps
+ static std::vector<std::string> DIRS_TO_CHECK = {"special://xbmc/system/keymaps/",
+ "special://masterprofile/keymaps/",
+ "special://profile/keymaps/"};
+
+ bool success = false;
+
+ for (const auto& dir : DIRS_TO_CHECK)
+ {
+ if (XFILE::CDirectory::Exists(dir))
+ {
+ CFileItemList files;
+ XFILE::CDirectory::GetDirectory(dir, files, ".xml", XFILE::DIR_FLAG_DEFAULTS);
+ // Sort the list for filesystem based priorities, e.g. 01-keymap.xml, 02-keymap-overrides.xml
+ files.Sort(SortByFile, SortOrderAscending);
+ for (int fileIndex = 0; fileIndex < files.Size(); ++fileIndex)
+ {
+ if (!files[fileIndex]->m_bIsFolder)
+ success |= LoadKeymap(files[fileIndex]->GetPath());
+ }
+
+ // Load mappings for any HID devices we have connected
+ for (const auto& device : m_deviceList)
+ {
+ std::string devicedir = dir;
+ devicedir.append(device);
+ devicedir.append("/");
+ if (XFILE::CDirectory::Exists(devicedir))
+ {
+ CFileItemList files;
+ XFILE::CDirectory::GetDirectory(devicedir, files, ".xml", XFILE::DIR_FLAG_DEFAULTS);
+ // Sort the list for filesystem based priorities, e.g. 01-keymap.xml,
+ // 02-keymap-overrides.xml
+ files.Sort(SortByFile, SortOrderAscending);
+ for (int fileIndex = 0; fileIndex < files.Size(); ++fileIndex)
+ {
+ if (!files[fileIndex]->m_bIsFolder)
+ success |= LoadKeymap(files[fileIndex]->GetPath());
+ }
+ }
+ }
+ }
+ }
+
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "Error loading keymaps from: {} or {} or {}", DIRS_TO_CHECK[0],
+ DIRS_TO_CHECK[1], DIRS_TO_CHECK[2]);
+ return false;
+ }
+
+ // Done!
+ return true;
+}
+
+bool CButtonTranslator::LoadKeymap(const std::string& keymapPath)
+{
+ CXBMCTinyXML xmlDoc;
+
+ CLog::Log(LOGINFO, "Loading {}", keymapPath);
+ if (!xmlDoc.LoadFile(keymapPath))
+ {
+ CLog::Log(LOGERROR, "Error loading keymap: {}, Line {}\n{}", keymapPath, xmlDoc.ErrorRow(),
+ xmlDoc.ErrorDesc());
+ return false;
+ }
+
+ TiXmlElement* pRoot = xmlDoc.RootElement();
+ if (pRoot == nullptr)
+ {
+ CLog::Log(LOGERROR, "Error getting keymap root: {}", keymapPath);
+ return false;
+ }
+
+ std::string strValue = pRoot->Value();
+ if (strValue != "keymap")
+ {
+ CLog::Log(LOGERROR, "{} Doesn't contain <keymap>", keymapPath);
+ return false;
+ }
+
+ // run through our window groups
+ TiXmlNode* pWindow = pRoot->FirstChild();
+ while (pWindow != nullptr)
+ {
+ if (pWindow->Type() == TiXmlNode::TINYXML_ELEMENT)
+ {
+ int windowID = WINDOW_INVALID;
+ const char* szWindow = pWindow->Value();
+ if (szWindow != nullptr)
+ {
+ if (StringUtils::CompareNoCase(szWindow, "global") == 0)
+ windowID = -1;
+ else
+ windowID = CWindowTranslator::TranslateWindow(szWindow);
+ }
+ MapWindowActions(pWindow, windowID);
+ }
+ pWindow = pWindow->NextSibling();
+ }
+
+ return true;
+}
+
+CAction CButtonTranslator::GetAction(int window, const CKey& key, bool fallback)
+{
+ std::string strAction;
+
+ // handle virtual windows
+ window = CWindowTranslator::GetVirtualWindow(window);
+
+ // try to get the action from the current window
+ unsigned int actionID = GetActionCode(window, key, strAction);
+
+ if (fallback)
+ {
+ // if it's invalid, try to get it from fallback windows or the global map (window == -1)
+ while (actionID == ACTION_NONE && window > -1)
+ {
+ window = CWindowTranslator::GetFallbackWindow(window);
+ actionID = GetActionCode(window, key, strAction);
+ }
+ }
+
+ return CAction(actionID, strAction, key);
+}
+
+bool CButtonTranslator::HasLongpressMapping(int window, const CKey& key)
+{
+ // handle virtual windows
+ window = CWindowTranslator::GetVirtualWindow(window);
+ return HasLongpressMapping_Internal(window, key);
+}
+
+bool CButtonTranslator::HasLongpressMapping_Internal(int window, const CKey& key)
+{
+ std::map<int, buttonMap>::const_iterator it = m_translatorMap.find(window);
+ if (it != m_translatorMap.end())
+ {
+ uint32_t code = key.GetButtonCode();
+ code |= CKey::MODIFIER_LONG;
+ buttonMap::const_iterator it2 = (*it).second.find(code);
+
+ if (it2 != (*it).second.end())
+ return it2->second.id != ACTION_NOOP;
+
+#ifdef TARGET_POSIX
+ // Some buttoncodes changed in Hardy
+ if ((code & KEY_VKEY) == KEY_VKEY && (code & 0x0F00))
+ {
+ code &= ~0x0F00;
+ it2 = (*it).second.find(code);
+ if (it2 != (*it).second.end())
+ return true;
+ }
+#endif
+ }
+
+ // no key mapping found for the current window do the fallback handling
+ if (window > -1)
+ {
+ // first check if we have a fallback for the window
+ int fallbackWindow = CWindowTranslator::GetFallbackWindow(window);
+ if (fallbackWindow > -1 && HasLongpressMapping_Internal(fallbackWindow, key))
+ return true;
+
+ // fallback to default section
+ return HasLongpressMapping_Internal(-1, key);
+ }
+
+ return false;
+}
+
+unsigned int CButtonTranslator::GetActionCode(int window,
+ const CKey& key,
+ std::string& strAction) const
+{
+ uint32_t code = key.GetButtonCode();
+
+ std::map<int, buttonMap>::const_iterator it = m_translatorMap.find(window);
+ if (it == m_translatorMap.end())
+ return ACTION_NONE;
+
+ buttonMap::const_iterator it2 = (*it).second.find(code);
+ unsigned int action = ACTION_NONE;
+ if (it2 == (*it).second.end() &&
+ code & CKey::MODIFIER_LONG) // If long action not found, try short one
+ {
+ code &= ~CKey::MODIFIER_LONG;
+ it2 = (*it).second.find(code);
+ }
+
+ if (it2 != (*it).second.end())
+ {
+ action = (*it2).second.id;
+ strAction = (*it2).second.strID;
+ }
+
+#ifdef TARGET_POSIX
+ // Some buttoncodes changed in Hardy
+ if (action == ACTION_NONE && (code & KEY_VKEY) == KEY_VKEY && (code & 0x0F00))
+ {
+ CLog::Log(LOGDEBUG, "{}: Trying Hardy keycode for {:#04x}", __FUNCTION__, code);
+ code &= ~0x0F00;
+ it2 = (*it).second.find(code);
+ if (it2 != (*it).second.end())
+ {
+ action = (*it2).second.id;
+ strAction = (*it2).second.strID;
+ }
+ }
+#endif
+
+ return action;
+}
+
+void CButtonTranslator::MapAction(uint32_t buttonCode, const std::string& szAction, buttonMap& map)
+{
+ unsigned int action = ACTION_NONE;
+ if (!CActionTranslator::TranslateString(szAction, action) || buttonCode == 0)
+ return; // no valid action, or an invalid buttoncode
+
+ // have a valid action, and a valid button - map it.
+ // check to see if we've already got this (button,action) pair defined
+ buttonMap::iterator it = map.find(buttonCode);
+ if (it == map.end() || (*it).second.id != action || (*it).second.strID != szAction)
+ {
+ // NOTE: This multimap is only being used as a normal map at this point (no support
+ // for multiple actions per key)
+ if (it != map.end())
+ map.erase(it);
+ CButtonAction button;
+ button.id = action;
+ button.strID = szAction;
+ map.insert(std::pair<uint32_t, CButtonAction>(buttonCode, button));
+ }
+}
+
+void CButtonTranslator::MapWindowActions(const TiXmlNode* pWindow, int windowID)
+{
+ if (pWindow == nullptr || windowID == WINDOW_INVALID)
+ return;
+
+ const TiXmlNode* pDevice;
+
+ static const std::vector<std::string> types = {"gamepad", "remote", "universalremote",
+ "keyboard", "mouse", "appcommand"};
+
+ for (const auto& type : types)
+ {
+ for (pDevice = pWindow->FirstChild(type); pDevice != nullptr;
+ pDevice = pDevice->NextSiblingElement(type))
+ {
+ buttonMap map;
+ std::map<int, buttonMap>::iterator it = m_translatorMap.find(windowID);
+ if (it != m_translatorMap.end())
+ {
+ map = std::move(it->second);
+ m_translatorMap.erase(it);
+ }
+
+ const TiXmlElement* pButton = pDevice->FirstChildElement();
+
+ while (pButton != nullptr)
+ {
+ uint32_t buttonCode = 0;
+
+ if (type == "gamepad")
+ buttonCode = CGamepadTranslator::TranslateString(pButton->Value());
+ else if (type == "remote")
+ buttonCode = CIRTranslator::TranslateString(pButton->Value());
+ else if (type == "universalremote")
+ buttonCode = CIRTranslator::TranslateUniversalRemoteString(pButton->Value());
+ else if (type == "keyboard")
+ buttonCode = CKeyboardTranslator::TranslateButton(pButton);
+ else if (type == "mouse")
+ buttonCode = CMouseTranslator::TranslateCommand(pButton);
+ else if (type == "appcommand")
+ buttonCode = CAppTranslator::TranslateAppCommand(pButton->Value());
+
+ if (buttonCode != 0)
+ {
+ if (pButton->FirstChild() && pButton->FirstChild()->Value()[0])
+ MapAction(buttonCode, pButton->FirstChild()->Value(), map);
+ else
+ {
+ buttonMap::iterator it = map.find(buttonCode);
+ while (it != map.end())
+ {
+ map.erase(it);
+ it = map.find(buttonCode);
+ }
+ }
+ }
+ pButton = pButton->NextSiblingElement();
+ }
+
+ // add our map to our table
+ if (!map.empty())
+ m_translatorMap.insert(std::make_pair(windowID, std::move(map)));
+ }
+ }
+
+ for (const auto& it : m_buttonMappers)
+ {
+ const std::string& device = it.first;
+ IButtonMapper* mapper = it.second;
+
+ // Map device actions
+ pDevice = pWindow->FirstChild(device);
+ while (pDevice != nullptr)
+ {
+ mapper->MapActions(windowID, pDevice);
+ pDevice = pDevice->NextSibling(device);
+ }
+ }
+}
+
+void CButtonTranslator::Clear()
+{
+ m_translatorMap.clear();
+
+ for (auto it : m_buttonMappers)
+ it.second->Clear();
+}
+
+void CButtonTranslator::RegisterMapper(const std::string& device, IButtonMapper* mapper)
+{
+ m_buttonMappers[device] = mapper;
+}
+
+void CButtonTranslator::UnregisterMapper(const IButtonMapper* mapper)
+{
+ for (auto it = m_buttonMappers.begin(); it != m_buttonMappers.end(); ++it)
+ {
+ if (it->second == mapper)
+ {
+ m_buttonMappers.erase(it);
+ break;
+ }
+ }
+}
+
+uint32_t CButtonTranslator::TranslateString(const std::string& strMap, const std::string& strButton)
+{
+ if (strMap == "KB") // standard keyboard map
+ {
+ return CKeyboardTranslator::TranslateString(strButton);
+ }
+ else if (strMap == "XG") // xbox gamepad map
+ {
+ return CGamepadTranslator::TranslateString(strButton);
+ }
+ else if (strMap == "R1") // xbox remote map
+ {
+ return CIRTranslator::TranslateString(strButton);
+ }
+ else if (strMap == "R2") // xbox universal remote map
+ {
+ return CIRTranslator::TranslateUniversalRemoteString(strButton);
+ }
+ else
+ {
+ return 0;
+ }
+}